diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index bae0d0a..a61f32b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,10 +1,13 @@ set(CMAKE_CXX_STANDARD 17) -# Enable SDL Vulkan integration -set(PLUME_SDL_VULKAN_ENABLED ON CACHE BOOL "Enable SDL Vulkan integration" FORCE) +# Enable SDL Vulkan integration (Linux only) +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + set(PLUME_SDL_VULKAN_ENABLED ON CACHE BOOL "Enable SDL Vulkan integration" FORCE) +endif() -# Find SDL2 (required for examples) -find_package(SDL2 REQUIRED) +# Find or fetch SDL2 (required for examples) +include(${CMAKE_SOURCE_DIR}/examples/cmake/modules/PlumeSDL2.cmake) +plume_find_sdl2() # Initialize shader compilation include(${CMAKE_SOURCE_DIR}/examples/cmake/PlumeShaders.cmake) diff --git a/examples/cmake/modules/PlumeSDL2.cmake b/examples/cmake/modules/PlumeSDL2.cmake new file mode 100644 index 0000000..a5d9a69 --- /dev/null +++ b/examples/cmake/modules/PlumeSDL2.cmake @@ -0,0 +1,91 @@ +# PlumeSDL2.cmake +# Finds or fetches SDL2 for Plume examples + +include(FetchContent) + +set(PLUME_SDL2_VERSION "2.30.10" CACHE STRING "SDL2 version for auto-download on Windows") + +function(plume_find_sdl2) + if(PLUME_SDL2_FOUND) + return() + endif() + + if(NOT DEFINED PLUME_SDL2_DIR AND DEFINED ENV{PLUME_SDL2_DIR}) + set(PLUME_SDL2_DIR "$ENV{PLUME_SDL2_DIR}") + endif() + + # If PLUME_SDL2_DIR is explicitly provided, use it directly + # Expected layout: PLUME_SDL2_DIR/include/SDL.h, PLUME_SDL2_DIR/lib/x64/SDL2.lib + if(DEFINED PLUME_SDL2_DIR AND EXISTS "${PLUME_SDL2_DIR}/include") + message(STATUS "Plume - Using SDL2 from PLUME_SDL2_DIR: ${PLUME_SDL2_DIR}") + _plume_sdl2_setup_from_dir("${PLUME_SDL2_DIR}") + return() + endif() + + # Try find_package (vcpkg, Homebrew, apt, etc.) + find_package(SDL2 QUIET) + + if(SDL2_FOUND OR TARGET SDL2::SDL2) + message(STATUS "Plume - Found SDL2 via find_package") + + if(TARGET SDL2::SDL2) + if(TARGET SDL2::SDL2main) + set(SDL2_LIBRARIES SDL2::SDL2 SDL2::SDL2main CACHE INTERNAL "") + else() + set(SDL2_LIBRARIES SDL2::SDL2 CACHE INTERNAL "") + endif() + else() + set(SDL2_LIBRARIES "${SDL2_LIBRARIES}" CACHE INTERNAL "") + endif() + + if(DEFINED SDL2_INCLUDE_DIRS) + set(SDL2_INCLUDE_DIRS "${SDL2_INCLUDE_DIRS}" CACHE INTERNAL "") + endif() + + set(PLUME_SDL2_FOUND TRUE CACHE INTERNAL "") + return() + endif() + + # Auto-download SDL2 development libraries on Windows + if(WIN32) + message(STATUS "Plume - SDL2 not found, downloading v${PLUME_SDL2_VERSION}...") + + if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) + endif() + + FetchContent_Declare( + plume_sdl2 + URL "https://github.com/libsdl-org/SDL/releases/download/release-${PLUME_SDL2_VERSION}/SDL2-devel-${PLUME_SDL2_VERSION}-VC.zip" + ) + FetchContent_MakeAvailable(plume_sdl2) + + set(_sdl2_src "${plume_sdl2_SOURCE_DIR}") + if(NOT EXISTS "${_sdl2_src}/include" AND EXISTS "${_sdl2_src}/SDL2-${PLUME_SDL2_VERSION}/include") + set(_sdl2_src "${_sdl2_src}/SDL2-${PLUME_SDL2_VERSION}") + endif() + + _plume_sdl2_setup_from_dir("${_sdl2_src}") + return() + endif() + + message(FATAL_ERROR + "SDL2 not found. Please install SDL2 or pass -DPLUME_SDL2_DIR=" + ) +endfunction() + +# Set up SDL2 paths from a directory with include/ and lib// subdirectories +macro(_plume_sdl2_setup_from_dir _sdl2_root) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_plume_sdl2_arch "x64") + else() + set(_plume_sdl2_arch "x86") + endif() + + set(SDL2_INCLUDE_DIRS "${_sdl2_root}/include" CACHE INTERNAL "") + set(SDL2_BINDIR "${_sdl2_root}/lib/${_plume_sdl2_arch}" CACHE INTERNAL "") + set(SDL2_LIBRARIES "${SDL2_BINDIR}/SDL2.lib;${SDL2_BINDIR}/SDL2main.lib" CACHE INTERNAL "") + set(PLUME_SDL2_FOUND TRUE CACHE INTERNAL "") + + unset(_plume_sdl2_arch) +endmacro() diff --git a/examples/cube/main.cpp b/examples/cube/main.cpp index 3670358..9668fe5 100644 --- a/examples/cube/main.cpp +++ b/examples/cube/main.cpp @@ -507,36 +507,28 @@ namespace plume { ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); } - void CubeExample(RenderInterface* renderInterface, const std::string& apiName) { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); - return; - } - - uint32_t flags = SDL_WINDOW_RESIZABLE; -#if defined(__APPLE__) - flags |= SDL_WINDOW_METAL; -#endif - + void CubeExample(RenderInterface* renderInterface, SDL_Window* window, const std::string& apiName) { std::string windowTitle = "Plume Cube Texture Example (" + apiName + ")"; - SDL_Window* window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); - if (!window) { - fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); - SDL_Quit(); - return; - } + SDL_SetWindowTitle(window, windowTitle.c_str()); + CubeContext ctx; +#if PLUME_SDL_VULKAN_ENABLED + createContext(ctx, renderInterface, window, apiName); +#elif defined(__linux__) SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); - - CubeContext ctx; -#if defined(__linux__) createContext(ctx, renderInterface, { wmInfo.info.x11.display, wmInfo.info.x11.window }, apiName); #elif defined(__APPLE__) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); SDL_MetalView view = SDL_Metal_CreateView(window); createContext(ctx, renderInterface, { wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) }, apiName); #elif defined(WIN32) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); createContext(ctx, renderInterface, { wmInfo.info.win.window }, apiName); #endif @@ -577,17 +569,19 @@ namespace plume { #if defined(__APPLE__) SDL_Metal_DestroyView(view); #endif - SDL_DestroyWindow(window); - SDL_Quit(); } } -std::unique_ptr CreateRenderInterface(std::string& apiName) { +std::unique_ptr CreateRenderInterface(SDL_Window* window, std::string& apiName) { const bool useVulkan = false; #if defined(_WIN32) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "D3D12"; return plume::CreateD3D12Interface(); @@ -595,20 +589,57 @@ std::unique_ptr CreateRenderInterface(std::string& apiNa #elif defined(__APPLE__) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "Metal"; return plume::CreateMetalInterface(); } #else apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); #endif +#endif } int main(int argc, char* argv[]) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); + return 1; + } + + uint32_t flags = SDL_WINDOW_RESIZABLE; +#if PLUME_SDL_VULKAN_ENABLED + flags |= SDL_WINDOW_VULKAN; +#elif defined(__APPLE__) + flags |= SDL_WINDOW_METAL; +#endif + + SDL_Window* window = SDL_CreateWindow("Plume Cube Texture Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); + if (!window) { + fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + std::string apiName = "Unknown"; - auto renderInterface = CreateRenderInterface(apiName); - plume::CubeExample(renderInterface.get(), apiName); + auto renderInterface = CreateRenderInterface(window, apiName); + if (!renderInterface) { + fprintf(stderr, "Failed to create render interface\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + plume::CubeExample(renderInterface.get(), window, apiName); + + SDL_DestroyWindow(window); + SDL_Quit(); return 0; } diff --git a/examples/cube/shaders/cube.frag.hlsl b/examples/cube/shaders/cube.frag.hlsl index c66f3db..182e489 100644 --- a/examples/cube/shaders/cube.frag.hlsl +++ b/examples/cube/shaders/cube.frag.hlsl @@ -2,7 +2,7 @@ // Samples from a cubemap texture [[vk::binding(0, 0)]] TextureCube cubeTexture : register(t0); -[[vk::binding(1, 0)]] SamplerState cubeSampler : register(s0); +[[vk::binding(1, 0)]] SamplerState cubeSampler : register(s1); struct PSInput { float4 position : SV_POSITION; diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp index 090c4ba..fc34404 100644 --- a/examples/triangle/main.cpp +++ b/examples/triangle/main.cpp @@ -293,36 +293,28 @@ namespace plume { ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); } - void RenderInterfaceTest(RenderInterface* renderInterface, const std::string &apiName) { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); - return; - } - - uint32_t flags = SDL_WINDOW_RESIZABLE; -#if defined(__APPLE__) - flags |= SDL_WINDOW_METAL; -#endif - + void RenderInterfaceTest(RenderInterface* renderInterface, SDL_Window* window, const std::string &apiName) { std::string windowTitle = "Plume Example (" + apiName + ")"; - SDL_Window* window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); - if (!window) { - fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); - SDL_Quit(); - return; - } + SDL_SetWindowTitle(window, windowTitle.c_str()); + TestContext ctx; +#if PLUME_SDL_VULKAN_ENABLED + createContext(ctx, renderInterface, window, apiName); +#elif defined(__linux__) SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); - - TestContext ctx; -#if defined(__linux__) createContext(ctx, renderInterface, { wmInfo.info.x11.display, wmInfo.info.x11.window }, apiName); #elif defined(__APPLE__) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); SDL_MetalView view = SDL_Metal_CreateView(window); createContext(ctx, renderInterface, { wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) }, apiName); #elif defined(WIN32) + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); createContext(ctx, renderInterface, { wmInfo.info.win.window }, apiName); #endif @@ -365,17 +357,19 @@ namespace plume { #if defined(__APPLE__) SDL_Metal_DestroyView(view); #endif - SDL_DestroyWindow(window); - SDL_Quit(); } } -std::unique_ptr CreateRenderInterface(std::string &apiName) { +std::unique_ptr CreateRenderInterface(SDL_Window* window, std::string &apiName) { const bool useVulkan = false; #if defined(_WIN32) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "D3D12"; @@ -384,7 +378,11 @@ std::unique_ptr CreateRenderInterface(std::string &apiNa #elif defined(__APPLE__) if (useVulkan) { apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); +#endif } else { apiName = "Metal"; @@ -392,13 +390,46 @@ std::unique_ptr CreateRenderInterface(std::string &apiNa } #else apiName = "Vulkan"; +#if PLUME_SDL_VULKAN_ENABLED + return plume::CreateVulkanInterface(window); +#else return plume::CreateVulkanInterface(); #endif +#endif } int main(int argc, char* argv[]) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); + return 1; + } + + uint32_t flags = SDL_WINDOW_RESIZABLE; +#if PLUME_SDL_VULKAN_ENABLED + flags |= SDL_WINDOW_VULKAN; +#elif defined(__APPLE__) + flags |= SDL_WINDOW_METAL; +#endif + + SDL_Window* window = SDL_CreateWindow("Plume Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); + if (!window) { + fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + std::string apiName = "Unknown"; - auto renderInterface = CreateRenderInterface(apiName); - plume::RenderInterfaceTest(renderInterface.get(), apiName); + auto renderInterface = CreateRenderInterface(window, apiName); + if (!renderInterface) { + fprintf(stderr, "Failed to create render interface\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + plume::RenderInterfaceTest(renderInterface.get(), window, apiName); + + SDL_DestroyWindow(window); + SDL_Quit(); return 0; } diff --git a/plume_d3d12.cpp b/plume_d3d12.cpp index 8c239f4..2cdcf18 100644 --- a/plume_d3d12.cpp +++ b/plume_d3d12.cpp @@ -2726,9 +2726,10 @@ namespace plume { this->pool = pool; this->desc = desc; + // Constant buffers must be aligned to D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT (256 bytes). D3D12_RESOURCE_DESC resourceDesc = {}; resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - resourceDesc.Width = desc.size; + resourceDesc.Width = (desc.flags & RenderBufferFlag::CONSTANT) ? roundUp(desc.size, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT) : desc.size; resourceDesc.Height = 1; resourceDesc.DepthOrArraySize = 1; resourceDesc.MipLevels = 1; diff --git a/plume_vulkan.cpp b/plume_vulkan.cpp index 2800022..f1dd503 100644 --- a/plume_vulkan.cpp +++ b/plume_vulkan.cpp @@ -921,7 +921,7 @@ namespace plume { } void VulkanBuffer::setName(const std::string &name) { - setObjectName(device->vk, VK_OBJECT_TYPE_IMAGE, uint64_t(vk), name); + setObjectName(device->vk, VK_OBJECT_TYPE_BUFFER, uint64_t(vk), name); } uint64_t VulkanBuffer::getDeviceAddress() const { @@ -2320,10 +2320,16 @@ namespace plume { // Destroy any image view references to the current swap chain. releaseImageViews(); - // We don't actually need to query the surface capabilities but the validation layer seems to cache the valid extents from this call. + // Query surface capabilities to get the valid extent bounds. VkSurfaceCapabilitiesKHR surfaceCapabilities = {}; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(commandQueue->device->physicalDevice, surface, &surfaceCapabilities); + // Clamp the extent to the surface capabilities' min/max bounds. + // This is required because the window size may differ from the valid surface extent + // (e.g., due to window decorations, compositor behavior, or timing issues). + width = std::clamp(width, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width); + height = std::clamp(height, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height); + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; createInfo.minImageCount = desc.textureCount;