From 00deb788279225c4dbfe6c5d51458c9770e55a40 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Mon, 17 Nov 2025 21:46:25 +0000 Subject: [PATCH 1/8] Working on adding vulkan renderer --- .gitignore | 3 +- renderers/vulkan/clay_renderer_vulkan.c | 287 ++++++++++++++++++++++++ 2 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 renderers/vulkan/clay_renderer_vulkan.c diff --git a/.gitignore b/.gitignore index 920b172..3e936e9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ cmake-build-release/ .idea/ node_modules/ *.dSYM -.vs/ \ No newline at end of file +.vs/ +out/ diff --git a/renderers/vulkan/clay_renderer_vulkan.c b/renderers/vulkan/clay_renderer_vulkan.c new file mode 100644 index 0000000..7d2cbf3 --- /dev/null +++ b/renderers/vulkan/clay_renderer_vulkan.c @@ -0,0 +1,287 @@ +// Clay Vulkan renderer sample (C17/C20 friendly) +// This file demonstrates how to traverse Clay_RenderCommandArray and submit Vulkan draw calls. +// The implementation favors clarity over micro-optimizations and documents future improvements. +// Vulkan reference: https://vulkan.lunarg.com/doc/sdk +// ImGui integration notes (for font atlas usage ideas): https://github.com/ocornut/imgui/wiki + +#include +#include +#include +#include "clay.h" + +// Forward declarations ------------------------------------------------------- +struct ClayVulkanRenderer; +static void ClayVulkan_PushScissor(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer); +static void ClayVulkan_PopScissor(struct ClayVulkanRenderer* renderer, VkCommandBuffer commandBuffer); +static void ClayVulkan_DrawRectangle(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer); +static void ClayVulkan_DrawBorder(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer); +static void ClayVulkan_DrawImage(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer); +static void ClayVulkan_DrawText(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer); +static void ClayVulkan_DrawCustom(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer); + +// Resource cache keyed by Clay IDs ------------------------------------------ +typedef struct ClayVulkanTexture +{ + uint32_t m_Id; // Clay image id + VkImageView m_ImageView; + VkSampler m_Sampler; + VkDescriptorSet m_DescriptorSet; +} ClayVulkanTexture; + +typedef struct ClayVulkanFont +{ + uint16_t m_FontId; + uint16_t m_FontSize; + void* m_FontUserData; // Pointer to glyph atlas / font metrics owned by the application +} ClayVulkanFont; + +typedef struct ClayVulkanResourceCache +{ + ClayVulkanTexture m_Textures[64]; + uint32_t m_TextureCount; + ClayVulkanFont m_Fonts[32]; + uint32_t m_FontCount; +} ClayVulkanResourceCache; + +// Renderer state ------------------------------------------------------------ +typedef struct ClayVulkanRenderer +{ + VkDevice m_Device; + VkRenderPass m_RenderPass; // Compatible with the framebuffer bound by the host application + VkPipeline m_RectPipeline; + VkPipeline m_TextPipeline; + VkPipeline m_ImagePipeline; + VkPipelineLayout m_PipelineLayout; + VkCommandBuffer m_ActiveCommandBuffer; + VkRect2D m_ScissorStack[16]; + uint32_t m_ScissorCount; + ClayVulkanResourceCache m_ResourceCache; + // The sample keeps descriptor pools outside; the host passes ready-to-bind descriptor sets. +} ClayVulkanRenderer; + +// Utility: fetch or insert cached texture ----------------------------------- +static ClayVulkanTexture* ClayVulkan_GetTexture(ClayVulkanResourceCache* cache, uint32_t id) +{ + for (uint32_t it_Index = 0; it_Index < cache->m_TextureCount; ++it_Index) + { + ClayVulkanTexture* l_Texture = &cache->m_Textures[it_Index]; + if (l_Texture->m_Id == id) + { + return l_Texture; + } + } + if (cache->m_TextureCount >= (uint32_t)(sizeof(cache->m_Textures) / sizeof(cache->m_Textures[0]))) + { + return NULL; // Out of cache entries; caller can log and skip drawing. + } + ClayVulkanTexture* l_Texture = &cache->m_Textures[cache->m_TextureCount++]; + memset(l_Texture, 0, sizeof(*l_Texture)); + l_Texture->m_Id = id; + + return l_Texture; +} + +// Utility: fetch or insert cached font -------------------------------------- +static ClayVulkanFont* ClayVulkan_GetFont(ClayVulkanResourceCache* cache, uint16_t fontId, uint16_t fontSize) +{ + for (uint32_t it_Index = 0; it_Index < cache->m_FontCount; ++it_Index) + { + ClayVulkanFont* l_Font = &cache->m_Fonts[it_Index]; + if (l_Font->m_FontId == fontId && l_Font->m_FontSize == fontSize) + { + return l_Font; + } + } + if (cache->m_FontCount >= (uint32_t)(sizeof(cache->m_Fonts) / sizeof(cache->m_Fonts[0]))) + { + return NULL; + } + ClayVulkanFont* l_Font = &cache->m_Fonts[cache->m_FontCount++]; + memset(l_Font, 0, sizeof(*l_Font)); + l_Font->m_FontId = fontId; + l_Font->m_FontSize = fontSize; + + return l_Font; +} + +// Text measurement hook ------------------------------------------------------ +// Backs Clay_SetMeasureTextFunction and aligns with Clay_TextRenderData expectations. +static Clay_Dimensions ClayVulkan_MeasureText(Clay_StringSlice text, Clay_TextElementConfig* config, void* userData) +{ + ClayVulkanRenderer* l_Renderer = (ClayVulkanRenderer*)userData; + ClayVulkanFont* l_Font = ClayVulkan_GetFont(&l_Renderer->m_ResourceCache, config->fontId, config->fontSize); + // The sample assumes the host supplies pixel metrics via m_FontUserData (e.g., stb_truetype bake results). + // For demonstration, approximate width using a monospaced assumption and letter spacing. + const float s_DefaultGlyphWidth = (float)config->fontSize * 0.6f; + float l_Width = (float)text.length * s_DefaultGlyphWidth + (float)config->letterSpacing * (float)text.length; + float l_Height = (float)config->lineHeight; + (void)l_Font; // In a real integration, sample glyph advances from l_Font->m_FontUserData. + + return (Clay_Dimensions) { l_Width, l_Height }; +} + +// Scissor management --------------------------------------------------------- +static void ClayVulkan_PushScissor(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) +{ + if (renderer->m_ScissorCount >= (uint32_t)(sizeof(renderer->m_ScissorStack) / sizeof(renderer->m_ScissorStack[0]))) + { + return; // Avoid overflow; future improvement could resize dynamically. + } + VkRect2D l_Rect = { 0 }; + l_Rect.offset.x = (int32_t)command->boundingBox.x; + l_Rect.offset.y = (int32_t)command->boundingBox.y; + l_Rect.extent.width = (uint32_t)command->boundingBox.width; + l_Rect.extent.height = (uint32_t)command->boundingBox.height; + renderer->m_ScissorStack[renderer->m_ScissorCount++] = l_Rect; + vkCmdSetScissor(commandBuffer, 0, 1, &l_Rect); +} + +static void ClayVulkan_PopScissor(ClayVulkanRenderer* renderer, VkCommandBuffer commandBuffer) +{ + if (renderer->m_ScissorCount == 0) + { + return; + } + renderer->m_ScissorCount--; + if (renderer->m_ScissorCount == 0) + { + // Fall back to a full viewport scissor. The host should have set a default scissor before calling render. + return; + } + + VkRect2D l_Rect = renderer->m_ScissorStack[renderer->m_ScissorCount - 1]; + vkCmdSetScissor(commandBuffer, 0, 1, &l_Rect); +} + +// Rendering helpers ---------------------------------------------------------- +static void ClayVulkan_DrawRectangle(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) +{ + (void)renderer; + // The sample assumes a pipeline using push constants for color + corner radius and a unit quad vertex buffer. + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_RectPipeline); + // Future improvement: instance multiple rectangles in a single vkCmdDrawIndexed to amortize state changes. + vkCmdDraw(commandBuffer, 6, 1, 0, 0); +} + +static void ClayVulkan_DrawBorder(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) +{ + (void)command; + // Border rendering could expand to 4 rectangles; here we rely on a shader that interprets width + corner radius from push constants. + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_RectPipeline); + vkCmdDraw(commandBuffer, 6, 1, 0, 0); +} + +static void ClayVulkan_DrawImage(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) +{ + ClayVulkanTexture* l_Texture = ClayVulkan_GetTexture(&renderer->m_ResourceCache, command->id); + if (!l_Texture || !l_Texture->m_DescriptorSet) { + return; // Caller must populate descriptor sets before rendering. + } + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_ImagePipeline); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_PipelineLayout, 0, 1, &l_Texture->m_DescriptorSet, 0, NULL); + vkCmdDraw(commandBuffer, 6, 1, 0, 0); +} + +static void ClayVulkan_DrawText(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) +{ + Clay_TextRenderData l_Text = command->renderData.text; + ClayVulkanFont* l_Font = ClayVulkan_GetFont(&renderer->m_ResourceCache, l_Text.fontId, l_Text.fontSize); + (void)l_Font; + // The host is expected to have built glyph vertices into a transient buffer before calling this renderer. + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_TextPipeline); + // Future improvement: bind per-font descriptor sets for font atlas textures instead of assuming a global one. + vkCmdDraw(commandBuffer, (uint32_t)l_Text.stringContents.length * 4, 1, 0, 0); +} + +static void ClayVulkan_DrawCustom(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) +{ + (void)renderer; + (void)command; + // Custom elements give the host full control. Applications can use command->renderData.custom.customData to pick pipelines. + // Future improvement: provide callback hooks so applications can supply their own vkCmd* sequence here. + (void)commandBuffer; +} + +// Main entry point ----------------------------------------------------------- +void Clay_Vulkan_Render(ClayVulkanRenderer* renderer, Clay_RenderCommandArray renderCommands, VkCommandBuffer commandBuffer) +{ + renderer->m_ActiveCommandBuffer = commandBuffer; + // Command traversal: the array is already z-sorted; we iterate sequentially and rely on lightweight state changes. + for (int32_t it_Command = 0; it_Command < renderCommands.length; ++it_Command) + { + Clay_RenderCommand* l_Command = Clay_RenderCommandArray_Get(&renderCommands, it_Command); + switch (l_Command->commandType) + { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: + ClayVulkan_DrawRectangle(renderer, l_Command, commandBuffer); + break; + case CLAY_RENDER_COMMAND_TYPE_BORDER: + ClayVulkan_DrawBorder(renderer, l_Command, commandBuffer); + break; + case CLAY_RENDER_COMMAND_TYPE_TEXT: + ClayVulkan_DrawText(renderer, l_Command, commandBuffer); + break; + case CLAY_RENDER_COMMAND_TYPE_IMAGE: + ClayVulkan_DrawImage(renderer, l_Command, commandBuffer); + break; + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: + ClayVulkan_PushScissor(renderer, l_Command, commandBuffer); + break; + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: + ClayVulkan_PopScissor(renderer, commandBuffer); + break; + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: + ClayVulkan_DrawCustom(renderer, l_Command, commandBuffer); + break; + default: + break; + } + } +} + +// Initialization helpers ---------------------------------------------------- +void Clay_VulkanRenderer_Init(ClayVulkanRenderer* renderer, VkDevice device, VkRenderPass renderPass, VkPipelineLayout pipelineLayout) +{ + renderer->m_Device = device; + renderer->m_RenderPass = renderPass; + renderer->m_PipelineLayout = pipelineLayout; + renderer->m_RectPipeline = VK_NULL_HANDLE; + renderer->m_TextPipeline = VK_NULL_HANDLE; + renderer->m_ImagePipeline = VK_NULL_HANDLE; + renderer->m_ActiveCommandBuffer = VK_NULL_HANDLE; + renderer->m_ScissorCount = 0; + + memset(&renderer->m_ResourceCache, 0, sizeof(renderer->m_ResourceCache)); +} + +void Clay_VulkanRenderer_SetPipelines(ClayVulkanRenderer* renderer, VkPipeline rectPipeline, VkPipeline textPipeline, VkPipeline imagePipeline) +{ + renderer->m_RectPipeline = rectPipeline; + renderer->m_TextPipeline = textPipeline; + renderer->m_ImagePipeline = imagePipeline; +} + +// Hook into Clay's text measuring API. +void Clay_VulkanRenderer_RegisterTextMeasure(ClayVulkanRenderer* renderer) +{ + Clay_SetMeasureTextFunction(ClayVulkan_MeasureText, renderer); +} + +// Example for populating the texture cache with externally created descriptors. +void Clay_VulkanRenderer_RegisterTexture(ClayVulkanRenderer* renderer, uint32_t clayId, VkImageView imageView, VkSampler sampler, VkDescriptorSet descriptorSet) +{ + ClayVulkanTexture* l_Texture = ClayVulkan_GetTexture(&renderer->m_ResourceCache, clayId); + if (!l_Texture) + { + return; + } + + l_Texture->m_ImageView = imageView; + l_Texture->m_Sampler = sampler; + l_Texture->m_DescriptorSet = descriptorSet; +} + +// Future improvements ------------------------------------------------------- +// - Descriptor recycling: shrink transient allocations by pooling VkDescriptorSet objects per frame. +// - Dynamic rendering: replace render pass dependency with vkCmdBeginRendering for more flexible swapchain formats. +// - Batching: group adjacent rectangles or text quads into large instance draws using secondary command buffers. \ No newline at end of file From f110559639011331e2aa916fe1fa75ddb51180d9 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Mon, 17 Nov 2025 22:55:06 +0000 Subject: [PATCH 2/8] Updated the main.cpp file --- README.md | 80 +++++++++++++++++++++++++++ examples/cpp-project-example/main.cpp | 70 ++++++++++++++++++++--- 2 files changed, 141 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 802e5be..8648256 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,86 @@ The above example, rendered correctly will look something like the following: ![Clay Example](https://github.com/user-attachments/assets/1928c6d4-ada9-4a4c-a3d1-44fe9b23b3bd) +### Visual Studio 2022 / MSVC build configuration + +The current README did not spell out how to set up Clay with Visual Studio 2022, so the following steps target MSVC users building a single `CLAY_IMPLEMENTATION` translation unit alongside a Vulkan renderer. These steps also leave room for future improvements such as better swapchain-resize handling and validation tightening. + +- **Language standard & warning level:** + - Set the **C Language Standard** to **ISO C17** (`/std:c17`) in *Configuration Properties → C/C++ → Language*. Clay is plain C, but C++20 projects can include the generated object because the C translation unit is compiled separately. + - Use **Warning Level** `/W4` (or `/Wall` when validating new integrations) and treat warnings as errors for the Clay translation unit to catch integration issues early. + - Keep `CLAY_IMPLEMENTATION` defined in exactly one `.c` file to avoid multiple-definition linker errors; all other translation units should include `clay.h` without the define. +- **Vulkan SDK include/lib setup (LunarG):** + - Install the [LunarG Vulkan SDK](https://vulkan.lunarg.com/doc/sdk) and ensure the `VULKAN_SDK` environment variable is set by the installer. + - Add `$(VULKAN_SDK)\Include` to *Configuration Properties → C/C++ → General → Additional Include Directories*. + - Add `$(VULKAN_SDK)\Lib` to *Configuration Properties → Linker → General → Additional Library Directories* and link against `vulkan-1.lib` (plus any allocator / shader utility libs you use). + - When mixing with ImGui backends, keep the [ImGui wiki guidelines](https://github.com/ocornut/imgui/wiki) handy for sampler, descriptor, and swapchain best practices. + +**Minimal MSVC-friendly Vulkan loop (C17) showing Clay command mapping and scissor stack for scrolling** + +```C +// This translation unit should be compiled with /std:c17 and /W4 using MSVC in Visual Studio 2022. +// The snippet iterates Clay_RenderCommandArray, maps commands to Vulkan pipelines, and maintains a scissor stack for scroll regions. +static void RenderClayCommands(VkCommandBuffer l_CommandBuffer, Clay_RenderCommandArray l_RenderCommands) { + VkPipeline l_RectPipeline = /* created elsewhere */ VK_NULL_HANDLE; + VkPipeline l_TextPipeline = /* created elsewhere */ VK_NULL_HANDLE; + VkRect2D l_ScissorStack[8] = {0}; + int32_t l_ScissorDepth = 0; + + for (int32_t l_CommandIndex = 0; l_CommandIndex < l_RenderCommands.length; ++l_CommandIndex) { + Clay_RenderCommand *l_Command = &l_RenderCommands.internalArray[l_CommandIndex]; + + switch (l_Command->commandType) { + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + // Push the scrollable clip rect to keep nested scroll areas isolated. + VkRect2D l_NewScissor = { + .offset = { (int32_t)l_Command->boundingBox.x, (int32_t)l_Command->boundingBox.y }, + .extent = { (uint32_t)l_Command->boundingBox.width, (uint32_t)l_Command->boundingBox.height } + }; + if (l_ScissorDepth < (int32_t)(sizeof l_ScissorStack / sizeof l_ScissorStack[0])) { + l_ScissorStack[l_ScissorDepth++] = l_NewScissor; + vkCmdSetScissor(l_CommandBuffer, 0, 1, &l_NewScissor); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + // Pop the scissor so parents resume drawing without child clips. + if (l_ScissorDepth > 0) { + --l_ScissorDepth; + if (l_ScissorDepth > 0) { + vkCmdSetScissor(l_CommandBuffer, 0, 1, &l_ScissorStack[l_ScissorDepth - 1]); + } + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + // Map to a color-pass pipeline and draw the quad for this UI rect. + vkCmdBindPipeline(l_CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, l_RectPipeline); + // ...bind vertex buffers and push constants for l_Command->renderData.rectangle.backgroundColor + // ...issue vkCmdDraw to render the rectangle + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + // Select the text pipeline; glyph atlas binding follows ImGui-style descriptor usage. + vkCmdBindPipeline(l_CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, l_TextPipeline); + // ...bind font atlas descriptors, set per-draw uniforms, and vkCmdDrawIndexed for glyphs + break; + } + default: { + // Additional Clay command types (images, borders, custom) can be routed here. + break; + } + } + } +} +``` + +**Troubleshooting and future enhancements** + +- If scissor clipping appears inverted, verify that the swapchain surface transform is accounted for before setting `VkRect2D`. +- MSVC link errors referencing `CLAY_IMPLEMENTATION` usually mean more than one translation unit defined the macro; keep it in a single `.c` file. +- Vulkan validation warnings about descriptor lifetimes often stem from text pipelines; cross-check against the [ImGui wiki recommendations](https://github.com/ocornut/imgui/wiki). +- Consider adding a resize strategy that rebuilds pipelines only when swapchain formats change (future enhancement), and batch multiple Clay rectangles into shared vertex buffers to reduce draw calls. + In summary, the general order of steps is: 1. [Clay_SetLayoutDimensions(dimensions)](#clay_setlayoutdimensions) diff --git a/examples/cpp-project-example/main.cpp b/examples/cpp-project-example/main.cpp index 3770d29..ca1b736 100644 --- a/examples/cpp-project-example/main.cpp +++ b/examples/cpp-project-example/main.cpp @@ -1,21 +1,73 @@ -#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include + #define CLAY_IMPLEMENTATION #include "../../clay.h" -Clay_LayoutConfig layoutElement = Clay_LayoutConfig { .padding = {5} }; +// Simple layout config with prefixed naming to match the MSVC-friendly style guide. +Clay_LayoutConfig s_LayoutElement = Clay_LayoutConfig{ .padding = {5} }; -void HandleClayErrors(Clay_ErrorData errorData) { +// Win32-compatible font handle that can be reused by the text measuring callback. +static HFONT s_TextFont = static_cast(GetStockObject(DEFAULT_GUI_FONT)); + +// Bridge Clay's text measuring hook to GDI so layouts get accurate extents on Windows. +Clay_Dimensions HandleMeasureText(Clay_StringSlice text, Clay_TextElementConfig* config, void* userData) +{ + (void)config; // Config is available for future font switching or styling. + HFONT* l_FontHandle = static_cast(userData); + HFONT l_ResolvedFont = l_FontHandle != nullptr && *l_FontHandle != nullptr ? *l_FontHandle : s_TextFont; + + HDC l_DeviceContext = GetDC(nullptr); + if (l_DeviceContext == nullptr) + { + return Clay_Dimensions{ 0, 0 }; + } + + HGDIOBJ l_PreviousFont = nullptr; + if (l_ResolvedFont != nullptr) + { + l_PreviousFont = SelectObject(l_DeviceContext, l_ResolvedFont); + } + + SIZE l_TextSize{ 0, 0 }; + int l_TextLength = static_cast(text.length); + GetTextExtentPoint32A(l_DeviceContext, text.chars, l_TextLength, &l_TextSize); + + if (l_PreviousFont != nullptr) + { + SelectObject(l_DeviceContext, l_PreviousFont); + } + ReleaseDC(nullptr, l_DeviceContext); + + // Future improvement: swap GDI for DirectWrite or cache glyph metrics to avoid repeated calls. + return Clay_Dimensions{ static_cast(l_TextSize.cx), static_cast(l_TextSize.cy) }; +} + +void HandleClayErrors(Clay_ErrorData errorData) +{ printf("%s", errorData.errorText.chars); } -int main(void) { - uint64_t totalMemorySize = Clay_MinMemorySize(); - Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize)); - Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors }); +int main(void) +{ + uint64_t l_TotalMemorySize = Clay_MinMemorySize(); + Clay_Arena l_ClayMemory = Clay_CreateArenaWithCapacityAndMemory(l_TotalMemorySize, static_cast(malloc(l_TotalMemorySize))); + + // Initialize the Clay context and immediately provide a Windows-friendly text measure callback. + Clay_Initialize(l_ClayMemory, Clay_Dimensions{ 1024, 768 }, Clay_ErrorHandler{ HandleClayErrors }); + Clay_SetMeasureTextFunction(HandleMeasureText, &s_TextFont); + + // The measure function lets Clay compute text bounds before laying out widgets. Clay_BeginLayout(); - CLAY_AUTO_ID({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) { + CLAY_AUTO_ID({ .layout = s_LayoutElement, .backgroundColor = {255,255,255,0} }) + { CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 })); } Clay_EndLayout(); + return 0; -} +} \ No newline at end of file From 6de8bfa4ae0e4d9c8b017babcc99350764dc82b2 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Mon, 17 Nov 2025 23:19:18 +0000 Subject: [PATCH 3/8] Added vulkan demo --- examples/vulkan-demo/CMakeLists.txt | 49 ++ examples/vulkan-demo/README.md | 28 ++ examples/vulkan-demo/main.cpp | 736 ++++++++++++++++++++++++++++ 3 files changed, 813 insertions(+) create mode 100644 examples/vulkan-demo/CMakeLists.txt create mode 100644 examples/vulkan-demo/README.md create mode 100644 examples/vulkan-demo/main.cpp diff --git a/examples/vulkan-demo/CMakeLists.txt b/examples/vulkan-demo/CMakeLists.txt new file mode 100644 index 0000000..d2cfd90 --- /dev/null +++ b/examples/vulkan-demo/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.27) +project(clay_examples_vulkan_demo LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) + +# GLFW is used only for window creation and Vulkan surface management in this demo. +FetchContent_Declare( + glfw + GIT_REPOSITORY "https://github.com/glfw/glfw.git" + GIT_TAG "3.4" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(glfw) + +find_package(Vulkan REQUIRED) + +add_executable(clay_examples_vulkan_demo + main.cpp +) + +target_include_directories(clay_examples_vulkan_demo PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../.. +) + +target_link_libraries(clay_examples_vulkan_demo PRIVATE + glfw + Vulkan::Vulkan +) + +if(MSVC) + # Keep MSVC warnings visible while allowing the demo to compile cleanly. + target_compile_options(clay_examples_vulkan_demo PRIVATE /W4) +else() + target_compile_options(clay_examples_vulkan_demo PRIVATE -Wall -Wextra) +endif() + +# Allow Visual Studio users to copy sample resources (fonts, textures) beside the executable later. +add_custom_target(clay_examples_vulkan_demo_copy_resources ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/resources + COMMENT "Prepare resource directory for future font/texture assets" +) +add_dependencies(clay_examples_vulkan_demo clay_examples_vulkan_demo_copy_resources) \ No newline at end of file diff --git a/examples/vulkan-demo/README.md b/examples/vulkan-demo/README.md new file mode 100644 index 0000000..bcba16f --- /dev/null +++ b/examples/vulkan-demo/README.md @@ -0,0 +1,28 @@ +# Clay Vulkan Demo + +This Visual Studio 2022 friendly sample shows how to pair Clay's layout engine with a Vulkan swapchain. It uses GLFW for the Win32 surface and stitches Clay render commands through the Vulkan renderer adapter. + +## Build prerequisites +- [Vulkan SDK](https://vulkan.lunarg.com/doc/sdk) installed and the `VULKAN_SDK` environment variable set. +- Visual Studio 2022 with CMake and the MSVC toolset. +- Git submodules are not required because GLFW is fetched automatically through CMake's `FetchContent`. + +## Configure and build (Visual Studio 2022) +1. Open a **Developer PowerShell for VS 2022** so the compiler environment variables are available. +2. Generate a build directory: + ```pwsh + cmake -S examples/vulkan-demo -B build/vulkan-demo -G "Visual Studio 17 2022" -A x64 + ``` +3. Build the executable: + ```pwsh + cmake --build build/vulkan-demo --config Debug + ``` +4. Run the demo from the build tree (the app opens a GLFW window): + ```pwsh + build/vulkan-demo/Debug/clay_examples_vulkan_demo.exe + ``` + +## Notes and future work +- The code is intentionally verbose and commented with links to the Vulkan SDK reference and the ImGui wiki for font atlas/descriptor sharing advice. +- Swapchain, render pass, and command buffer setup are present, but pipeline configuration uses placeholders so you can wire your own shaders. +- TODOs remain for dynamic descriptor management and richer font handling; these are natural next steps after the basic window + swapchain bring-up. \ No newline at end of file diff --git a/examples/vulkan-demo/main.cpp b/examples/vulkan-demo/main.cpp new file mode 100644 index 0000000..bda7b98 --- /dev/null +++ b/examples/vulkan-demo/main.cpp @@ -0,0 +1,736 @@ +// Vulkan + Clay demo +// Vulkan SDK documentation: https://vulkan.lunarg.com/doc/sdk +// ImGui integration ideas for font atlases and descriptor reuse: https://github.com/ocornut/imgui/wiki + +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLAY_IMPLEMENTATION +extern "C" { +#include "../../clay.h" +#include "../../renderers/vulkan/clay_renderer_vulkan.c" +} + +namespace +{ + const uint32_t s_DefaultWidth = 1280; + const uint32_t s_DefaultHeight = 720; + const std::vector s_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + + struct QueueFamilyIndices + { + std::optional m_GraphicsFamily; + std::optional m_PresentFamily; + + bool IsComplete() const + { + return m_GraphicsFamily.has_value() && m_PresentFamily.has_value(); + } + }; + + struct SwapchainSupportDetails + { + VkSurfaceCapabilitiesKHR m_Capabilities{}; + std::vector m_Formats{}; + std::vector m_PresentModes{}; + }; +} // namespace + +class VulkanDemoApp +{ +public: + void Run() + { + try + { + InitializeWindow(); + InitializeVulkan(); + InitializeClay(); + MainLoop(); + Cleanup(); + } + catch (const std::exception& l_Error) + { + std::cerr << "Demo failed: " << l_Error.what() << std::endl; + throw; + } + } + +private: + GLFWwindow* m_Window = nullptr; + VkInstance m_Instance = VK_NULL_HANDLE; + VkSurfaceKHR m_Surface = VK_NULL_HANDLE; + VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE; + VkDevice m_Device = VK_NULL_HANDLE; + VkQueue m_GraphicsQueue = VK_NULL_HANDLE; + VkQueue m_PresentQueue = VK_NULL_HANDLE; + VkSwapchainKHR m_Swapchain = VK_NULL_HANDLE; + std::vector m_SwapchainImages{}; + std::vector m_SwapchainImageViews{}; + VkFormat m_SwapchainImageFormat = VK_FORMAT_B8G8R8A8_SRGB; + VkExtent2D m_SwapchainExtent{}; + VkRenderPass m_RenderPass = VK_NULL_HANDLE; + std::vector m_SwapchainFramebuffers{}; + VkCommandPool m_CommandPool = VK_NULL_HANDLE; + std::vector m_CommandBuffers{}; + VkSemaphore m_ImageAvailableSemaphore = VK_NULL_HANDLE; + VkSemaphore m_RenderFinishedSemaphore = VK_NULL_HANDLE; + VkFence m_InFlightFence = VK_NULL_HANDLE; + + // Clay renderer bindings + ClayVulkanRenderer m_ClayRenderer{}; + Clay_Arena m_ClayArena{}; + + void InitializeWindow() + { + if (!glfwInit()) + { + throw std::runtime_error("Failed to initialize GLFW"); + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + m_Window = glfwCreateWindow(static_cast(s_DefaultWidth), static_cast(s_DefaultHeight), "Clay Vulkan Demo", nullptr, nullptr); + if (m_Window == nullptr) + { + throw std::runtime_error("Failed to create GLFW window"); + } + } + + void InitializeVulkan() + { + CreateInstance(); + CreateSurface(); + PickPhysicalDevice(); + CreateLogicalDevice(); + CreateSwapchain(); + CreateImageViews(); + CreateRenderPass(); + CreateFramebuffers(); + CreateCommandPool(); + AllocateCommandBuffers(); + CreateSyncObjects(); + } + + void InitializeClay() + { + uint64_t l_TotalMemorySize = Clay_MinMemorySize(); + m_ClayArena = Clay_CreateArenaWithCapacityAndMemory(l_TotalMemorySize, malloc(l_TotalMemorySize)); + Clay_Initialize(m_ClayArena, (Clay_Dimensions) { static_cast(m_SwapchainExtent.width), static_cast(m_SwapchainExtent.height) }, (Clay_ErrorHandler) { 0 }); + + Clay_VulkanRenderer_Init(&m_ClayRenderer, m_Device, m_RenderPass, VK_NULL_HANDLE); + Clay_VulkanRenderer_SetPipelines(&m_ClayRenderer, VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE); + // TODO: Plug descriptor pool + layout management for textures/fonts instead of placeholder pipeline handles. + Clay_VulkanRenderer_RegisterTextMeasure(&m_ClayRenderer); + } + + void MainLoop() + { + while (!glfwWindowShouldClose(m_Window)) + { + glfwPollEvents(); + + int l_Width = 0; + int l_Height = 0; + glfwGetFramebufferSize(m_Window, &l_Width, &l_Height); + m_SwapchainExtent.width = static_cast(l_Width); + m_SwapchainExtent.height = static_cast(l_Height); + Clay_SetLayoutDimensions((Clay_Dimensions) { static_cast(l_Width), static_cast(l_Height) }); + + Clay_RenderCommandArray l_Commands = CreateLayout(); + DrawFrame(l_Commands); + } + + vkDeviceWaitIdle(m_Device); + } + + void Cleanup() + { + vkDeviceWaitIdle(m_Device); + + vkDestroyFence(m_Device, m_InFlightFence, nullptr); + vkDestroySemaphore(m_Device, m_RenderFinishedSemaphore, nullptr); + vkDestroySemaphore(m_Device, m_ImageAvailableSemaphore, nullptr); + + for (VkFramebuffer l_Framebuffer : m_SwapchainFramebuffers) + { + vkDestroyFramebuffer(m_Device, l_Framebuffer, nullptr); + } + for (VkImageView l_ImageView : m_SwapchainImageViews) + { + vkDestroyImageView(m_Device, l_ImageView, nullptr); + } + + vkDestroyRenderPass(m_Device, m_RenderPass, nullptr); + vkDestroyCommandPool(m_Device, m_CommandPool, nullptr); + vkDestroySwapchainKHR(m_Device, m_Swapchain, nullptr); + vkDestroyDevice(m_Device, nullptr); + vkDestroySurfaceKHR(m_Instance, m_Surface, nullptr); + vkDestroyInstance(m_Instance, nullptr); + + glfwDestroyWindow(m_Window); + glfwTerminate(); + + Clay_FreeArena(m_ClayArena); + } + + void CreateInstance() + { + VkApplicationInfo l_AppInfo{}; + l_AppInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + l_AppInfo.pApplicationName = "Clay Vulkan Demo"; + l_AppInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + l_AppInfo.pEngineName = "Clay"; + l_AppInfo.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + l_AppInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo l_CreateInfo{}; + l_CreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + l_CreateInfo.pApplicationInfo = &l_AppInfo; + + uint32_t l_GlfwExtensionCount = 0; + const char** l_Extensions = glfwGetRequiredInstanceExtensions(&l_GlfwExtensionCount); + l_CreateInfo.enabledExtensionCount = l_GlfwExtensionCount; + l_CreateInfo.ppEnabledExtensionNames = l_Extensions; + + if (vkCreateInstance(&l_CreateInfo, nullptr, &m_Instance) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create Vulkan instance; see Vulkan SDK setup guidance."); + } + } + + void CreateSurface() + { + if (glfwCreateWindowSurface(m_Instance, m_Window, nullptr, &m_Surface) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create window surface"); + } + } + + void PickPhysicalDevice() + { + uint32_t l_DeviceCount = 0; + vkEnumeratePhysicalDevices(m_Instance, &l_DeviceCount, nullptr); + if (l_DeviceCount == 0) + { + throw std::runtime_error("No Vulkan-capable GPU found"); + } + + std::vector l_Devices(l_DeviceCount); + vkEnumeratePhysicalDevices(m_Instance, &l_DeviceCount, l_Devices.data()); + + for (const VkPhysicalDevice& it_Device : l_Devices) + { + if (IsDeviceSuitable(it_Device)) + { + m_PhysicalDevice = it_Device; + break; + } + } + + if (m_PhysicalDevice == VK_NULL_HANDLE) + { + throw std::runtime_error("No suitable GPU found"); + } + } + + void CreateLogicalDevice() + { + QueueFamilyIndices l_Indices = FindQueueFamilies(m_PhysicalDevice); + + std::vector l_QueueCreateInfos{}; + std::set l_UniqueQueueFamilies = { l_Indices.m_GraphicsFamily.value(), l_Indices.m_PresentFamily.value() }; + + float l_QueuePriority = 1.0f; + for (uint32_t it_FamilyIndex : l_UniqueQueueFamilies) + { + VkDeviceQueueCreateInfo l_QueueCreateInfo{}; + l_QueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + l_QueueCreateInfo.queueFamilyIndex = it_FamilyIndex; + l_QueueCreateInfo.queueCount = 1; + l_QueueCreateInfo.pQueuePriorities = &l_QueuePriority; + l_QueueCreateInfos.push_back(l_QueueCreateInfo); + } + + VkPhysicalDeviceFeatures l_DeviceFeatures{}; + VkDeviceCreateInfo l_CreateInfo{}; + l_CreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + l_CreateInfo.queueCreateInfoCount = static_cast(l_QueueCreateInfos.size()); + l_CreateInfo.pQueueCreateInfos = l_QueueCreateInfos.data(); + l_CreateInfo.pEnabledFeatures = &l_DeviceFeatures; + l_CreateInfo.enabledExtensionCount = static_cast(s_DeviceExtensions.size()); + l_CreateInfo.ppEnabledExtensionNames = s_DeviceExtensions.data(); + + if (vkCreateDevice(m_PhysicalDevice, &l_CreateInfo, nullptr, &m_Device) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create logical device"); + } + + vkGetDeviceQueue(m_Device, l_Indices.m_GraphicsFamily.value(), 0, &m_GraphicsQueue); + vkGetDeviceQueue(m_Device, l_Indices.m_PresentFamily.value(), 0, &m_PresentQueue); + } + + void CreateSwapchain() + { + SwapchainSupportDetails l_Support = QuerySwapchainSupport(m_PhysicalDevice); + + VkSurfaceFormatKHR l_SurfaceFormat = ChooseSwapSurfaceFormat(l_Support.m_Formats); + VkPresentModeKHR l_PresentMode = ChoosePresentMode(l_Support.m_PresentModes); + VkExtent2D l_Extent = ChooseSwapExtent(l_Support.m_Capabilities); + + uint32_t l_ImageCount = l_Support.m_Capabilities.minImageCount + 1; + if (l_Support.m_Capabilities.maxImageCount > 0 && l_ImageCount > l_Support.m_Capabilities.maxImageCount) + { + l_ImageCount = l_Support.m_Capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR l_CreateInfo{}; + l_CreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + l_CreateInfo.surface = m_Surface; + l_CreateInfo.minImageCount = l_ImageCount; + l_CreateInfo.imageFormat = l_SurfaceFormat.format; + l_CreateInfo.imageColorSpace = l_SurfaceFormat.colorSpace; + l_CreateInfo.imageExtent = l_Extent; + l_CreateInfo.imageArrayLayers = 1; + l_CreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices l_Indices = FindQueueFamilies(m_PhysicalDevice); + uint32_t l_QueueFamilyIndices[] = { l_Indices.m_GraphicsFamily.value(), l_Indices.m_PresentFamily.value() }; + + if (l_Indices.m_GraphicsFamily != l_Indices.m_PresentFamily) + { + l_CreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + l_CreateInfo.queueFamilyIndexCount = 2; + l_CreateInfo.pQueueFamilyIndices = l_QueueFamilyIndices; + } + else + { + l_CreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + l_CreateInfo.queueFamilyIndexCount = 0; + l_CreateInfo.pQueueFamilyIndices = nullptr; + } + + l_CreateInfo.preTransform = l_Support.m_Capabilities.currentTransform; + l_CreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + l_CreateInfo.presentMode = l_PresentMode; + l_CreateInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(m_Device, &l_CreateInfo, nullptr, &m_Swapchain) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create swapchain"); + } + + vkGetSwapchainImagesKHR(m_Device, m_Swapchain, &l_ImageCount, nullptr); + m_SwapchainImages.resize(l_ImageCount); + vkGetSwapchainImagesKHR(m_Device, m_Swapchain, &l_ImageCount, m_SwapchainImages.data()); + + m_SwapchainImageFormat = l_SurfaceFormat.format; + m_SwapchainExtent = l_Extent; + } + + void CreateImageViews() + { + m_SwapchainImageViews.resize(m_SwapchainImages.size()); + + for (size_t it_Index = 0; it_Index < m_SwapchainImages.size(); ++it_Index) + { + VkImageViewCreateInfo l_CreateInfo{}; + l_CreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + l_CreateInfo.image = m_SwapchainImages[it_Index]; + l_CreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + l_CreateInfo.format = m_SwapchainImageFormat; + l_CreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + l_CreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + l_CreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + l_CreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + l_CreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + l_CreateInfo.subresourceRange.baseMipLevel = 0; + l_CreateInfo.subresourceRange.levelCount = 1; + l_CreateInfo.subresourceRange.baseArrayLayer = 0; + l_CreateInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(m_Device, &l_CreateInfo, nullptr, &m_SwapchainImageViews[it_Index]) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create image views"); + } + } + } + + void CreateRenderPass() + { + VkAttachmentDescription l_ColorAttachment{}; + l_ColorAttachment.format = m_SwapchainImageFormat; + l_ColorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + l_ColorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + l_ColorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + l_ColorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + l_ColorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + l_ColorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + l_ColorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference l_ColorAttachmentRef{}; + l_ColorAttachmentRef.attachment = 0; + l_ColorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription l_Subpass{}; + l_Subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + l_Subpass.colorAttachmentCount = 1; + l_Subpass.pColorAttachments = &l_ColorAttachmentRef; + + VkRenderPassCreateInfo l_RenderPassInfo{}; + l_RenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + l_RenderPassInfo.attachmentCount = 1; + l_RenderPassInfo.pAttachments = &l_ColorAttachment; + l_RenderPassInfo.subpassCount = 1; + l_RenderPassInfo.pSubpasses = &l_Subpass; + + if (vkCreateRenderPass(m_Device, &l_RenderPassInfo, nullptr, &m_RenderPass) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create render pass"); + } + } + + void CreateFramebuffers() + { + m_SwapchainFramebuffers.resize(m_SwapchainImageViews.size()); + + for (size_t it_Index = 0; it_Index < m_SwapchainImageViews.size(); ++it_Index) + { + VkImageView l_Attachments[] = { m_SwapchainImageViews[it_Index] }; + + VkFramebufferCreateInfo l_FramebufferInfo{}; + l_FramebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + l_FramebufferInfo.renderPass = m_RenderPass; + l_FramebufferInfo.attachmentCount = 1; + l_FramebufferInfo.pAttachments = l_Attachments; + l_FramebufferInfo.width = m_SwapchainExtent.width; + l_FramebufferInfo.height = m_SwapchainExtent.height; + l_FramebufferInfo.layers = 1; + + if (vkCreateFramebuffer(m_Device, &l_FramebufferInfo, nullptr, &m_SwapchainFramebuffers[it_Index]) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create framebuffer"); + } + } + } + + void CreateCommandPool() + { + QueueFamilyIndices l_QueueFamilyIndices = FindQueueFamilies(m_PhysicalDevice); + + VkCommandPoolCreateInfo l_PoolInfo{}; + l_PoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + l_PoolInfo.queueFamilyIndex = l_QueueFamilyIndices.m_GraphicsFamily.value(); + l_PoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + if (vkCreateCommandPool(m_Device, &l_PoolInfo, nullptr, &m_CommandPool) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create command pool"); + } + } + + void AllocateCommandBuffers() + { + m_CommandBuffers.resize(m_SwapchainFramebuffers.size()); + + VkCommandBufferAllocateInfo l_AllocInfo{}; + l_AllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + l_AllocInfo.commandPool = m_CommandPool; + l_AllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + l_AllocInfo.commandBufferCount = static_cast(m_CommandBuffers.size()); + + if (vkAllocateCommandBuffers(m_Device, &l_AllocInfo, m_CommandBuffers.data()) != VK_SUCCESS) + { + throw std::runtime_error("Failed to allocate command buffers"); + } + } + + void CreateSyncObjects() + { + VkSemaphoreCreateInfo l_SemaphoreInfo{}; + l_SemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo l_FenceInfo{}; + l_FenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + l_FenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + if (vkCreateSemaphore(m_Device, &l_SemaphoreInfo, nullptr, &m_ImageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(m_Device, &l_SemaphoreInfo, nullptr, &m_RenderFinishedSemaphore) != VK_SUCCESS || + vkCreateFence(m_Device, &l_FenceInfo, nullptr, &m_InFlightFence) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create synchronization objects"); + } + } + + void DrawFrame(const Clay_RenderCommandArray& renderCommands) + { + vkWaitForFences(m_Device, 1, &m_InFlightFence, VK_TRUE, UINT64_MAX); + vkResetFences(m_Device, 1, &m_InFlightFence); + + uint32_t l_ImageIndex = 0; + vkAcquireNextImageKHR(m_Device, m_Swapchain, UINT64_MAX, m_ImageAvailableSemaphore, VK_NULL_HANDLE, &l_ImageIndex); + + vkResetCommandBuffer(m_CommandBuffers[l_ImageIndex], 0); + RecordCommandBuffer(m_CommandBuffers[l_ImageIndex], l_ImageIndex, renderCommands); + + VkSemaphore l_WaitSemaphores[] = { m_ImageAvailableSemaphore }; + VkPipelineStageFlags l_WaitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + VkSemaphore l_SignalSemaphores[] = { m_RenderFinishedSemaphore }; + + VkSubmitInfo l_SubmitInfo{}; + l_SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + l_SubmitInfo.waitSemaphoreCount = 1; + l_SubmitInfo.pWaitSemaphores = l_WaitSemaphores; + l_SubmitInfo.pWaitDstStageMask = l_WaitStages; + l_SubmitInfo.commandBufferCount = 1; + l_SubmitInfo.pCommandBuffers = &m_CommandBuffers[l_ImageIndex]; + l_SubmitInfo.signalSemaphoreCount = 1; + l_SubmitInfo.pSignalSemaphores = l_SignalSemaphores; + + if (vkQueueSubmit(m_GraphicsQueue, 1, &l_SubmitInfo, m_InFlightFence) != VK_SUCCESS) + { + throw std::runtime_error("Failed to submit draw command buffer"); + } + + VkPresentInfoKHR l_PresentInfo{}; + l_PresentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + l_PresentInfo.waitSemaphoreCount = 1; + l_PresentInfo.pWaitSemaphores = l_SignalSemaphores; + l_PresentInfo.swapchainCount = 1; + l_PresentInfo.pSwapchains = &m_Swapchain; + l_PresentInfo.pImageIndices = &l_ImageIndex; + + vkQueuePresentKHR(m_PresentQueue, &l_PresentInfo); + } + + void RecordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, const Clay_RenderCommandArray& renderCommands) + { + VkCommandBufferBeginInfo l_BeginInfo{}; + l_BeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &l_BeginInfo) != VK_SUCCESS) + { + throw std::runtime_error("Failed to begin recording command buffer"); + } + + VkClearValue l_ClearColor{}; + l_ClearColor.color = { { 0.1f, 0.1f, 0.12f, 1.0f } }; + + VkRenderPassBeginInfo l_RenderPassInfo{}; + l_RenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + l_RenderPassInfo.renderPass = m_RenderPass; + l_RenderPassInfo.framebuffer = m_SwapchainFramebuffers[imageIndex]; + l_RenderPassInfo.renderArea.offset = { 0, 0 }; + l_RenderPassInfo.renderArea.extent = m_SwapchainExtent; + l_RenderPassInfo.clearValueCount = 1; + l_RenderPassInfo.pClearValues = &l_ClearColor; + + vkCmdBeginRenderPass(commandBuffer, &l_RenderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + // Clay renderer translates render commands into Vulkan draw calls. Pipelines are left as TODOs. + Clay_Vulkan_Render(&m_ClayRenderer, renderCommands, commandBuffer); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) + { + throw std::runtime_error("Failed to record command buffer"); + } + } + + Clay_RenderCommandArray CreateLayout() + { + Clay_BeginLayout(); + + Clay_Sizing l_Expand = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }; + + CLAY(CLAY_ID("Root"), { + .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = l_Expand, .childGap = 12, .padding = CLAY_PADDING_ALL(12) }, .backgroundColor = (Clay_Color){ 18, 18, 20, 255 } + }) + { + CLAY(CLAY_ID("Header"), { + .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(64) }, .childAlignment = CLAY_ALIGN_CENTER + }, .backgroundColor = (Clay_Color){ 32, 64, 96, 255 }, .text = { .text = CLAY_STRING("Clay Vulkan Demo"), + .fontId = 0, .fontSize = 28, .textColor = (Clay_Color){ 245, 245, 245, 255 } } + }); + + CLAY(CLAY_ID("Body"), { + .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT, .sizing = l_Expand, .childGap = 10 } + }) + { + CLAY(CLAY_ID("Sidebar"), { + .layout = { .sizing = {.width = CLAY_SIZING_FIXED(240), .height = CLAY_SIZING_GROW(0) }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 }, .backgroundColor = (Clay_Color){ 40, 40, 44, 255 }, .padding = CLAY_PADDING_ALL(10) + }) + { + CLAY(CLAY_ID("Stats"), { .layout = {.sizing = l_Expand }, + .text = { .text = CLAY_STRING("Swapchain + Clay wiring ready.\nTODO: upload fonts and textures."), + .fontId = 0, .fontSize = 20, .textColor = (Clay_Color){ 200, 220, 255, 255 } } + }); + } + + CLAY(CLAY_ID("Viewport"), { .layout = {.sizing = l_Expand }, .backgroundColor = (Clay_Color){ 28, 28, 32, 255 } }) + { + CLAY(CLAY_ID("Content"), { .layout = {.sizing = l_Expand }, + .text = { .text = CLAY_STRING("Render scene would appear here."), + .fontId = 0, .fontSize = 22, .textColor = (Clay_Color){ 240, 240, 240, 255 } } + }); + } + } + } + + return Clay_EndLayout(); + } + + QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device) + { + QueueFamilyIndices l_Indices{}; + + uint32_t l_QueueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &l_QueueFamilyCount, nullptr); + + std::vector l_QueueFamilies(l_QueueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &l_QueueFamilyCount, l_QueueFamilies.data()); + + uint32_t it_Index = 0; + for (const VkQueueFamilyProperties& it_QueueFamily : l_QueueFamilies) + { + if (it_QueueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + l_Indices.m_GraphicsFamily = it_Index; + } + + VkBool32 l_PresentSupport = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(device, it_Index, m_Surface, &l_PresentSupport); + if (l_PresentSupport) + { + l_Indices.m_PresentFamily = it_Index; + } + + if (l_Indices.IsComplete()) + { + break; + } + ++it_Index; + } + + return l_Indices; + } + + bool IsDeviceSuitable(VkPhysicalDevice device) + { + QueueFamilyIndices l_Indices = FindQueueFamilies(device); + + bool l_ExtensionsSupported = CheckDeviceExtensionSupport(device); + bool l_SwapchainAdequate = false; + if (l_ExtensionsSupported) + { + SwapchainSupportDetails l_SwapchainSupport = QuerySwapchainSupport(device); + l_SwapchainAdequate = !l_SwapchainSupport.m_Formats.empty() && !l_SwapchainSupport.m_PresentModes.empty(); + } + + return l_Indices.IsComplete() && l_ExtensionsSupported && l_SwapchainAdequate; + } + + bool CheckDeviceExtensionSupport(VkPhysicalDevice device) + { + uint32_t l_ExtensionCount = 0; + vkEnumerateDeviceExtensionProperties(device, nullptr, &l_ExtensionCount, nullptr); + + std::vector l_AvailableExtensions(l_ExtensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &l_ExtensionCount, l_AvailableExtensions.data()); + + std::set l_RequiredExtensions(s_DeviceExtensions.begin(), s_DeviceExtensions.end()); + + for (const VkExtensionProperties& it_Extension : l_AvailableExtensions) + { + l_RequiredExtensions.erase(it_Extension.extensionName); + } + + return l_RequiredExtensions.empty(); + } + + SwapchainSupportDetails QuerySwapchainSupport(VkPhysicalDevice device) + { + SwapchainSupportDetails l_Details{}; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, m_Surface, &l_Details.m_Capabilities); + + uint32_t l_FormatCount = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_Surface, &l_FormatCount, nullptr); + if (l_FormatCount != 0) + { + l_Details.m_Formats.resize(l_FormatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_Surface, &l_FormatCount, l_Details.m_Formats.data()); + } + + uint32_t l_PresentModeCount = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_Surface, &l_PresentModeCount, nullptr); + if (l_PresentModeCount != 0) + { + l_Details.m_PresentModes.resize(l_PresentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_Surface, &l_PresentModeCount, l_Details.m_PresentModes.data()); + } + + return l_Details; + } + + VkSurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector& availableFormats) + { + for (const VkSurfaceFormatKHR& it_AvailableFormat : availableFormats) + { + if (it_AvailableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && it_AvailableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + { + return it_AvailableFormat; + } + } + + return availableFormats.front(); + } + + VkPresentModeKHR ChoosePresentMode(const std::vector& availablePresentModes) + { + for (VkPresentModeKHR it_PresentMode : availablePresentModes) + { + if (it_PresentMode == VK_PRESENT_MODE_MAILBOX_KHR) + { + return it_PresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) + { + if (capabilities.currentExtent.width != std::numeric_limits::max()) + { + return capabilities.currentExtent; + } + + VkExtent2D l_ActualExtent = { s_DefaultWidth, s_DefaultHeight }; + l_ActualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, l_ActualExtent.width)); + l_ActualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, l_ActualExtent.height)); + + return l_ActualExtent; + } +}; + +int main() +{ + VulkanDemoApp l_App{}; + + l_App.Run(); + + return 0; +} \ No newline at end of file From b0b336f98790b0697901fcd73dbc690b900c0539 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Mon, 17 Nov 2025 23:31:38 +0000 Subject: [PATCH 4/8] Updated the CMakeLists.txt --- CMakeLists.txt | 10 ++++++++++ examples/vulkan-demo/CMakeLists.txt | 23 +++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 191b2f7..d23b65e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,13 @@ option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) +# Enable Vulkan demo by default when the Windows generator can see a Vulkan SDK installation. +set(CLAY_INCLUDE_VULKAN_DEMO_DEFAULT OFF) +if(WIN32 AND DEFINED ENV{VULKAN_SDK}) + set(CLAY_INCLUDE_VULKAN_DEMO_DEFAULT ON) +endif() +option(CLAY_INCLUDE_VULKAN_DEMO "Build Vulkan demo (requires Vulkan SDK)" ${CLAY_INCLUDE_VULKAN_DEMO_DEFAULT}) + message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}") if(APPLE) @@ -44,6 +51,9 @@ if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES) add_subdirectory("examples/sokol-video-demo") add_subdirectory("examples/sokol-corner-radius") endif() +if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_VULAKN_EXAMPLES) + add_subdirectory("examples/vulkan-demo") +endif() # Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement if(CLAY_INCLUDE_PLAYDATE_EXAMPLES) diff --git a/examples/vulkan-demo/CMakeLists.txt b/examples/vulkan-demo/CMakeLists.txt index d2cfd90..192fac5 100644 --- a/examples/vulkan-demo/CMakeLists.txt +++ b/examples/vulkan-demo/CMakeLists.txt @@ -8,14 +8,21 @@ include(FetchContent) set(FETCHCONTENT_QUIET FALSE) # GLFW is used only for window creation and Vulkan surface management in this demo. -FetchContent_Declare( - glfw - GIT_REPOSITORY "https://github.com/glfw/glfw.git" - GIT_TAG "3.4" - GIT_PROGRESS TRUE - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(glfw) +# When other examples are enabled (e.g., raylib), GLFW may already be present via +# their dependencies. Guarding the population prevents duplicate target errors +# (CMP0002) while still reusing the existing GLFW target when available. +if(NOT TARGET glfw) + FetchContent_Declare( + glfw + GIT_REPOSITORY "https://github.com/glfw/glfw.git" + GIT_TAG "3.4" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(glfw) +else() + message(STATUS "Reusing existing GLFW target for Vulkan demo") +endif() find_package(Vulkan REQUIRED) From 7f8a1128e3b4bf11509f3dd8872b67ee066c76b7 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Mon, 17 Nov 2025 23:45:24 +0000 Subject: [PATCH 5/8] Project is building now --- examples/vulkan-demo/main.cpp | 113 +++++++++++++++++++----- renderers/vulkan/clay_renderer_vulkan.c | 7 +- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/examples/vulkan-demo/main.cpp b/examples/vulkan-demo/main.cpp index bda7b98..3d9b6e9 100644 --- a/examples/vulkan-demo/main.cpp +++ b/examples/vulkan-demo/main.cpp @@ -126,7 +126,13 @@ private: { uint64_t l_TotalMemorySize = Clay_MinMemorySize(); m_ClayArena = Clay_CreateArenaWithCapacityAndMemory(l_TotalMemorySize, malloc(l_TotalMemorySize)); - Clay_Initialize(m_ClayArena, (Clay_Dimensions) { static_cast(m_SwapchainExtent.width), static_cast(m_SwapchainExtent.height) }, (Clay_ErrorHandler) { 0 }); + + // Build the layout dimensions explicitly to keep MSVC happy with aggregate initialization rules. + Clay_Dimensions l_LayoutDimensions{}; + l_LayoutDimensions.width = static_cast(m_SwapchainExtent.width); + l_LayoutDimensions.height = static_cast(m_SwapchainExtent.height); + + Clay_Initialize(m_ClayArena, l_LayoutDimensions, Clay_ErrorHandler{ 0 }); Clay_VulkanRenderer_Init(&m_ClayRenderer, m_Device, m_RenderPass, VK_NULL_HANDLE); Clay_VulkanRenderer_SetPipelines(&m_ClayRenderer, VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE); @@ -145,7 +151,11 @@ private: glfwGetFramebufferSize(m_Window, &l_Width, &l_Height); m_SwapchainExtent.width = static_cast(l_Width); m_SwapchainExtent.height = static_cast(l_Height); - Clay_SetLayoutDimensions((Clay_Dimensions) { static_cast(l_Width), static_cast(l_Height) }); + + Clay_Dimensions l_LayoutDimensions{}; + l_LayoutDimensions.width = static_cast(l_Width); + l_LayoutDimensions.height = static_cast(l_Height); + Clay_SetLayoutDimensions(l_LayoutDimensions); Clay_RenderCommandArray l_Commands = CreateLayout(); DrawFrame(l_Commands); @@ -181,7 +191,8 @@ private: glfwDestroyWindow(m_Window); glfwTerminate(); - Clay_FreeArena(m_ClayArena); + // Clay_Arena owns memory allocated with malloc above; release the backing allocation explicitly. + free(m_ClayArena.memory); } void CreateInstance() @@ -552,40 +563,98 @@ private: { Clay_BeginLayout(); - Clay_Sizing l_Expand = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }; + // Shared sizing config to expand containers along both axes. + Clay_Sizing l_Expand{}; + l_Expand.width = CLAY_SIZING_GROW(0); + l_Expand.height = CLAY_SIZING_GROW(0); + // Root container stacks children vertically and adds generous padding. CLAY(CLAY_ID("Root"), { - .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, - .sizing = l_Expand, .childGap = 12, .padding = CLAY_PADDING_ALL(12) }, .backgroundColor = (Clay_Color){ 18, 18, 20, 255 } + .layout = { + .sizing = l_Expand, + .padding = CLAY_PADDING_ALL(12), + .childGap = 12, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = { 18, 18, 20, 255 } }) { + // Header keeps children centered and reserves a fixed height. CLAY(CLAY_ID("Header"), { - .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(64) }, .childAlignment = CLAY_ALIGN_CENTER - }, .backgroundColor = (Clay_Color){ 32, 64, 96, 255 }, .text = { .text = CLAY_STRING("Clay Vulkan Demo"), - .fontId = 0, .fontSize = 28, .textColor = (Clay_Color){ 245, 245, 245, 255 } } - }); - - CLAY(CLAY_ID("Body"), { - .layout = { .layoutDirection = CLAY_LEFT_TO_RIGHT, .sizing = l_Expand, .childGap = 10 } + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(64) }, + .padding = CLAY_PADDING_ALL(0), + .childGap = 0, + .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .backgroundColor = { 32, 64, 96, 255 } }) { + // Keep designated fields in declaration order for MSVC aggregate compatibility. + Clay_TextElementConfig* l_HeaderTextConfig = CLAY_TEXT_CONFIG({ + .userData = nullptr, + .textColor = { 245, 245, 245, 255 }, + .fontId = 0, + .fontSize = 28 + }); + CLAY_TEXT(CLAY_STRING("Clay Vulkan Demo"), l_HeaderTextConfig); + } + + // Body splits the space horizontally into a sidebar and viewport. + CLAY(CLAY_ID("Body"), { + .layout = { + .sizing = l_Expand, + .padding = CLAY_PADDING_ALL(0), + .childGap = 10, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) + { + // Sidebar lists current wiring status. CLAY(CLAY_ID("Sidebar"), { - .layout = { .sizing = {.width = CLAY_SIZING_FIXED(240), .height = CLAY_SIZING_GROW(0) }, - .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 }, .backgroundColor = (Clay_Color){ 40, 40, 44, 255 }, .padding = CLAY_PADDING_ALL(10) + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(240), .height = CLAY_SIZING_GROW(0) }, + .padding = CLAY_PADDING_ALL(10), + .childGap = 8, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = { 40, 40, 44, 255 } }) { - CLAY(CLAY_ID("Stats"), { .layout = {.sizing = l_Expand }, - .text = { .text = CLAY_STRING("Swapchain + Clay wiring ready.\nTODO: upload fonts and textures."), - .fontId = 0, .fontSize = 20, .textColor = (Clay_Color){ 200, 220, 255, 255 } } + // Keep designated fields in declaration order for MSVC aggregate compatibility. + Clay_TextElementConfig* l_StatusTextConfig = CLAY_TEXT_CONFIG({ + .userData = nullptr, + .textColor = { 200, 220, 255, 255 }, + .fontId = 0, + .fontSize = 20 }); + CLAY_TEXT(CLAY_STRING("Swapchain + Clay wiring ready.\nTODO: upload fonts and textures."), l_StatusTextConfig); } - CLAY(CLAY_ID("Viewport"), { .layout = {.sizing = l_Expand }, .backgroundColor = (Clay_Color){ 28, 28, 32, 255 } }) + // Viewport stands in for rendered content. + CLAY(CLAY_ID("Viewport"), { + .layout = { + .sizing = l_Expand, + .padding = CLAY_PADDING_ALL(0), + .childGap = 0, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = { 28, 28, 32, 255 } + }) { - CLAY(CLAY_ID("Content"), { .layout = {.sizing = l_Expand }, - .text = { .text = CLAY_STRING("Render scene would appear here."), - .fontId = 0, .fontSize = 22, .textColor = (Clay_Color){ 240, 240, 240, 255 } } + // Keep designated fields in declaration order for MSVC aggregate compatibility. + Clay_TextElementConfig* l_ContentTextConfig = CLAY_TEXT_CONFIG({ + .userData = nullptr, + .textColor = { 240, 240, 240, 255 }, + .fontId = 0, + .fontSize = 22 }); + CLAY_TEXT(CLAY_STRING("Render scene would appear here."), l_ContentTextConfig); } } } diff --git a/renderers/vulkan/clay_renderer_vulkan.c b/renderers/vulkan/clay_renderer_vulkan.c index 7d2cbf3..6e55b45 100644 --- a/renderers/vulkan/clay_renderer_vulkan.c +++ b/renderers/vulkan/clay_renderer_vulkan.c @@ -117,7 +117,12 @@ static Clay_Dimensions ClayVulkan_MeasureText(Clay_StringSlice text, Clay_TextEl float l_Height = (float)config->lineHeight; (void)l_Font; // In a real integration, sample glyph advances from l_Font->m_FontUserData. - return (Clay_Dimensions) { l_Width, l_Height }; + // MSVC doesn't support C99 compound literals in C mode; build the return value explicitly for portability. + Clay_Dimensions l_Dimensions = { 0 }; + l_Dimensions.width = l_Width; + l_Dimensions.height = l_Height; + + return l_Dimensions; } // Scissor management --------------------------------------------------------- From 24d64f196f5cb977dad223a502256301f3467606 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Mon, 17 Nov 2025 23:56:39 +0000 Subject: [PATCH 6/8] Milestone reached: Main loop is working, but no UI is in place, that's the next goal --- examples/vulkan-demo/main.cpp | 350 +++++++++++++++++++++++- renderers/vulkan/clay_renderer_vulkan.c | 6 +- 2 files changed, 347 insertions(+), 9 deletions(-) diff --git a/examples/vulkan-demo/main.cpp b/examples/vulkan-demo/main.cpp index 3d9b6e9..ea91da9 100644 --- a/examples/vulkan-demo/main.cpp +++ b/examples/vulkan-demo/main.cpp @@ -17,9 +17,10 @@ #include #define CLAY_IMPLEMENTATION -extern "C" { -#include "../../clay.h" -#include "../../renderers/vulkan/clay_renderer_vulkan.c" +extern "C" +{ + #include "../../clay.h" + #include "../../renderers/vulkan/clay_renderer_vulkan.c" } namespace @@ -87,6 +88,12 @@ private: VkSemaphore m_ImageAvailableSemaphore = VK_NULL_HANDLE; VkSemaphore m_RenderFinishedSemaphore = VK_NULL_HANDLE; VkFence m_InFlightFence = VK_NULL_HANDLE; + VkDescriptorSetLayout m_DescriptorSetLayout = VK_NULL_HANDLE; + VkPipelineLayout m_PipelineLayout = VK_NULL_HANDLE; + VkPipeline m_RectPipeline = VK_NULL_HANDLE; + VkPipeline m_TextPipeline = VK_NULL_HANDLE; + VkPipeline m_ImagePipeline = VK_NULL_HANDLE; + VkDescriptorPool m_DescriptorPool = VK_NULL_HANDLE; // Clay renderer bindings ClayVulkanRenderer m_ClayRenderer{}; @@ -116,6 +123,9 @@ private: CreateSwapchain(); CreateImageViews(); CreateRenderPass(); + CreateDescriptorSetLayout(); + CreatePipelineLayout(); + CreatePipelines(); CreateFramebuffers(); CreateCommandPool(); AllocateCommandBuffers(); @@ -134,9 +144,9 @@ private: Clay_Initialize(m_ClayArena, l_LayoutDimensions, Clay_ErrorHandler{ 0 }); - Clay_VulkanRenderer_Init(&m_ClayRenderer, m_Device, m_RenderPass, VK_NULL_HANDLE); - Clay_VulkanRenderer_SetPipelines(&m_ClayRenderer, VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE); - // TODO: Plug descriptor pool + layout management for textures/fonts instead of placeholder pipeline handles. + Clay_VulkanRenderer_Init(&m_ClayRenderer, m_Device, m_RenderPass, m_PipelineLayout); + Clay_VulkanRenderer_SetPipelines(&m_ClayRenderer, m_RectPipeline, m_TextPipeline, m_ImagePipeline); + // Descriptor pool and layouts have been created; future improvements can allocate texture/font descriptors per frame. Clay_VulkanRenderer_RegisterTextMeasure(&m_ClayRenderer); } @@ -172,6 +182,31 @@ private: vkDestroySemaphore(m_Device, m_RenderFinishedSemaphore, nullptr); vkDestroySemaphore(m_Device, m_ImageAvailableSemaphore, nullptr); + if (m_RectPipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(m_Device, m_RectPipeline, nullptr); + } + if (m_TextPipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(m_Device, m_TextPipeline, nullptr); + } + if (m_ImagePipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(m_Device, m_ImagePipeline, nullptr); + } + if (m_PipelineLayout != VK_NULL_HANDLE) + { + vkDestroyPipelineLayout(m_Device, m_PipelineLayout, nullptr); + } + if (m_DescriptorSetLayout != VK_NULL_HANDLE) + { + vkDestroyDescriptorSetLayout(m_Device, m_DescriptorSetLayout, nullptr); + } + if (m_DescriptorPool != VK_NULL_HANDLE) + { + vkDestroyDescriptorPool(m_Device, m_DescriptorPool, nullptr); + } + for (VkFramebuffer l_Framebuffer : m_SwapchainFramebuffers) { vkDestroyFramebuffer(m_Device, l_Framebuffer, nullptr); @@ -220,6 +255,291 @@ private: } } + VkShaderModule CreateShaderModule(const std::vector& code) + { + VkShaderModuleCreateInfo l_CreateInfo{}; + l_CreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + l_CreateInfo.codeSize = code.size() * sizeof(uint32_t); + l_CreateInfo.pCode = code.data(); + + VkShaderModule l_ShaderModule = VK_NULL_HANDLE; + if (vkCreateShaderModule(m_Device, &l_CreateInfo, nullptr, &l_ShaderModule) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create shader module; see Vulkan SDK validation output."); + } + + return l_ShaderModule; + } + + void CreateDescriptorSetLayout() + { + // Texture sampling uses a single combined image sampler bound at set 0, binding 0. + VkDescriptorSetLayoutBinding l_SamplerBinding{}; + l_SamplerBinding.binding = 0; + l_SamplerBinding.descriptorCount = 1; + l_SamplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + l_SamplerBinding.pImmutableSamplers = nullptr; + l_SamplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutCreateInfo l_LayoutInfo{}; + l_LayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + l_LayoutInfo.bindingCount = 1; + l_LayoutInfo.pBindings = &l_SamplerBinding; + + if (vkCreateDescriptorSetLayout(m_Device, &l_LayoutInfo, nullptr, &m_DescriptorSetLayout) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create descriptor set layout for images"); + } + + // Keep a small descriptor pool ready for sample textures; real integrations should grow this pool. + VkDescriptorPoolSize l_PoolSize{}; + l_PoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + l_PoolSize.descriptorCount = 8; + + VkDescriptorPoolCreateInfo l_PoolInfo{}; + l_PoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + l_PoolInfo.poolSizeCount = 1; + l_PoolInfo.pPoolSizes = &l_PoolSize; + l_PoolInfo.maxSets = 8; + + if (vkCreateDescriptorPool(m_Device, &l_PoolInfo, nullptr, &m_DescriptorPool) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create descriptor pool for textures"); + } + } + + void CreatePipelineLayout() + { + VkPipelineLayoutCreateInfo l_PipelineLayoutInfo{}; + l_PipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + l_PipelineLayoutInfo.setLayoutCount = 1; + l_PipelineLayoutInfo.pSetLayouts = &m_DescriptorSetLayout; + + if (vkCreatePipelineLayout(m_Device, &l_PipelineLayoutInfo, nullptr, &m_PipelineLayout) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create pipeline layout"); + } + } + + void CreatePipelines() + { + // Embedded SPIR-V shaders keep the demo self contained; see Vulkan SDK docs for shader compilation guidance. + static const std::array s_RectVertSpv = { + 0x7230203,0x10000,0x8000b,0x32,0x0,0x20011,0x1,0x6000b, + 0x1,0x4c534c47,0x6474732e,0x3035342e,0x0,0x3000e,0x0,0x1, + 0x8000f,0x0,0x4,0x6e69616d,0x0,0xd,0x1b,0x29, + 0x30003,0x2,0x1c2,0x40005,0x4,0x6e69616d,0x0,0x60005, + 0xb,0x505f6c67,0x65567265,0x78657472,0x0,0x60006,0xb,0x0, + 0x505f6c67,0x7469736f,0x6e6f69,0x70006,0xb,0x1,0x505f6c67,0x746e696f, + 0x657a6953,0x0,0x70006,0xb,0x2,0x435f6c67,0x4470696c,0x61747369, + 0x65636e,0x70006,0xb,0x3,0x435f6c67,0x446c6c75,0x61747369,0x65636e, + 0x30005,0xd,0x0,0x60005,0x1b,0x565f6c67,0x65747265,0x646e4978, + 0x7865,0x50005,0x1e,0x65646e69,0x6c626178,0x65,0x40005,0x29, + 0x5574756f,0x56,0x50005,0x2f,0x65646e69,0x6c626178,0x65,0x30047, + 0xb,0x2,0x50048,0xb,0x0,0xb,0x0,0x50048, + 0xb,0x1,0xb,0x1,0x50048,0xb,0x2,0xb, + 0x3,0x50048,0xb,0x3,0xb,0x4,0x40047,0x1b, + 0xb,0x2a,0x40047,0x29,0x1e,0x0,0x20013,0x2, + 0x30021,0x3,0x2,0x30016,0x6,0x20,0x40017,0x7, + 0x6,0x4,0x40015,0x8,0x20,0x0,0x4002b,0x8, + 0x9,0x1,0x4001c,0xa,0x6,0x9,0x6001e,0xb, + 0x7,0x6,0xa,0xa,0x40020,0xc,0x3,0xb, + 0x4003b,0xc,0xd,0x3,0x40015,0xe,0x20,0x1, + 0x4002b,0xe,0xf,0x0,0x40017,0x10,0x6,0x2, + 0x4002b,0x8,0x11,0x6,0x4001c,0x12,0x10,0x11, + 0x4002b,0x6,0x13,0xbf800000,0x5002c,0x10,0x14,0x13, + 0x13,0x4002b,0x6,0x15,0x3f800000,0x5002c,0x10,0x16, + 0x15,0x13,0x5002c,0x10,0x17,0x15,0x15,0x5002c, + 0x10,0x18,0x13,0x15,0x9002c,0x12,0x19,0x14, + 0x16,0x17,0x14,0x17,0x18,0x40020,0x1a,0x1, + 0xe,0x4003b,0x1a,0x1b,0x1,0x40020,0x1d,0x7, + 0x12,0x40020,0x1f,0x7,0x10,0x4002b,0x6,0x22, + 0x0,0x40020,0x26,0x3,0x7,0x40020,0x28,0x3, + 0x10,0x4003b,0x28,0x29,0x3,0x5002c,0x10,0x2a, + 0x22,0x22,0x5002c,0x10,0x2b,0x15,0x22,0x5002c, + 0x10,0x2c,0x22,0x15,0x9002c,0x12,0x2d,0x2a, + 0x2b,0x17,0x2a,0x17,0x2c,0x50036,0x2,0x4, + 0x0,0x3,0x200f8,0x5,0x4003b,0x1d,0x1e,0x7, + 0x4003b,0x1d,0x2f,0x7,0x4003d,0xe,0x1c,0x1b, + 0x3003e,0x1e,0x19,0x50041,0x1f,0x20,0x1e,0x1c, + 0x4003d,0x10,0x21,0x20,0x50051,0x6,0x23,0x21, + 0x0,0x50051,0x6,0x24,0x21,0x1,0x70050,0x7, + 0x25,0x23,0x24,0x22,0x15,0x50041,0x26,0x27, + 0xd,0xf,0x3003e,0x27,0x25,0x4003d,0xe,0x2e, + 0x1b,0x3003e,0x2f,0x2d,0x50041,0x1f,0x30,0x2f, + 0x2e,0x4003d,0x10,0x31,0x30,0x3003e,0x29,0x31, + 0x100fd,0x10038, + }; + static const std::array s_ColorFragSpv = { + 0x7230203,0x10000,0x8000b,0x13,0x0,0x20011,0x1,0x6000b, + 0x1,0x4c534c47,0x6474732e,0x3035342e,0x0,0x3000e,0x0,0x1, + 0x7000f,0x4,0x4,0x6e69616d,0x0,0x9,0xc,0x30010, + 0x4,0x7,0x30003,0x2,0x1c2,0x40005,0x4,0x6e69616d, + 0x0,0x50005,0x9,0x4374756f,0x726f6c6f,0x0,0x40005,0xc, + 0x56556e69,0x0,0x40047,0x9,0x1e,0x0,0x40047,0xc, + 0x1e,0x0,0x20013,0x2,0x30021,0x3,0x2,0x30016, + 0x6,0x20,0x40017,0x7,0x6,0x4,0x40020,0x8, + 0x3,0x7,0x4003b,0x8,0x9,0x3,0x40017,0xa, + 0x6,0x2,0x40020,0xb,0x1,0xa,0x4003b,0xb, + 0xc,0x1,0x4002b,0x6,0xe,0x3f000000,0x4002b,0x6, + 0xf,0x3f800000,0x50036,0x2,0x4,0x0,0x3,0x200f8, + 0x5,0x4003d,0xa,0xd,0xc,0x50051,0x6,0x10, + 0xd,0x0,0x50051,0x6,0x11,0xd,0x1,0x70050, + 0x7,0x12,0x10,0x11,0xe,0xf,0x3003e,0x9, + 0x12,0x100fd,0x10038, + }; + static const std::array s_ImageFragSpv = { + 0x7230203,0x10000,0x8000b,0x14,0x0,0x20011,0x1,0x6000b, + 0x1,0x4c534c47,0x6474732e,0x3035342e,0x0,0x3000e,0x0,0x1, + 0x7000f,0x4,0x4,0x6e69616d,0x0,0x9,0x11,0x30010, + 0x4,0x7,0x30003,0x2,0x1c2,0x40005,0x4,0x6e69616d, + 0x0,0x50005,0x9,0x4374756f,0x726f6c6f,0x0,0x50005,0xd, + 0x78655475,0x65727574,0x0,0x40005,0x11,0x56556e69,0x0,0x40047, + 0x9,0x1e,0x0,0x40047,0xd,0x21,0x0,0x40047, + 0xd,0x22,0x0,0x40047,0x11,0x1e,0x0,0x20013, + 0x2,0x30021,0x3,0x2,0x30016,0x6,0x20,0x40017, + 0x7,0x6,0x4,0x40020,0x8,0x3,0x7,0x4003b, + 0x8,0x9,0x3,0x90019,0xa,0x6,0x1,0x0, + 0x0,0x0,0x1,0x0,0x3001b,0xb,0xa,0x40020, + 0xc,0x0,0xb,0x4003b,0xc,0xd,0x0,0x40017, + 0xf,0x6,0x2,0x40020,0x10,0x1,0xf,0x4003b, + 0x10,0x11,0x1,0x50036,0x2,0x4,0x0,0x3, + 0x200f8,0x5,0x4003d,0xb,0xe,0xd,0x4003d,0xf, + 0x12,0x11,0x50057,0x7,0x13,0xe,0x12,0x3003e, + 0x9,0x13,0x100fd,0x10038, + }; + + const std::vector l_VertCode(s_RectVertSpv.begin(), s_RectVertSpv.end()); + const std::vector l_ColorFragCode(s_ColorFragSpv.begin(), s_ColorFragSpv.end()); + const std::vector l_ImageFragCode(s_ImageFragSpv.begin(), s_ImageFragSpv.end()); + + VkShaderModule l_VertShaderModule = CreateShaderModule(l_VertCode); + VkShaderModule l_ColorFragShaderModule = CreateShaderModule(l_ColorFragCode); + VkShaderModule l_ImageFragShaderModule = CreateShaderModule(l_ImageFragCode); + + VkPipelineShaderStageCreateInfo l_VertStageInfo{}; + l_VertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + l_VertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + l_VertStageInfo.module = l_VertShaderModule; + l_VertStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo l_ColorFragStage{}; + l_ColorFragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + l_ColorFragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + l_ColorFragStage.module = l_ColorFragShaderModule; + l_ColorFragStage.pName = "main"; + + VkPipelineShaderStageCreateInfo l_ImageFragStage{}; + l_ImageFragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + l_ImageFragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + l_ImageFragStage.module = l_ImageFragShaderModule; + l_ImageFragStage.pName = "main"; + + VkPipelineShaderStageCreateInfo l_ColorStages[] = { l_VertStageInfo, l_ColorFragStage }; + VkPipelineShaderStageCreateInfo l_ImageStages[] = { l_VertStageInfo, l_ImageFragStage }; + + VkPipelineVertexInputStateCreateInfo l_VertexInputInfo{}; + l_VertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + VkPipelineInputAssemblyStateCreateInfo l_InputAssembly{}; + l_InputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + l_InputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + l_InputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport l_Viewport{}; + l_Viewport.x = 0.0f; + l_Viewport.y = 0.0f; + l_Viewport.width = static_cast(m_SwapchainExtent.width); + l_Viewport.height = static_cast(m_SwapchainExtent.height); + l_Viewport.minDepth = 0.0f; + l_Viewport.maxDepth = 1.0f; + + VkRect2D l_Scissor{}; + l_Scissor.offset = { 0, 0 }; + l_Scissor.extent = m_SwapchainExtent; + + VkPipelineViewportStateCreateInfo l_ViewportState{}; + l_ViewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + l_ViewportState.viewportCount = 1; + l_ViewportState.pViewports = &l_Viewport; + l_ViewportState.scissorCount = 1; + l_ViewportState.pScissors = &l_Scissor; + + VkPipelineRasterizationStateCreateInfo l_Rasterizer{}; + l_Rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + l_Rasterizer.depthClampEnable = VK_FALSE; + l_Rasterizer.rasterizerDiscardEnable = VK_FALSE; + l_Rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + l_Rasterizer.lineWidth = 1.0f; + l_Rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + l_Rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + l_Rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo l_Multisampling{}; + l_Multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + l_Multisampling.sampleShadingEnable = VK_FALSE; + l_Multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState l_ColorBlendAttachment{}; + l_ColorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + l_ColorBlendAttachment.blendEnable = VK_TRUE; + l_ColorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + l_ColorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + l_ColorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + l_ColorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + l_ColorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + l_ColorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo l_ColorBlending{}; + l_ColorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + l_ColorBlending.logicOpEnable = VK_FALSE; + l_ColorBlending.attachmentCount = 1; + l_ColorBlending.pAttachments = &l_ColorBlendAttachment; + + std::vector l_DynamicStates = { VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT }; + VkPipelineDynamicStateCreateInfo l_DynamicState{}; + l_DynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + l_DynamicState.dynamicStateCount = static_cast(l_DynamicStates.size()); + l_DynamicState.pDynamicStates = l_DynamicStates.data(); + + VkGraphicsPipelineCreateInfo l_PipelineInfo{}; + l_PipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + l_PipelineInfo.pVertexInputState = &l_VertexInputInfo; + l_PipelineInfo.pInputAssemblyState = &l_InputAssembly; + l_PipelineInfo.pViewportState = &l_ViewportState; + l_PipelineInfo.pRasterizationState = &l_Rasterizer; + l_PipelineInfo.pMultisampleState = &l_Multisampling; + l_PipelineInfo.pColorBlendState = &l_ColorBlending; + l_PipelineInfo.pDynamicState = &l_DynamicState; + l_PipelineInfo.layout = m_PipelineLayout; + l_PipelineInfo.renderPass = m_RenderPass; + l_PipelineInfo.subpass = 0; + + l_PipelineInfo.stageCount = 2; + l_PipelineInfo.pStages = l_ColorStages; + if (vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &l_PipelineInfo, nullptr, &m_RectPipeline) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create rectangle pipeline"); + } + + l_PipelineInfo.pStages = l_ImageStages; + if (vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &l_PipelineInfo, nullptr, &m_ImagePipeline) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create image pipeline"); + } + + // Text rendering can reuse the color fragment shader for now; future work can swap in a signed-distance-field shader. + l_PipelineInfo.pStages = l_ColorStages; + if (vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &l_PipelineInfo, nullptr, &m_TextPipeline) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create text pipeline"); + } + + vkDestroyShaderModule(m_Device, l_VertShaderModule, nullptr); + vkDestroyShaderModule(m_Device, l_ColorFragShaderModule, nullptr); + vkDestroyShaderModule(m_Device, l_ImageFragShaderModule, nullptr); + } + void CreateSurface() { if (glfwCreateWindowSurface(m_Instance, m_Window, nullptr, &m_Surface) != VK_SUCCESS) @@ -546,9 +866,25 @@ private: l_RenderPassInfo.clearValueCount = 1; l_RenderPassInfo.pClearValues = &l_ClearColor; + VkViewport l_Viewport{}; + l_Viewport.x = 0.0f; + l_Viewport.y = 0.0f; + l_Viewport.width = static_cast(m_SwapchainExtent.width); + l_Viewport.height = static_cast(m_SwapchainExtent.height); + l_Viewport.minDepth = 0.0f; + l_Viewport.maxDepth = 1.0f; + + VkRect2D l_Scissor{}; + l_Scissor.offset = { 0, 0 }; + l_Scissor.extent = m_SwapchainExtent; + vkCmdBeginRenderPass(commandBuffer, &l_RenderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - // Clay renderer translates render commands into Vulkan draw calls. Pipelines are left as TODOs. + // Set dynamic state expected by the pipelines before Clay emits draw calls. + vkCmdSetViewport(commandBuffer, 0, 1, &l_Viewport); + vkCmdSetScissor(commandBuffer, 0, 1, &l_Scissor); + + // Clay renderer translates render commands into Vulkan draw calls using the pipelines prepared during initialization. Clay_Vulkan_Render(&m_ClayRenderer, renderCommands, commandBuffer); vkCmdEndRenderPass(commandBuffer); diff --git a/renderers/vulkan/clay_renderer_vulkan.c b/renderers/vulkan/clay_renderer_vulkan.c index 6e55b45..9062c20 100644 --- a/renderers/vulkan/clay_renderer_vulkan.c +++ b/renderers/vulkan/clay_renderer_vulkan.c @@ -179,8 +179,10 @@ static void ClayVulkan_DrawBorder(ClayVulkanRenderer* renderer, const Clay_Rende static void ClayVulkan_DrawImage(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer) { ClayVulkanTexture* l_Texture = ClayVulkan_GetTexture(&renderer->m_ResourceCache, command->id); - if (!l_Texture || !l_Texture->m_DescriptorSet) { - return; // Caller must populate descriptor sets before rendering. + if (!l_Texture || !l_Texture->m_DescriptorSet || !l_Texture->m_ImageView || !l_Texture->m_Sampler) + { + // Ensure descriptor sets are prepared before binding; application is responsible for keeping resources live. + return; } vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_ImagePipeline); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_PipelineLayout, 0, 1, &l_Texture->m_DescriptorSet, 0, NULL); From 556e6b54f245f05ba4128ab84b0689418a337712 Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Tue, 18 Nov 2025 00:20:05 +0000 Subject: [PATCH 7/8] [Renderers/vulkan] Minor changes, updating CMakeLists.txt, project is not working --- CMakeLists.txt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d23b65e..4b8fd13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(clay) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON) +option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" OFF) option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF) option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF) option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF) @@ -12,13 +12,7 @@ option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF) option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) - -# Enable Vulkan demo by default when the Windows generator can see a Vulkan SDK installation. -set(CLAY_INCLUDE_VULKAN_DEMO_DEFAULT OFF) -if(WIN32 AND DEFINED ENV{VULKAN_SDK}) - set(CLAY_INCLUDE_VULKAN_DEMO_DEFAULT ON) -endif() -option(CLAY_INCLUDE_VULKAN_DEMO "Build Vulkan demo (requires Vulkan SDK)" ${CLAY_INCLUDE_VULKAN_DEMO_DEFAULT}) +option(CLAY_INCLUDE_VULKAN_DEMO "Build Vulkan examples" ON) message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}") From 0e9da36bdac9a5e5ef168615242c94c2a3fa43af Mon Sep 17 00:00:00 2001 From: ThatTanishqTak Date: Tue, 18 Nov 2025 22:09:54 +0000 Subject: [PATCH 8/8] [Renderers/vulkan] application is working now, start work on the actual demo --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b8fd13..7b188dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.27) +cmake_minimum_required(VERSION 3.25) project(clay) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") @@ -12,7 +12,7 @@ option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF) option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) -option(CLAY_INCLUDE_VULKAN_DEMO "Build Vulkan examples" ON) +option(CLAY_INCLUDE_VULAKN_EXAMPLES "Build Vulkan examples" ON) message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")