├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── Walnut ├── premake5.lua └── src │ └── Walnut │ ├── Application.cpp │ ├── Application.h │ ├── EntryPoint.h │ ├── ImGui │ ├── ImGuiBuild.cpp │ └── Roboto-Regular.embed │ ├── Image.cpp │ ├── Image.h │ ├── Input │ ├── Input.cpp │ ├── Input.h │ └── KeyCodes.h │ ├── Layer.h │ ├── Random.cpp │ ├── Random.h │ └── Timer.h ├── WalnutApp ├── premake5.lua └── src │ └── WalnutApp.cpp ├── WalnutExternal.lua ├── premake5.lua ├── scripts └── Setup.bat └── vendor ├── bin ├── LICENSE.txt └── premake5.exe └── stb_image └── stb_image.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | .vs/ 3 | bin/ 4 | bin-int/ 5 | 6 | # Files 7 | *.vcxproj 8 | *.vcxproj.user 9 | *.vcxproj.filters 10 | *.sln 11 | 12 | # Exclude 13 | !vendor/bin -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Walnut/vendor/imgui/src"] 2 | path = Walnut/vendor/imgui/src 3 | url = https://github.com/ocornut/imgui 4 | branch = docking 5 | [submodule "vendor/imgui"] 6 | path = vendor/imgui 7 | url = https://github.com/TheCherno/imgui 8 | branch = docking 9 | [submodule "vendor/glfw"] 10 | path = vendor/glfw 11 | url = https://github.com/thecherno/glfw 12 | [submodule "vendor/glm"] 13 | path = vendor/glm 14 | url = https://github.com/g-truc/glm 15 | [submodule "vendor/GLFW"] 16 | path = vendor/GLFW 17 | url = https://github.com/TheCherno/GLFW 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Studio Cherno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Walnut 2 | 3 | Walnut is a simple application framework built with Dear ImGui and designed to be used with Vulkan - basically this means you can seemlessly blend real-time Vulkan rendering with a great UI library to build desktop applications. The plan is to expand Walnut to include common utilities to make immediate-mode desktop apps and simple Vulkan applications. 4 | 5 | Currently supports Windows - with macOS and Linux support planned. Setup scripts support Visual Studio 2022 by default. 6 | 7 | ![WalnutExample](https://hazelengine.com/images/ForestLauncherScreenshot.jpg) 8 | _
Forest Launcher - an application made with Walnut
_ 9 | 10 | ## Requirements 11 | - [Visual Studio 2022](https://visualstudio.com) (not strictly required, however included setup scripts only support this) 12 | - [Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows) (preferably a recent version) 13 | 14 | ## Getting Started 15 | Once you've cloned, run `scripts/Setup.bat` to generate Visual Studio 2022 solution/project files. Once you've opened the solution, you can run the WalnutApp project to see a basic example (code in `WalnutApp.cpp`). I recommend modifying that WalnutApp project to create your own application, as everything should be setup and ready to go. 16 | 17 | ### 3rd party libaries 18 | - [Dear ImGui](https://github.com/ocornut/imgui) 19 | - [GLFW](https://github.com/glfw/glfw) 20 | - [stb_image](https://github.com/nothings/stb) 21 | - [GLM](https://github.com/g-truc/glm) (included for convenience) 22 | 23 | ### Additional 24 | - Walnut uses the [Roboto](https://fonts.google.com/specimen/Roboto) font ([Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)) -------------------------------------------------------------------------------- /Walnut/premake5.lua: -------------------------------------------------------------------------------- 1 | project "Walnut" 2 | kind "StaticLib" 3 | language "C++" 4 | cppdialect "C++17" 5 | targetdir "bin/%{cfg.buildcfg}" 6 | staticruntime "off" 7 | 8 | files { "src/**.h", "src/**.cpp" } 9 | 10 | includedirs 11 | { 12 | "src", 13 | 14 | "../vendor/imgui", 15 | "../vendor/glfw/include", 16 | "../vendor/stb_image", 17 | 18 | "%{IncludeDir.VulkanSDK}", 19 | "%{IncludeDir.glm}", 20 | } 21 | 22 | links 23 | { 24 | "ImGui", 25 | "GLFW", 26 | 27 | "%{Library.Vulkan}", 28 | } 29 | 30 | targetdir ("bin/" .. outputdir .. "/%{prj.name}") 31 | objdir ("../bin-int/" .. outputdir .. "/%{prj.name}") 32 | 33 | filter "system:windows" 34 | systemversion "latest" 35 | defines { "WL_PLATFORM_WINDOWS" } 36 | 37 | filter "configurations:Debug" 38 | defines { "WL_DEBUG" } 39 | runtime "Debug" 40 | symbols "On" 41 | 42 | filter "configurations:Release" 43 | defines { "WL_RELEASE" } 44 | runtime "Release" 45 | optimize "On" 46 | symbols "On" 47 | 48 | filter "configurations:Dist" 49 | defines { "WL_DIST" } 50 | runtime "Release" 51 | optimize "On" 52 | symbols "Off" -------------------------------------------------------------------------------- /Walnut/src/Walnut/Application.cpp: -------------------------------------------------------------------------------- 1 | #include "Application.h" 2 | 3 | // 4 | // Adapted from Dear ImGui Vulkan example 5 | // 6 | 7 | #include "backends/imgui_impl_glfw.h" 8 | #include "backends/imgui_impl_vulkan.h" 9 | #include // printf, fprintf 10 | #include // abort 11 | #define GLFW_INCLUDE_NONE 12 | #define GLFW_INCLUDE_VULKAN 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | // Emedded font 20 | #include "ImGui/Roboto-Regular.embed" 21 | 22 | extern bool g_ApplicationRunning; 23 | 24 | // [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to maximize ease of testing and compatibility with old VS compilers. 25 | // To link with VS2010-era libraries, VS2015+ requires linking with legacy_stdio_definitions.lib, which we do using this pragma. 26 | // Your own project should not be affected, as you are likely to link with a newer binary of GLFW that is adequate for your version of Visual Studio. 27 | #if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) 28 | #pragma comment(lib, "legacy_stdio_definitions") 29 | #endif 30 | 31 | //#define IMGUI_UNLIMITED_FRAME_RATE 32 | #ifdef _DEBUG 33 | #define IMGUI_VULKAN_DEBUG_REPORT 34 | #endif 35 | 36 | static VkAllocationCallbacks* g_Allocator = NULL; 37 | static VkInstance g_Instance = VK_NULL_HANDLE; 38 | static VkPhysicalDevice g_PhysicalDevice = VK_NULL_HANDLE; 39 | static VkDevice g_Device = VK_NULL_HANDLE; 40 | static uint32_t g_QueueFamily = (uint32_t)-1; 41 | static VkQueue g_Queue = VK_NULL_HANDLE; 42 | static VkDebugReportCallbackEXT g_DebugReport = VK_NULL_HANDLE; 43 | static VkPipelineCache g_PipelineCache = VK_NULL_HANDLE; 44 | static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; 45 | 46 | static ImGui_ImplVulkanH_Window g_MainWindowData; 47 | static int g_MinImageCount = 2; 48 | static bool g_SwapChainRebuild = false; 49 | 50 | // Per-frame-in-flight 51 | static std::vector> s_AllocatedCommandBuffers; 52 | static std::vector>> s_ResourceFreeQueue; 53 | 54 | // Unlike g_MainWindowData.FrameIndex, this is not the the swapchain image index 55 | // and is always guaranteed to increase (eg. 0, 1, 2, 0, 1, 2) 56 | static uint32_t s_CurrentFrameIndex = 0; 57 | 58 | static Walnut::Application* s_Instance = nullptr; 59 | 60 | void check_vk_result(VkResult err) 61 | { 62 | if (err == 0) 63 | return; 64 | fprintf(stderr, "[vulkan] Error: VkResult = %d\n", err); 65 | if (err < 0) 66 | abort(); 67 | } 68 | 69 | #ifdef IMGUI_VULKAN_DEBUG_REPORT 70 | static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) 71 | { 72 | (void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments 73 | fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage); 74 | return VK_FALSE; 75 | } 76 | #endif // IMGUI_VULKAN_DEBUG_REPORT 77 | 78 | static void SetupVulkan(const char** extensions, uint32_t extensions_count) 79 | { 80 | VkResult err; 81 | 82 | // Create Vulkan Instance 83 | { 84 | VkInstanceCreateInfo create_info = {}; 85 | create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 86 | create_info.enabledExtensionCount = extensions_count; 87 | create_info.ppEnabledExtensionNames = extensions; 88 | #ifdef IMGUI_VULKAN_DEBUG_REPORT 89 | // Enabling validation layers 90 | const char* layers[] = { "VK_LAYER_KHRONOS_validation" }; 91 | create_info.enabledLayerCount = 1; 92 | create_info.ppEnabledLayerNames = layers; 93 | 94 | // Enable debug report extension (we need additional storage, so we duplicate the user array to add our new extension to it) 95 | const char** extensions_ext = (const char**)malloc(sizeof(const char*) * (extensions_count + 1)); 96 | memcpy(extensions_ext, extensions, extensions_count * sizeof(const char*)); 97 | extensions_ext[extensions_count] = "VK_EXT_debug_report"; 98 | create_info.enabledExtensionCount = extensions_count + 1; 99 | create_info.ppEnabledExtensionNames = extensions_ext; 100 | 101 | // Create Vulkan Instance 102 | err = vkCreateInstance(&create_info, g_Allocator, &g_Instance); 103 | check_vk_result(err); 104 | free(extensions_ext); 105 | 106 | // Get the function pointer (required for any extensions) 107 | auto vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkCreateDebugReportCallbackEXT"); 108 | IM_ASSERT(vkCreateDebugReportCallbackEXT != NULL); 109 | 110 | // Setup the debug report callback 111 | VkDebugReportCallbackCreateInfoEXT debug_report_ci = {}; 112 | debug_report_ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; 113 | debug_report_ci.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; 114 | debug_report_ci.pfnCallback = debug_report; 115 | debug_report_ci.pUserData = NULL; 116 | err = vkCreateDebugReportCallbackEXT(g_Instance, &debug_report_ci, g_Allocator, &g_DebugReport); 117 | check_vk_result(err); 118 | #else 119 | // Create Vulkan Instance without any debug feature 120 | err = vkCreateInstance(&create_info, g_Allocator, &g_Instance); 121 | check_vk_result(err); 122 | IM_UNUSED(g_DebugReport); 123 | #endif 124 | } 125 | 126 | // Select GPU 127 | { 128 | uint32_t gpu_count; 129 | err = vkEnumeratePhysicalDevices(g_Instance, &gpu_count, NULL); 130 | check_vk_result(err); 131 | IM_ASSERT(gpu_count > 0); 132 | 133 | VkPhysicalDevice* gpus = (VkPhysicalDevice*)malloc(sizeof(VkPhysicalDevice) * gpu_count); 134 | err = vkEnumeratePhysicalDevices(g_Instance, &gpu_count, gpus); 135 | check_vk_result(err); 136 | 137 | // If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers 138 | // most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple 139 | // dedicated GPUs) is out of scope of this sample. 140 | int use_gpu = 0; 141 | for (int i = 0; i < (int)gpu_count; i++) 142 | { 143 | VkPhysicalDeviceProperties properties; 144 | vkGetPhysicalDeviceProperties(gpus[i], &properties); 145 | if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) 146 | { 147 | use_gpu = i; 148 | break; 149 | } 150 | } 151 | 152 | g_PhysicalDevice = gpus[use_gpu]; 153 | free(gpus); 154 | } 155 | 156 | // Select graphics queue family 157 | { 158 | uint32_t count; 159 | vkGetPhysicalDeviceQueueFamilyProperties(g_PhysicalDevice, &count, NULL); 160 | VkQueueFamilyProperties* queues = (VkQueueFamilyProperties*)malloc(sizeof(VkQueueFamilyProperties) * count); 161 | vkGetPhysicalDeviceQueueFamilyProperties(g_PhysicalDevice, &count, queues); 162 | for (uint32_t i = 0; i < count; i++) 163 | if (queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) 164 | { 165 | g_QueueFamily = i; 166 | break; 167 | } 168 | free(queues); 169 | IM_ASSERT(g_QueueFamily != (uint32_t)-1); 170 | } 171 | 172 | // Create Logical Device (with 1 queue) 173 | { 174 | int device_extension_count = 1; 175 | const char* device_extensions[] = { "VK_KHR_swapchain" }; 176 | const float queue_priority[] = { 1.0f }; 177 | VkDeviceQueueCreateInfo queue_info[1] = {}; 178 | queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; 179 | queue_info[0].queueFamilyIndex = g_QueueFamily; 180 | queue_info[0].queueCount = 1; 181 | queue_info[0].pQueuePriorities = queue_priority; 182 | VkDeviceCreateInfo create_info = {}; 183 | create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 184 | create_info.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); 185 | create_info.pQueueCreateInfos = queue_info; 186 | create_info.enabledExtensionCount = device_extension_count; 187 | create_info.ppEnabledExtensionNames = device_extensions; 188 | err = vkCreateDevice(g_PhysicalDevice, &create_info, g_Allocator, &g_Device); 189 | check_vk_result(err); 190 | vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue); 191 | } 192 | 193 | // Create Descriptor Pool 194 | { 195 | VkDescriptorPoolSize pool_sizes[] = 196 | { 197 | { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, 198 | { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, 199 | { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, 200 | { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, 201 | { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, 202 | { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, 203 | { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, 204 | { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, 205 | { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, 206 | { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, 207 | { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } 208 | }; 209 | VkDescriptorPoolCreateInfo pool_info = {}; 210 | pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; 211 | pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; 212 | pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); 213 | pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); 214 | pool_info.pPoolSizes = pool_sizes; 215 | err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); 216 | check_vk_result(err); 217 | } 218 | } 219 | 220 | // All the ImGui_ImplVulkanH_XXX structures/functions are optional helpers used by the demo. 221 | // Your real engine/app may not use them. 222 | static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface, int width, int height) 223 | { 224 | wd->Surface = surface; 225 | 226 | // Check for WSI support 227 | VkBool32 res; 228 | vkGetPhysicalDeviceSurfaceSupportKHR(g_PhysicalDevice, g_QueueFamily, wd->Surface, &res); 229 | if (res != VK_TRUE) 230 | { 231 | fprintf(stderr, "Error no WSI support on physical device 0\n"); 232 | exit(-1); 233 | } 234 | 235 | // Select Surface Format 236 | const VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }; 237 | const VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; 238 | wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(g_PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); 239 | 240 | // Select Present Mode 241 | #ifdef IMGUI_UNLIMITED_FRAME_RATE 242 | VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR }; 243 | #else 244 | VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_FIFO_KHR }; 245 | #endif 246 | wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(g_PhysicalDevice, wd->Surface, &present_modes[0], IM_ARRAYSIZE(present_modes)); 247 | //printf("[vulkan] Selected PresentMode = %d\n", wd->PresentMode); 248 | 249 | // Create SwapChain, RenderPass, Framebuffer, etc. 250 | IM_ASSERT(g_MinImageCount >= 2); 251 | ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount); 252 | } 253 | 254 | static void CleanupVulkan() 255 | { 256 | vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); 257 | 258 | #ifdef IMGUI_VULKAN_DEBUG_REPORT 259 | // Remove the debug report callback 260 | auto vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT"); 261 | vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator); 262 | #endif // IMGUI_VULKAN_DEBUG_REPORT 263 | 264 | vkDestroyDevice(g_Device, g_Allocator); 265 | vkDestroyInstance(g_Instance, g_Allocator); 266 | } 267 | 268 | static void CleanupVulkanWindow() 269 | { 270 | ImGui_ImplVulkanH_DestroyWindow(g_Instance, g_Device, &g_MainWindowData, g_Allocator); 271 | } 272 | 273 | static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) 274 | { 275 | VkResult err; 276 | 277 | VkSemaphore image_acquired_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore; 278 | VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; 279 | err = vkAcquireNextImageKHR(g_Device, wd->Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE, &wd->FrameIndex); 280 | if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) 281 | { 282 | g_SwapChainRebuild = true; 283 | return; 284 | } 285 | check_vk_result(err); 286 | 287 | s_CurrentFrameIndex = (s_CurrentFrameIndex + 1) % g_MainWindowData.ImageCount; 288 | 289 | ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex]; 290 | { 291 | err = vkWaitForFences(g_Device, 1, &fd->Fence, VK_TRUE, UINT64_MAX); // wait indefinitely instead of periodically checking 292 | check_vk_result(err); 293 | 294 | err = vkResetFences(g_Device, 1, &fd->Fence); 295 | check_vk_result(err); 296 | } 297 | 298 | { 299 | // Free resources in queue 300 | for (auto& func : s_ResourceFreeQueue[s_CurrentFrameIndex]) 301 | func(); 302 | s_ResourceFreeQueue[s_CurrentFrameIndex].clear(); 303 | } 304 | { 305 | // Free command buffers allocated by Application::GetCommandBuffer 306 | // These use g_MainWindowData.FrameIndex and not s_CurrentFrameIndex because they're tied to the swapchain image index 307 | auto& allocatedCommandBuffers = s_AllocatedCommandBuffers[wd->FrameIndex]; 308 | if (allocatedCommandBuffers.size() > 0) 309 | { 310 | vkFreeCommandBuffers(g_Device, fd->CommandPool, (uint32_t)allocatedCommandBuffers.size(), allocatedCommandBuffers.data()); 311 | allocatedCommandBuffers.clear(); 312 | } 313 | 314 | err = vkResetCommandPool(g_Device, fd->CommandPool, 0); 315 | check_vk_result(err); 316 | VkCommandBufferBeginInfo info = {}; 317 | info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 318 | info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 319 | err = vkBeginCommandBuffer(fd->CommandBuffer, &info); 320 | check_vk_result(err); 321 | } 322 | { 323 | VkRenderPassBeginInfo info = {}; 324 | info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; 325 | info.renderPass = wd->RenderPass; 326 | info.framebuffer = fd->Framebuffer; 327 | info.renderArea.extent.width = wd->Width; 328 | info.renderArea.extent.height = wd->Height; 329 | info.clearValueCount = 1; 330 | info.pClearValues = &wd->ClearValue; 331 | vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); 332 | } 333 | 334 | // Record dear imgui primitives into command buffer 335 | ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer); 336 | 337 | // Submit command buffer 338 | vkCmdEndRenderPass(fd->CommandBuffer); 339 | { 340 | VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 341 | VkSubmitInfo info = {}; 342 | info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 343 | info.waitSemaphoreCount = 1; 344 | info.pWaitSemaphores = &image_acquired_semaphore; 345 | info.pWaitDstStageMask = &wait_stage; 346 | info.commandBufferCount = 1; 347 | info.pCommandBuffers = &fd->CommandBuffer; 348 | info.signalSemaphoreCount = 1; 349 | info.pSignalSemaphores = &render_complete_semaphore; 350 | 351 | err = vkEndCommandBuffer(fd->CommandBuffer); 352 | check_vk_result(err); 353 | err = vkQueueSubmit(g_Queue, 1, &info, fd->Fence); 354 | check_vk_result(err); 355 | } 356 | } 357 | 358 | static void FramePresent(ImGui_ImplVulkanH_Window* wd) 359 | { 360 | if (g_SwapChainRebuild) 361 | return; 362 | VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; 363 | VkPresentInfoKHR info = {}; 364 | info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; 365 | info.waitSemaphoreCount = 1; 366 | info.pWaitSemaphores = &render_complete_semaphore; 367 | info.swapchainCount = 1; 368 | info.pSwapchains = &wd->Swapchain; 369 | info.pImageIndices = &wd->FrameIndex; 370 | VkResult err = vkQueuePresentKHR(g_Queue, &info); 371 | if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) 372 | { 373 | g_SwapChainRebuild = true; 374 | return; 375 | } 376 | check_vk_result(err); 377 | wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores 378 | } 379 | 380 | static void glfw_error_callback(int error, const char* description) 381 | { 382 | fprintf(stderr, "Glfw Error %d: %s\n", error, description); 383 | } 384 | 385 | namespace Walnut { 386 | 387 | Application::Application(const ApplicationSpecification& specification) 388 | : m_Specification(specification) 389 | { 390 | s_Instance = this; 391 | 392 | Init(); 393 | } 394 | 395 | Application::~Application() 396 | { 397 | Shutdown(); 398 | 399 | s_Instance = nullptr; 400 | } 401 | 402 | Application& Application::Get() 403 | { 404 | return *s_Instance; 405 | } 406 | 407 | void Application::Init() 408 | { 409 | // Setup GLFW window 410 | glfwSetErrorCallback(glfw_error_callback); 411 | if (!glfwInit()) 412 | { 413 | std::cerr << "Could not initalize GLFW!\n"; 414 | return; 415 | } 416 | 417 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 418 | m_WindowHandle = glfwCreateWindow(m_Specification.Width, m_Specification.Height, m_Specification.Name.c_str(), NULL, NULL); 419 | 420 | // Setup Vulkan 421 | if (!glfwVulkanSupported()) 422 | { 423 | std::cerr << "GLFW: Vulkan not supported!\n"; 424 | return; 425 | } 426 | uint32_t extensions_count = 0; 427 | const char** extensions = glfwGetRequiredInstanceExtensions(&extensions_count); 428 | SetupVulkan(extensions, extensions_count); 429 | 430 | // Create Window Surface 431 | VkSurfaceKHR surface; 432 | VkResult err = glfwCreateWindowSurface(g_Instance, m_WindowHandle, g_Allocator, &surface); 433 | check_vk_result(err); 434 | 435 | // Create Framebuffers 436 | int w, h; 437 | glfwGetFramebufferSize(m_WindowHandle, &w, &h); 438 | ImGui_ImplVulkanH_Window* wd = &g_MainWindowData; 439 | SetupVulkanWindow(wd, surface, w, h); 440 | 441 | s_AllocatedCommandBuffers.resize(wd->ImageCount); 442 | s_ResourceFreeQueue.resize(wd->ImageCount); 443 | 444 | // Setup Dear ImGui context 445 | IMGUI_CHECKVERSION(); 446 | ImGui::CreateContext(); 447 | ImGuiIO& io = ImGui::GetIO(); (void)io; 448 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 449 | //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 450 | io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking 451 | io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows 452 | //io.ConfigViewportsNoAutoMerge = true; 453 | //io.ConfigViewportsNoTaskBarIcon = true; 454 | 455 | // Setup Dear ImGui style 456 | ImGui::StyleColorsDark(); 457 | //ImGui::StyleColorsClassic(); 458 | 459 | // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. 460 | ImGuiStyle& style = ImGui::GetStyle(); 461 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) 462 | { 463 | style.WindowRounding = 0.0f; 464 | style.Colors[ImGuiCol_WindowBg].w = 1.0f; 465 | } 466 | 467 | // Setup Platform/Renderer backends 468 | ImGui_ImplGlfw_InitForVulkan(m_WindowHandle, true); 469 | ImGui_ImplVulkan_InitInfo init_info = {}; 470 | init_info.Instance = g_Instance; 471 | init_info.PhysicalDevice = g_PhysicalDevice; 472 | init_info.Device = g_Device; 473 | init_info.QueueFamily = g_QueueFamily; 474 | init_info.Queue = g_Queue; 475 | init_info.PipelineCache = g_PipelineCache; 476 | init_info.DescriptorPool = g_DescriptorPool; 477 | init_info.Subpass = 0; 478 | init_info.MinImageCount = g_MinImageCount; 479 | init_info.ImageCount = wd->ImageCount; 480 | init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; 481 | init_info.Allocator = g_Allocator; 482 | init_info.CheckVkResultFn = check_vk_result; 483 | ImGui_ImplVulkan_Init(&init_info, wd->RenderPass); 484 | 485 | // Load default font 486 | ImFontConfig fontConfig; 487 | fontConfig.FontDataOwnedByAtlas = false; 488 | ImFont* robotoFont = io.Fonts->AddFontFromMemoryTTF((void*)g_RobotoRegular, sizeof(g_RobotoRegular), 20.0f, &fontConfig); 489 | io.FontDefault = robotoFont; 490 | 491 | // Upload Fonts 492 | { 493 | // Use any command queue 494 | VkCommandPool command_pool = wd->Frames[wd->FrameIndex].CommandPool; 495 | VkCommandBuffer command_buffer = wd->Frames[wd->FrameIndex].CommandBuffer; 496 | 497 | err = vkResetCommandPool(g_Device, command_pool, 0); 498 | check_vk_result(err); 499 | VkCommandBufferBeginInfo begin_info = {}; 500 | begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 501 | begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 502 | err = vkBeginCommandBuffer(command_buffer, &begin_info); 503 | check_vk_result(err); 504 | 505 | ImGui_ImplVulkan_CreateFontsTexture(command_buffer); 506 | 507 | VkSubmitInfo end_info = {}; 508 | end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 509 | end_info.commandBufferCount = 1; 510 | end_info.pCommandBuffers = &command_buffer; 511 | err = vkEndCommandBuffer(command_buffer); 512 | check_vk_result(err); 513 | err = vkQueueSubmit(g_Queue, 1, &end_info, VK_NULL_HANDLE); 514 | check_vk_result(err); 515 | 516 | err = vkDeviceWaitIdle(g_Device); 517 | check_vk_result(err); 518 | ImGui_ImplVulkan_DestroyFontUploadObjects(); 519 | } 520 | } 521 | 522 | void Application::Shutdown() 523 | { 524 | for (auto& layer : m_LayerStack) 525 | layer->OnDetach(); 526 | 527 | m_LayerStack.clear(); 528 | 529 | // Cleanup 530 | VkResult err = vkDeviceWaitIdle(g_Device); 531 | check_vk_result(err); 532 | 533 | // Free resources in queue 534 | for (auto& queue : s_ResourceFreeQueue) 535 | { 536 | for (auto& func : queue) 537 | func(); 538 | } 539 | s_ResourceFreeQueue.clear(); 540 | 541 | ImGui_ImplVulkan_Shutdown(); 542 | ImGui_ImplGlfw_Shutdown(); 543 | ImGui::DestroyContext(); 544 | 545 | CleanupVulkanWindow(); 546 | CleanupVulkan(); 547 | 548 | glfwDestroyWindow(m_WindowHandle); 549 | glfwTerminate(); 550 | 551 | g_ApplicationRunning = false; 552 | } 553 | 554 | void Application::Run() 555 | { 556 | m_Running = true; 557 | 558 | ImGui_ImplVulkanH_Window* wd = &g_MainWindowData; 559 | ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); 560 | ImGuiIO& io = ImGui::GetIO(); 561 | 562 | // Main loop 563 | while (!glfwWindowShouldClose(m_WindowHandle) && m_Running) 564 | { 565 | // Poll and handle events (inputs, window resize, etc.) 566 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. 567 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. 568 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. 569 | // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. 570 | glfwPollEvents(); 571 | 572 | for (auto& layer : m_LayerStack) 573 | layer->OnUpdate(m_TimeStep); 574 | 575 | // Resize swap chain? 576 | if (g_SwapChainRebuild) 577 | { 578 | int width, height; 579 | glfwGetFramebufferSize(m_WindowHandle, &width, &height); 580 | if (width > 0 && height > 0) 581 | { 582 | ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); 583 | ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, &g_MainWindowData, g_QueueFamily, g_Allocator, width, height, g_MinImageCount); 584 | g_MainWindowData.FrameIndex = 0; 585 | 586 | // Clear allocated command buffers from here since entire pool is destroyed 587 | s_AllocatedCommandBuffers.clear(); 588 | s_AllocatedCommandBuffers.resize(g_MainWindowData.ImageCount); 589 | 590 | g_SwapChainRebuild = false; 591 | } 592 | } 593 | 594 | // Start the Dear ImGui frame 595 | ImGui_ImplVulkan_NewFrame(); 596 | ImGui_ImplGlfw_NewFrame(); 597 | ImGui::NewFrame(); 598 | 599 | { 600 | static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; 601 | 602 | // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, 603 | // because it would be confusing to have two docking targets within each others. 604 | ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking; 605 | if (m_MenubarCallback) 606 | window_flags |= ImGuiWindowFlags_MenuBar; 607 | 608 | const ImGuiViewport* viewport = ImGui::GetMainViewport(); 609 | ImGui::SetNextWindowPos(viewport->WorkPos); 610 | ImGui::SetNextWindowSize(viewport->WorkSize); 611 | ImGui::SetNextWindowViewport(viewport->ID); 612 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 613 | ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); 614 | window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; 615 | window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; 616 | 617 | // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background 618 | // and handle the pass-thru hole, so we ask Begin() to not render a background. 619 | if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) 620 | window_flags |= ImGuiWindowFlags_NoBackground; 621 | 622 | // Important: note that we proceed even if Begin() returns false (aka window is collapsed). 623 | // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, 624 | // all active windows docked into it will lose their parent and become undocked. 625 | // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise 626 | // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. 627 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); 628 | ImGui::Begin("DockSpace Demo", nullptr, window_flags); 629 | ImGui::PopStyleVar(); 630 | 631 | ImGui::PopStyleVar(2); 632 | 633 | // Submit the DockSpace 634 | ImGuiIO& io = ImGui::GetIO(); 635 | if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) 636 | { 637 | ImGuiID dockspace_id = ImGui::GetID("VulkanAppDockspace"); 638 | ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); 639 | } 640 | 641 | if (m_MenubarCallback) 642 | { 643 | if (ImGui::BeginMenuBar()) 644 | { 645 | m_MenubarCallback(); 646 | ImGui::EndMenuBar(); 647 | } 648 | } 649 | 650 | for (auto& layer : m_LayerStack) 651 | layer->OnUIRender(); 652 | 653 | ImGui::End(); 654 | } 655 | 656 | // Rendering 657 | ImGui::Render(); 658 | ImDrawData* main_draw_data = ImGui::GetDrawData(); 659 | const bool main_is_minimized = (main_draw_data->DisplaySize.x <= 0.0f || main_draw_data->DisplaySize.y <= 0.0f); 660 | wd->ClearValue.color.float32[0] = clear_color.x * clear_color.w; 661 | wd->ClearValue.color.float32[1] = clear_color.y * clear_color.w; 662 | wd->ClearValue.color.float32[2] = clear_color.z * clear_color.w; 663 | wd->ClearValue.color.float32[3] = clear_color.w; 664 | if (!main_is_minimized) 665 | FrameRender(wd, main_draw_data); 666 | 667 | // Update and Render additional Platform Windows 668 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) 669 | { 670 | ImGui::UpdatePlatformWindows(); 671 | ImGui::RenderPlatformWindowsDefault(); 672 | } 673 | 674 | // Present Main Platform Window 675 | if (!main_is_minimized) 676 | FramePresent(wd); 677 | 678 | float time = GetTime(); 679 | m_FrameTime = time - m_LastFrameTime; 680 | m_TimeStep = glm::min(m_FrameTime, 0.0333f); 681 | m_LastFrameTime = time; 682 | } 683 | 684 | } 685 | 686 | void Application::Close() 687 | { 688 | m_Running = false; 689 | } 690 | 691 | float Application::GetTime() 692 | { 693 | return (float)glfwGetTime(); 694 | } 695 | 696 | VkInstance Application::GetInstance() 697 | { 698 | return g_Instance; 699 | } 700 | 701 | VkPhysicalDevice Application::GetPhysicalDevice() 702 | { 703 | return g_PhysicalDevice; 704 | } 705 | 706 | VkDevice Application::GetDevice() 707 | { 708 | return g_Device; 709 | } 710 | 711 | VkCommandBuffer Application::GetCommandBuffer(bool begin) 712 | { 713 | ImGui_ImplVulkanH_Window* wd = &g_MainWindowData; 714 | 715 | // Use any command queue 716 | VkCommandPool command_pool = wd->Frames[wd->FrameIndex].CommandPool; 717 | 718 | VkCommandBufferAllocateInfo cmdBufAllocateInfo = {}; 719 | cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 720 | cmdBufAllocateInfo.commandPool = command_pool; 721 | cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 722 | cmdBufAllocateInfo.commandBufferCount = 1; 723 | 724 | VkCommandBuffer& command_buffer = s_AllocatedCommandBuffers[wd->FrameIndex].emplace_back(); 725 | auto err = vkAllocateCommandBuffers(g_Device, &cmdBufAllocateInfo, &command_buffer); 726 | 727 | VkCommandBufferBeginInfo begin_info = {}; 728 | begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 729 | begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 730 | err = vkBeginCommandBuffer(command_buffer, &begin_info); 731 | check_vk_result(err); 732 | 733 | return command_buffer; 734 | } 735 | 736 | void Application::FlushCommandBuffer(VkCommandBuffer commandBuffer) 737 | { 738 | const uint64_t DEFAULT_FENCE_TIMEOUT = 100000000000; 739 | 740 | VkSubmitInfo end_info = {}; 741 | end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 742 | end_info.commandBufferCount = 1; 743 | end_info.pCommandBuffers = &commandBuffer; 744 | auto err = vkEndCommandBuffer(commandBuffer); 745 | check_vk_result(err); 746 | 747 | // Create fence to ensure that the command buffer has finished executing 748 | VkFenceCreateInfo fenceCreateInfo = {}; 749 | fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; 750 | fenceCreateInfo.flags = 0; 751 | VkFence fence; 752 | err = vkCreateFence(g_Device, &fenceCreateInfo, nullptr, &fence); 753 | check_vk_result(err); 754 | 755 | err = vkQueueSubmit(g_Queue, 1, &end_info, fence); 756 | check_vk_result(err); 757 | 758 | err = vkWaitForFences(g_Device, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT); 759 | check_vk_result(err); 760 | 761 | vkDestroyFence(g_Device, fence, nullptr); 762 | } 763 | 764 | 765 | void Application::SubmitResourceFree(std::function&& func) 766 | { 767 | s_ResourceFreeQueue[s_CurrentFrameIndex].emplace_back(func); 768 | } 769 | 770 | } 771 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/Application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Layer.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "imgui.h" 11 | #include "vulkan/vulkan.h" 12 | 13 | void check_vk_result(VkResult err); 14 | 15 | struct GLFWwindow; 16 | 17 | namespace Walnut { 18 | 19 | struct ApplicationSpecification 20 | { 21 | std::string Name = "Walnut App"; 22 | uint32_t Width = 1600; 23 | uint32_t Height = 900; 24 | }; 25 | 26 | class Application 27 | { 28 | public: 29 | Application(const ApplicationSpecification& applicationSpecification = ApplicationSpecification()); 30 | ~Application(); 31 | 32 | static Application& Get(); 33 | 34 | void Run(); 35 | void SetMenubarCallback(const std::function& menubarCallback) { m_MenubarCallback = menubarCallback; } 36 | 37 | template 38 | void PushLayer() 39 | { 40 | static_assert(std::is_base_of::value, "Pushed type is not subclass of Layer!"); 41 | m_LayerStack.emplace_back(std::make_shared())->OnAttach(); 42 | } 43 | 44 | void PushLayer(const std::shared_ptr& layer) { m_LayerStack.emplace_back(layer); layer->OnAttach(); } 45 | 46 | void Close(); 47 | 48 | float GetTime(); 49 | GLFWwindow* GetWindowHandle() const { return m_WindowHandle; } 50 | 51 | static VkInstance GetInstance(); 52 | static VkPhysicalDevice GetPhysicalDevice(); 53 | static VkDevice GetDevice(); 54 | 55 | static VkCommandBuffer GetCommandBuffer(bool begin); 56 | static void FlushCommandBuffer(VkCommandBuffer commandBuffer); 57 | 58 | static void SubmitResourceFree(std::function&& func); 59 | private: 60 | void Init(); 61 | void Shutdown(); 62 | private: 63 | ApplicationSpecification m_Specification; 64 | GLFWwindow* m_WindowHandle = nullptr; 65 | bool m_Running = false; 66 | 67 | float m_TimeStep = 0.0f; 68 | float m_FrameTime = 0.0f; 69 | float m_LastFrameTime = 0.0f; 70 | 71 | std::vector> m_LayerStack; 72 | std::function m_MenubarCallback; 73 | }; 74 | 75 | // Implemented by CLIENT 76 | Application* CreateApplication(int argc, char** argv); 77 | } -------------------------------------------------------------------------------- /Walnut/src/Walnut/EntryPoint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef WL_PLATFORM_WINDOWS 4 | 5 | extern Walnut::Application* Walnut::CreateApplication(int argc, char** argv); 6 | bool g_ApplicationRunning = true; 7 | 8 | namespace Walnut { 9 | 10 | int Main(int argc, char** argv) 11 | { 12 | while (g_ApplicationRunning) 13 | { 14 | Walnut::Application* app = Walnut::CreateApplication(argc, argv); 15 | app->Run(); 16 | delete app; 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | } 23 | 24 | #ifdef WL_DIST 25 | 26 | #include 27 | 28 | int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) 29 | { 30 | return Walnut::Main(__argc, __argv); 31 | } 32 | 33 | #else 34 | 35 | int main(int argc, char** argv) 36 | { 37 | return Walnut::Main(argc, argv); 38 | } 39 | 40 | #endif // WL_DIST 41 | 42 | #endif // WL_PLATFORM_WINDOWS 43 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/ImGui/ImGuiBuild.cpp: -------------------------------------------------------------------------------- 1 | #include "backends/imgui_impl_vulkan.cpp" 2 | #include "backends/imgui_impl_glfw.cpp" -------------------------------------------------------------------------------- /Walnut/src/Walnut/Image.cpp: -------------------------------------------------------------------------------- 1 | #include "Image.h" 2 | 3 | #include "imgui.h" 4 | #include "backends/imgui_impl_vulkan.h" 5 | 6 | #include "Application.h" 7 | 8 | #define STB_IMAGE_IMPLEMENTATION 9 | #include "stb_image.h" 10 | 11 | namespace Walnut { 12 | 13 | namespace Utils { 14 | 15 | static uint32_t GetVulkanMemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) 16 | { 17 | VkPhysicalDeviceMemoryProperties prop; 18 | vkGetPhysicalDeviceMemoryProperties(Application::GetPhysicalDevice(), &prop); 19 | for (uint32_t i = 0; i < prop.memoryTypeCount; i++) 20 | { 21 | if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i)) 22 | return i; 23 | } 24 | 25 | return 0xffffffff; 26 | } 27 | 28 | static uint32_t BytesPerPixel(ImageFormat format) 29 | { 30 | switch (format) 31 | { 32 | case ImageFormat::RGBA: return 4; 33 | case ImageFormat::RGBA32F: return 16; 34 | } 35 | return 0; 36 | } 37 | 38 | static VkFormat WalnutFormatToVulkanFormat(ImageFormat format) 39 | { 40 | switch (format) 41 | { 42 | case ImageFormat::RGBA: return VK_FORMAT_R8G8B8A8_UNORM; 43 | case ImageFormat::RGBA32F: return VK_FORMAT_R32G32B32A32_SFLOAT; 44 | } 45 | return (VkFormat)0; 46 | } 47 | 48 | } 49 | 50 | Image::Image(std::string_view path) 51 | : m_Filepath(path) 52 | { 53 | int width, height, channels; 54 | uint8_t* data = nullptr; 55 | 56 | if (stbi_is_hdr(m_Filepath.c_str())) 57 | { 58 | data = (uint8_t*)stbi_loadf(m_Filepath.c_str(), &width, &height, &channels, 4); 59 | m_Format = ImageFormat::RGBA32F; 60 | } 61 | else 62 | { 63 | data = stbi_load(m_Filepath.c_str(), &width, &height, &channels, 4); 64 | m_Format = ImageFormat::RGBA; 65 | } 66 | 67 | m_Width = width; 68 | m_Height = height; 69 | 70 | AllocateMemory(m_Width * m_Height * Utils::BytesPerPixel(m_Format)); 71 | SetData(data); 72 | stbi_image_free(data); 73 | } 74 | 75 | Image::Image(uint32_t width, uint32_t height, ImageFormat format, const void* data) 76 | : m_Width(width), m_Height(height), m_Format(format) 77 | { 78 | AllocateMemory(m_Width * m_Height * Utils::BytesPerPixel(m_Format)); 79 | if (data) 80 | SetData(data); 81 | } 82 | 83 | Image::~Image() 84 | { 85 | Release(); 86 | } 87 | 88 | void Image::AllocateMemory(uint64_t size) 89 | { 90 | VkDevice device = Application::GetDevice(); 91 | 92 | VkResult err; 93 | 94 | VkFormat vulkanFormat = Utils::WalnutFormatToVulkanFormat(m_Format); 95 | 96 | // Create the Image 97 | { 98 | VkImageCreateInfo info = {}; 99 | info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 100 | info.imageType = VK_IMAGE_TYPE_2D; 101 | info.format = vulkanFormat; 102 | info.extent.width = m_Width; 103 | info.extent.height = m_Height; 104 | info.extent.depth = 1; 105 | info.mipLevels = 1; 106 | info.arrayLayers = 1; 107 | info.samples = VK_SAMPLE_COUNT_1_BIT; 108 | info.tiling = VK_IMAGE_TILING_OPTIMAL; 109 | info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; 110 | info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 111 | info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 112 | err = vkCreateImage(device, &info, nullptr, &m_Image); 113 | check_vk_result(err); 114 | VkMemoryRequirements req; 115 | vkGetImageMemoryRequirements(device, m_Image, &req); 116 | VkMemoryAllocateInfo alloc_info = {}; 117 | alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 118 | alloc_info.allocationSize = req.size; 119 | alloc_info.memoryTypeIndex = Utils::GetVulkanMemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); 120 | err = vkAllocateMemory(device, &alloc_info, nullptr, &m_Memory); 121 | check_vk_result(err); 122 | err = vkBindImageMemory(device, m_Image, m_Memory, 0); 123 | check_vk_result(err); 124 | } 125 | 126 | // Create the Image View: 127 | { 128 | VkImageViewCreateInfo info = {}; 129 | info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 130 | info.image = m_Image; 131 | info.viewType = VK_IMAGE_VIEW_TYPE_2D; 132 | info.format = vulkanFormat; 133 | info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 134 | info.subresourceRange.levelCount = 1; 135 | info.subresourceRange.layerCount = 1; 136 | err = vkCreateImageView(device, &info, nullptr, &m_ImageView); 137 | check_vk_result(err); 138 | } 139 | 140 | // Create sampler: 141 | { 142 | VkSamplerCreateInfo info = {}; 143 | info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; 144 | info.magFilter = VK_FILTER_LINEAR; 145 | info.minFilter = VK_FILTER_LINEAR; 146 | info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; 147 | info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; 148 | info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; 149 | info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; 150 | info.minLod = -1000; 151 | info.maxLod = 1000; 152 | info.maxAnisotropy = 1.0f; 153 | VkResult err = vkCreateSampler(device, &info, nullptr, &m_Sampler); 154 | check_vk_result(err); 155 | } 156 | 157 | // Create the Descriptor Set: 158 | m_DescriptorSet = (VkDescriptorSet)ImGui_ImplVulkan_AddTexture(m_Sampler, m_ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); 159 | } 160 | 161 | void Image::Release() 162 | { 163 | Application::SubmitResourceFree([sampler = m_Sampler, imageView = m_ImageView, image = m_Image, 164 | memory = m_Memory, stagingBuffer = m_StagingBuffer, stagingBufferMemory = m_StagingBufferMemory]() 165 | { 166 | VkDevice device = Application::GetDevice(); 167 | 168 | vkDestroySampler(device, sampler, nullptr); 169 | vkDestroyImageView(device, imageView, nullptr); 170 | vkDestroyImage(device, image, nullptr); 171 | vkFreeMemory(device, memory, nullptr); 172 | vkDestroyBuffer(device, stagingBuffer, nullptr); 173 | vkFreeMemory(device, stagingBufferMemory, nullptr); 174 | }); 175 | 176 | m_Sampler = nullptr; 177 | m_ImageView = nullptr; 178 | m_Image = nullptr; 179 | m_Memory = nullptr; 180 | m_StagingBuffer = nullptr; 181 | m_StagingBufferMemory = nullptr; 182 | } 183 | 184 | void Image::SetData(const void* data) 185 | { 186 | VkDevice device = Application::GetDevice(); 187 | 188 | size_t upload_size = m_Width * m_Height * Utils::BytesPerPixel(m_Format); 189 | 190 | VkResult err; 191 | 192 | if (!m_StagingBuffer) 193 | { 194 | // Create the Upload Buffer 195 | { 196 | VkBufferCreateInfo buffer_info = {}; 197 | buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 198 | buffer_info.size = upload_size; 199 | buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; 200 | buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 201 | err = vkCreateBuffer(device, &buffer_info, nullptr, &m_StagingBuffer); 202 | check_vk_result(err); 203 | VkMemoryRequirements req; 204 | vkGetBufferMemoryRequirements(device, m_StagingBuffer, &req); 205 | m_AlignedSize = req.size; 206 | VkMemoryAllocateInfo alloc_info = {}; 207 | alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 208 | alloc_info.allocationSize = req.size; 209 | alloc_info.memoryTypeIndex = Utils::GetVulkanMemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); 210 | err = vkAllocateMemory(device, &alloc_info, nullptr, &m_StagingBufferMemory); 211 | check_vk_result(err); 212 | err = vkBindBufferMemory(device, m_StagingBuffer, m_StagingBufferMemory, 0); 213 | check_vk_result(err); 214 | } 215 | 216 | } 217 | 218 | // Upload to Buffer 219 | { 220 | char* map = NULL; 221 | err = vkMapMemory(device, m_StagingBufferMemory, 0, m_AlignedSize, 0, (void**)(&map)); 222 | check_vk_result(err); 223 | memcpy(map, data, upload_size); 224 | VkMappedMemoryRange range[1] = {}; 225 | range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; 226 | range[0].memory = m_StagingBufferMemory; 227 | range[0].size = m_AlignedSize; 228 | err = vkFlushMappedMemoryRanges(device, 1, range); 229 | check_vk_result(err); 230 | vkUnmapMemory(device, m_StagingBufferMemory); 231 | } 232 | 233 | 234 | // Copy to Image 235 | { 236 | VkCommandBuffer command_buffer = Application::GetCommandBuffer(true); 237 | 238 | VkImageMemoryBarrier copy_barrier = {}; 239 | copy_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 240 | copy_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 241 | copy_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; 242 | copy_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; 243 | copy_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 244 | copy_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 245 | copy_barrier.image = m_Image; 246 | copy_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 247 | copy_barrier.subresourceRange.levelCount = 1; 248 | copy_barrier.subresourceRange.layerCount = 1; 249 | vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, ©_barrier); 250 | 251 | VkBufferImageCopy region = {}; 252 | region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 253 | region.imageSubresource.layerCount = 1; 254 | region.imageExtent.width = m_Width; 255 | region.imageExtent.height = m_Height; 256 | region.imageExtent.depth = 1; 257 | vkCmdCopyBufferToImage(command_buffer, m_StagingBuffer, m_Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); 258 | 259 | VkImageMemoryBarrier use_barrier = {}; 260 | use_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 261 | use_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 262 | use_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; 263 | use_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; 264 | use_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 265 | use_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 266 | use_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 267 | use_barrier.image = m_Image; 268 | use_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 269 | use_barrier.subresourceRange.levelCount = 1; 270 | use_barrier.subresourceRange.layerCount = 1; 271 | vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &use_barrier); 272 | 273 | Application::FlushCommandBuffer(command_buffer); 274 | } 275 | } 276 | 277 | void Image::Resize(uint32_t width, uint32_t height) 278 | { 279 | if (m_Image && m_Width == width && m_Height == height) 280 | return; 281 | 282 | // TODO: max size? 283 | 284 | m_Width = width; 285 | m_Height = height; 286 | 287 | Release(); 288 | AllocateMemory(m_Width * m_Height * Utils::BytesPerPixel(m_Format)); 289 | } 290 | 291 | } 292 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/Image.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "vulkan/vulkan.h" 6 | 7 | namespace Walnut { 8 | 9 | enum class ImageFormat 10 | { 11 | None = 0, 12 | RGBA, 13 | RGBA32F 14 | }; 15 | 16 | class Image 17 | { 18 | public: 19 | Image(std::string_view path); 20 | Image(uint32_t width, uint32_t height, ImageFormat format, const void* data = nullptr); 21 | ~Image(); 22 | 23 | void SetData(const void* data); 24 | 25 | VkDescriptorSet GetDescriptorSet() const { return m_DescriptorSet; } 26 | 27 | void Resize(uint32_t width, uint32_t height); 28 | 29 | uint32_t GetWidth() const { return m_Width; } 30 | uint32_t GetHeight() const { return m_Height; } 31 | private: 32 | void AllocateMemory(uint64_t size); 33 | void Release(); 34 | private: 35 | uint32_t m_Width = 0, m_Height = 0; 36 | 37 | VkImage m_Image = nullptr; 38 | VkImageView m_ImageView = nullptr; 39 | VkDeviceMemory m_Memory = nullptr; 40 | VkSampler m_Sampler = nullptr; 41 | 42 | ImageFormat m_Format = ImageFormat::None; 43 | 44 | VkBuffer m_StagingBuffer = nullptr; 45 | VkDeviceMemory m_StagingBufferMemory = nullptr; 46 | 47 | size_t m_AlignedSize = 0; 48 | 49 | VkDescriptorSet m_DescriptorSet = nullptr; 50 | 51 | std::string m_Filepath; 52 | }; 53 | 54 | } 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/Input/Input.cpp: -------------------------------------------------------------------------------- 1 | #include "Input.h" 2 | 3 | #include "Walnut/Application.h" 4 | 5 | #include 6 | 7 | namespace Walnut { 8 | 9 | bool Input::IsKeyDown(KeyCode keycode) 10 | { 11 | GLFWwindow* windowHandle = Application::Get().GetWindowHandle(); 12 | int state = glfwGetKey(windowHandle, (int)keycode); 13 | return state == GLFW_PRESS || state == GLFW_REPEAT; 14 | } 15 | 16 | bool Input::IsMouseButtonDown(MouseButton button) 17 | { 18 | GLFWwindow* windowHandle = Application::Get().GetWindowHandle(); 19 | int state = glfwGetMouseButton(windowHandle, (int)button); 20 | return state == GLFW_PRESS; 21 | } 22 | 23 | glm::vec2 Input::GetMousePosition() 24 | { 25 | GLFWwindow* windowHandle = Application::Get().GetWindowHandle(); 26 | 27 | double x, y; 28 | glfwGetCursorPos(windowHandle, &x, &y); 29 | return { (float)x, (float)y }; 30 | } 31 | 32 | void Input::SetCursorMode(CursorMode mode) 33 | { 34 | GLFWwindow* windowHandle = Application::Get().GetWindowHandle(); 35 | glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_NORMAL + (int)mode); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /Walnut/src/Walnut/Input/Input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "KeyCodes.h" 4 | 5 | #include 6 | 7 | namespace Walnut { 8 | 9 | class Input 10 | { 11 | public: 12 | static bool IsKeyDown(KeyCode keycode); 13 | static bool IsMouseButtonDown(MouseButton button); 14 | 15 | static glm::vec2 GetMousePosition(); 16 | 17 | static void SetCursorMode(CursorMode mode); 18 | }; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/Input/KeyCodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Walnut { 7 | 8 | typedef enum class KeyCode : uint16_t 9 | { 10 | // From glfw3.h 11 | Space = 32, 12 | Apostrophe = 39, /* ' */ 13 | Comma = 44, /* , */ 14 | Minus = 45, /* - */ 15 | Period = 46, /* . */ 16 | Slash = 47, /* / */ 17 | 18 | D0 = 48, /* 0 */ 19 | D1 = 49, /* 1 */ 20 | D2 = 50, /* 2 */ 21 | D3 = 51, /* 3 */ 22 | D4 = 52, /* 4 */ 23 | D5 = 53, /* 5 */ 24 | D6 = 54, /* 6 */ 25 | D7 = 55, /* 7 */ 26 | D8 = 56, /* 8 */ 27 | D9 = 57, /* 9 */ 28 | 29 | Semicolon = 59, /* ; */ 30 | Equal = 61, /* = */ 31 | 32 | A = 65, 33 | B = 66, 34 | C = 67, 35 | D = 68, 36 | E = 69, 37 | F = 70, 38 | G = 71, 39 | H = 72, 40 | I = 73, 41 | J = 74, 42 | K = 75, 43 | L = 76, 44 | M = 77, 45 | N = 78, 46 | O = 79, 47 | P = 80, 48 | Q = 81, 49 | R = 82, 50 | S = 83, 51 | T = 84, 52 | U = 85, 53 | V = 86, 54 | W = 87, 55 | X = 88, 56 | Y = 89, 57 | Z = 90, 58 | 59 | LeftBracket = 91, /* [ */ 60 | Backslash = 92, /* \ */ 61 | RightBracket = 93, /* ] */ 62 | GraveAccent = 96, /* ` */ 63 | 64 | World1 = 161, /* non-US #1 */ 65 | World2 = 162, /* non-US #2 */ 66 | 67 | /* Function keys */ 68 | Escape = 256, 69 | Enter = 257, 70 | Tab = 258, 71 | Backspace = 259, 72 | Insert = 260, 73 | Delete = 261, 74 | Right = 262, 75 | Left = 263, 76 | Down = 264, 77 | Up = 265, 78 | PageUp = 266, 79 | PageDown = 267, 80 | Home = 268, 81 | End = 269, 82 | CapsLock = 280, 83 | ScrollLock = 281, 84 | NumLock = 282, 85 | PrintScreen = 283, 86 | Pause = 284, 87 | F1 = 290, 88 | F2 = 291, 89 | F3 = 292, 90 | F4 = 293, 91 | F5 = 294, 92 | F6 = 295, 93 | F7 = 296, 94 | F8 = 297, 95 | F9 = 298, 96 | F10 = 299, 97 | F11 = 300, 98 | F12 = 301, 99 | F13 = 302, 100 | F14 = 303, 101 | F15 = 304, 102 | F16 = 305, 103 | F17 = 306, 104 | F18 = 307, 105 | F19 = 308, 106 | F20 = 309, 107 | F21 = 310, 108 | F22 = 311, 109 | F23 = 312, 110 | F24 = 313, 111 | F25 = 314, 112 | 113 | /* Keypad */ 114 | KP0 = 320, 115 | KP1 = 321, 116 | KP2 = 322, 117 | KP3 = 323, 118 | KP4 = 324, 119 | KP5 = 325, 120 | KP6 = 326, 121 | KP7 = 327, 122 | KP8 = 328, 123 | KP9 = 329, 124 | KPDecimal = 330, 125 | KPDivide = 331, 126 | KPMultiply = 332, 127 | KPSubtract = 333, 128 | KPAdd = 334, 129 | KPEnter = 335, 130 | KPEqual = 336, 131 | 132 | LeftShift = 340, 133 | LeftControl = 341, 134 | LeftAlt = 342, 135 | LeftSuper = 343, 136 | RightShift = 344, 137 | RightControl = 345, 138 | RightAlt = 346, 139 | RightSuper = 347, 140 | Menu = 348 141 | } Key; 142 | 143 | enum class KeyState 144 | { 145 | None = -1, 146 | Pressed, 147 | Held, 148 | Released 149 | }; 150 | 151 | enum class CursorMode 152 | { 153 | Normal = 0, 154 | Hidden = 1, 155 | Locked = 2 156 | }; 157 | 158 | typedef enum class MouseButton : uint16_t 159 | { 160 | Button0 = 0, 161 | Button1 = 1, 162 | Button2 = 2, 163 | Button3 = 3, 164 | Button4 = 4, 165 | Button5 = 5, 166 | Left = Button0, 167 | Right = Button1, 168 | Middle = Button2 169 | } Button; 170 | 171 | 172 | inline std::ostream& operator<<(std::ostream& os, KeyCode keyCode) 173 | { 174 | os << static_cast(keyCode); 175 | return os; 176 | } 177 | 178 | inline std::ostream& operator<<(std::ostream& os, MouseButton button) 179 | { 180 | os << static_cast(button); 181 | return os; 182 | } 183 | } 184 | 185 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/Layer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Walnut { 4 | 5 | class Layer 6 | { 7 | public: 8 | virtual ~Layer() = default; 9 | 10 | virtual void OnAttach() {} 11 | virtual void OnDetach() {} 12 | 13 | virtual void OnUpdate(float ts) {} 14 | virtual void OnUIRender() {} 15 | }; 16 | 17 | } -------------------------------------------------------------------------------- /Walnut/src/Walnut/Random.cpp: -------------------------------------------------------------------------------- 1 | #include "Random.h" 2 | 3 | namespace Walnut { 4 | 5 | std::mt19937 Random::s_RandomEngine; 6 | std::uniform_int_distribution Random::s_Distribution; 7 | 8 | } -------------------------------------------------------------------------------- /Walnut/src/Walnut/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Walnut { 8 | 9 | class Random 10 | { 11 | public: 12 | static void Init() 13 | { 14 | s_RandomEngine.seed(std::random_device()()); 15 | } 16 | 17 | static uint32_t UInt() 18 | { 19 | return s_Distribution(s_RandomEngine); 20 | } 21 | 22 | static uint32_t UInt(uint32_t min, uint32_t max) 23 | { 24 | return min + (s_Distribution(s_RandomEngine) % (max - min + 1)); 25 | } 26 | 27 | static float Float() 28 | { 29 | return (float)s_Distribution(s_RandomEngine) / (float)std::numeric_limits::max(); 30 | } 31 | 32 | static glm::vec3 Vec3() 33 | { 34 | return glm::vec3(Float(), Float(), Float()); 35 | } 36 | 37 | static glm::vec3 Vec3(float min, float max) 38 | { 39 | return glm::vec3(Float() * (max - min) + min, Float() * (max - min) + min, Float() * (max - min) + min); 40 | } 41 | 42 | static glm::vec3 InUnitSphere() 43 | { 44 | return glm::normalize(Vec3(-1.0f, 1.0f)); 45 | } 46 | private: 47 | static std::mt19937 s_RandomEngine; 48 | static std::uniform_int_distribution s_Distribution; 49 | }; 50 | 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /Walnut/src/Walnut/Timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Walnut { 8 | 9 | class Timer 10 | { 11 | public: 12 | Timer() 13 | { 14 | Reset(); 15 | } 16 | 17 | void Reset() 18 | { 19 | m_Start = std::chrono::high_resolution_clock::now(); 20 | } 21 | 22 | float Elapsed() 23 | { 24 | return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_Start).count() * 0.001f * 0.001f * 0.001f; 25 | } 26 | 27 | float ElapsedMillis() 28 | { 29 | return Elapsed() * 1000.0f; 30 | } 31 | 32 | private: 33 | std::chrono::time_point m_Start; 34 | }; 35 | 36 | class ScopedTimer 37 | { 38 | public: 39 | ScopedTimer(const std::string& name) 40 | : m_Name(name) {} 41 | ~ScopedTimer() 42 | { 43 | float time = m_Timer.ElapsedMillis(); 44 | std::cout << "[TIMER] " << m_Name << " - " << time << "ms\n"; 45 | } 46 | private: 47 | std::string m_Name; 48 | Timer m_Timer; 49 | }; 50 | 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /WalnutApp/premake5.lua: -------------------------------------------------------------------------------- 1 | project "WalnutApp" 2 | kind "ConsoleApp" 3 | language "C++" 4 | cppdialect "C++17" 5 | targetdir "bin/%{cfg.buildcfg}" 6 | staticruntime "off" 7 | 8 | files { "src/**.h", "src/**.cpp" } 9 | 10 | includedirs 11 | { 12 | "../vendor/imgui", 13 | "../vendor/glfw/include", 14 | 15 | "../Walnut/src", 16 | 17 | "%{IncludeDir.VulkanSDK}", 18 | "%{IncludeDir.glm}", 19 | } 20 | 21 | links 22 | { 23 | "Walnut" 24 | } 25 | 26 | targetdir ("../bin/" .. outputdir .. "/%{prj.name}") 27 | objdir ("../bin-int/" .. outputdir .. "/%{prj.name}") 28 | 29 | filter "system:windows" 30 | systemversion "latest" 31 | defines { "WL_PLATFORM_WINDOWS" } 32 | 33 | filter "configurations:Debug" 34 | defines { "WL_DEBUG" } 35 | runtime "Debug" 36 | symbols "On" 37 | 38 | filter "configurations:Release" 39 | defines { "WL_RELEASE" } 40 | runtime "Release" 41 | optimize "On" 42 | symbols "On" 43 | 44 | filter "configurations:Dist" 45 | kind "WindowedApp" 46 | defines { "WL_DIST" } 47 | runtime "Release" 48 | optimize "On" 49 | symbols "Off" -------------------------------------------------------------------------------- /WalnutApp/src/WalnutApp.cpp: -------------------------------------------------------------------------------- 1 | #include "Walnut/Application.h" 2 | #include "Walnut/EntryPoint.h" 3 | 4 | #include "Walnut/Image.h" 5 | 6 | class ExampleLayer : public Walnut::Layer 7 | { 8 | public: 9 | virtual void OnUIRender() override 10 | { 11 | ImGui::Begin("Hello"); 12 | ImGui::Button("Button"); 13 | ImGui::End(); 14 | 15 | ImGui::ShowDemoWindow(); 16 | } 17 | }; 18 | 19 | Walnut::Application* Walnut::CreateApplication(int argc, char** argv) 20 | { 21 | Walnut::ApplicationSpecification spec; 22 | spec.Name = "Walnut Example"; 23 | 24 | Walnut::Application* app = new Walnut::Application(spec); 25 | app->PushLayer(); 26 | app->SetMenubarCallback([app]() 27 | { 28 | if (ImGui::BeginMenu("File")) 29 | { 30 | if (ImGui::MenuItem("Exit")) 31 | { 32 | app->Close(); 33 | } 34 | ImGui::EndMenu(); 35 | } 36 | }); 37 | return app; 38 | } -------------------------------------------------------------------------------- /WalnutExternal.lua: -------------------------------------------------------------------------------- 1 | -- WalnutExternal.lua 2 | 3 | VULKAN_SDK = os.getenv("VULKAN_SDK") 4 | 5 | IncludeDir = {} 6 | IncludeDir["VulkanSDK"] = "%{VULKAN_SDK}/Include" 7 | IncludeDir["glm"] = "../vendor/glm" 8 | 9 | LibraryDir = {} 10 | LibraryDir["VulkanSDK"] = "%{VULKAN_SDK}/Lib" 11 | 12 | Library = {} 13 | Library["Vulkan"] = "%{LibraryDir.VulkanSDK}/vulkan-1.lib" 14 | 15 | group "Dependencies" 16 | include "vendor/imgui" 17 | include "vendor/glfw" 18 | group "" 19 | 20 | group "Core" 21 | include "Walnut" 22 | group "" -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | -- premake5.lua 2 | workspace "WalnutApp" 3 | architecture "x64" 4 | configurations { "Debug", "Release", "Dist" } 5 | startproject "WalnutApp" 6 | 7 | outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}" 8 | 9 | include "WalnutExternal.lua" 10 | include "WalnutApp" -------------------------------------------------------------------------------- /scripts/Setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd .. 4 | vendor\bin\premake5.exe vs2022 5 | popd 6 | pause -------------------------------------------------------------------------------- /vendor/bin/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2003-2022 Jason Perkins and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Premake nor the names of its contributors may be 15 | used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/bin/premake5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StudioCherno/Walnut/3b8e414fdecfc6c8b58816106fe8d912bd172e31/vendor/bin/premake5.exe --------------------------------------------------------------------------------