├── .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 | 
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
--------------------------------------------------------------------------------