diff --git a/.gitignore b/.gitignore index 6340263f..9b0bba18 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ build-* .DS_Store clion_compile.sh .python-version -**/__pycache__ \ No newline at end of file +**/__pycache__ +src/.cache/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a79ccfee..4f7217ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,10 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI add_definitions(-DSKITY_RELEASE) endif() +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DSKITY_DEBUG) +endif() + if (${SKITY_OPTIMIZE_O3}) message("build with o3") set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") diff --git a/cmake/ThirdPartyDep.cmake b/cmake/ThirdPartyDep.cmake index 2f201cd6..37e18e2c 100644 --- a/cmake/ThirdPartyDep.cmake +++ b/cmake/ThirdPartyDep.cmake @@ -22,6 +22,7 @@ endif() # Vulkan deps if(${SKITY_VK_BACKEND}) target_include_directories(skity PRIVATE third_party/Vulkan-Headers/include) + target_include_directories(skity PRIVATE third_party/VulkanMemoryAllocator/include) endif() # OpenGL header file @@ -34,12 +35,13 @@ if(${SKITY_VK_BACKEND}) # set vulkan headers if (NOT ANDROID) # android can use system vulkan headers from NDK - set(VULKAN_HEADERS_INSTALL_DIR "third_party/Vulkan-Headers" CACHE PATH "Vulkan-Headers") + set(VULKAN_HEADERS_INSTALL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/Vulkan-Headers" CACHE PATH "Vulkan-Headers") endif() target_compile_definitions(skity PRIVATE VK_NO_PROTOTYPES=1) add_subdirectory(third_party/volk) target_link_libraries(skity PRIVATE volk::volk) + endif() # json parser diff --git a/example/common/CMakeLists.txt b/example/common/CMakeLists.txt index d1a761e9..88f9877f 100644 --- a/example/common/CMakeLists.txt +++ b/example/common/CMakeLists.txt @@ -43,10 +43,27 @@ if (${SKITY_MTL_BACKEND}) endif() +if (${SKITY_VK_BACKEND}) + target_compile_definitions(skity_example_common PUBLIC -DSKITY_EXAMPLE_VK_BACKEND=1) + + target_sources(skity_example_common PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/vk/window_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/vk/window_vk.hpp + ) + + target_link_libraries(skity_example_common PRIVATE volk) +endif() + add_library(skity::example_common ALIAS skity_example_common) target_include_directories(skity_example_common PUBLIC ${CMAKE_SOURCE_DIR}/example) target_include_directories(skity_example_common PUBLIC ${CMAKE_SOURCE_DIR}/third_party/glad/include) +target_include_directories(skity_example_common PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(skity_example_common PRIVATE ${CMAKE_SOURCE_DIR}/module/wgx/include) +target_include_directories(skity_example_common PRIVATE ${CMAKE_SOURCE_DIR}/third_party/glm) +target_include_directories(skity_example_common PRIVATE ${CMAKE_SOURCE_DIR}/third_party/volk) +target_include_directories(skity_example_common PRIVATE ${CMAKE_SOURCE_DIR}/third_party/Vulkan-Headers/include) +target_include_directories(skity_example_common PRIVATE ${CMAKE_SOURCE_DIR}/third_party/VulkanMemoryAllocator/include) target_link_libraries(skity_example_common PUBLIC glfw skity::skity) target_link_libraries(skity_example_common PUBLIC skity::codec) diff --git a/example/common/vk/window_vk.cc b/example/common/vk/window_vk.cc new file mode 100644 index 00000000..9a495d3c --- /dev/null +++ b/example/common/vk/window_vk.cc @@ -0,0 +1,196 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "common/vk/window_vk.hpp" + +#include // Include volk first to define Vulkan types + +#include +#define GLFW_INCLUDE_NONE // Don't include any API headers +#include + +// Forward declare GLFW Vulkan function +extern "C" { +VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window, + const VkAllocationCallbacks* allocator, + VkSurfaceKHR* surface); +} +#include +#include + +#include "src/gpu/vk/gpu_context_impl_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/render/hw/hw_canvas.hpp" + +namespace skity { +namespace example { + +WindowVK::WindowVK(int width, int height, std::string title) + : Window(width, height, std::move(title)) {} + +bool WindowVK::OnInit() { + // Set GLFW hints for Vulkan + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + return true; +} + +GLFWwindow* WindowVK::CreateWindowHandler() { + return glfwCreateWindow(GetWidth(), GetHeight(), GetTitle().c_str(), nullptr, + nullptr); +} + +std::unique_ptr WindowVK::CreateGPUContext() { + // Check if Vulkan is available + if (!skity::IsVulkanAvailable()) { + std::cerr << "[ERROR]Vulkan is not available on this system." << std::endl; + return nullptr; + } + + // Get available devices + uint32_t device_count = 0; + const char** devices = skity::VkGetAvailableDevices(&device_count); + std::cout << "Found " << device_count << " Vulkan devices:" << std::endl; + if (devices) { + for (uint32_t i = 0; i < device_count; i++) { + std::cout << " Device " << i << ": " << devices[i] << std::endl; + } + } + + // Configure Vulkan context with validation layers enabled + skity::VkDevicePreferences prefs; + prefs.enable_validation = true; // Enable validation layers for debugging + prefs.preferred_device_type = 2; // Prefer discrete GPU + + // Create Vulkan context with preferences + auto context = skity::VkContextCreate(prefs); + if (!context) { + std::cerr << "[ERROR] Failed to create Vulkan context with validation." + << std::endl; + return nullptr; + } + + // Validate the context backend type + auto backend_type = context->GetBackendType(); + std::cout << "Context backend type: " << static_cast(backend_type) + << std::endl; + return context; +} + +void WindowVK::OnShow() { + // Validate GPU context + auto* gpu_context = GetGPUContext(); + if (!gpu_context) { + std::cerr << "[ERROR] No GPU context available for surface creation" + << std::endl; + return; + } + + // Cast to Vulkan-specific implementations + auto* vk_context_impl = static_cast(gpu_context); + auto* vk_device = static_cast(vk_context_impl->GetGPUDevice()); + + // Try to create VkSurfaceKHR from GLFW window using the new API + uint64_t instance_handle = VkGetInstance(gpu_context); + + if (instance_handle == 0) { + std::cerr << "[ERROR] Failed to get VkInstance from context" << std::endl; + exit(0); + return; + } + + VkInstance instance = reinterpret_cast(instance_handle); + if (instance == VK_NULL_HANDLE) { + std::cerr << "[ERROR] VkInstance is VK_NULL_HANDLE" << std::endl; + } + + // Initialize volk if not already done + VkResult volk_result = volkInitialize(); + if (volk_result != VK_SUCCESS) { + std::cerr << "[ERROR] Failed to initialize volk: " << volk_result + << std::endl; + exit(0); + return; + } + + volkLoadInstance(instance); // Load instance-specific functions + + // Check what extensions GLFW requires + uint32_t glfw_extension_count = 0; + const char** glfw_extensions = + glfwGetRequiredInstanceExtensions(&glfw_extension_count); + std::cout << "GLFW requires " << glfw_extension_count + << " extensions:" << std::endl; + for (uint32_t i = 0; i < glfw_extension_count; i++) { + std::cout << " - " << glfw_extensions[i] << std::endl; + } + + std::cout << "Creating window surface..." << std::endl; + VkResult result = glfwCreateWindowSurface(instance, GetNativeWindow(), + nullptr, &vk_surface_); + if (result != VK_SUCCESS) { + std::cerr << "[ERROR] Failed to create window surface: " << result + << std::endl; + exit(0); + return; + } + + GPUSurfaceDescriptorVk vk_desc{}; + vk_desc.backend = GPUBackendType::kVulkan; + vk_desc.width = GetWidth(); + vk_desc.height = GetHeight(); + vk_desc.sample_count = 1; + vk_desc.content_scale = 1.0f; + vk_desc.surface_type = VkSurfaceType::kSwapchain; + vk_desc.native_surface = reinterpret_cast(vk_surface_); + + window_surface_ = gpu_context->CreateSurface(&vk_desc); + if (!window_surface_) { + std::cerr << "[ERROR] Failed to create GPUSurface with swapchain" + << std::endl; + exit(0); + return; + } +} + +skity::Canvas* WindowVK::AquireCanvas() { + if (window_surface_) { + auto* canvas = window_surface_->LockCanvas(false); + return canvas; + } + std::cerr << "[ERROR] No render surface available." << std::endl; + return nullptr; +} + +void WindowVK::OnPresent() { + if (window_surface_) { + window_surface_->Flush(); + return; + } + std::cerr << "[ERROR] No render surface available for present" << std::endl; +} + +void WindowVK::OnTerminate() { + // Clean up resources + window_surface_.reset(); + + // Destroy VkSurfaceKHR if created + if (vk_surface_ != VK_NULL_HANDLE) { + auto* gpu_context = GetGPUContext(); + if (gpu_context) { + uint64_t instance_handle = VkGetInstance(gpu_context); + if (instance_handle != 0) { + VkInstance instance = reinterpret_cast(instance_handle); + volkLoadInstance(instance); // Ensure volk functions are loaded + vkDestroySurfaceKHR(instance, vk_surface_, nullptr); + } + } + vk_surface_ = VK_NULL_HANDLE; + } + + std::cout << "Vulkan window terminated." << std::endl; +} + +} // namespace example +} // namespace skity \ No newline at end of file diff --git a/example/common/vk/window_vk.hpp b/example/common/vk/window_vk.hpp new file mode 100644 index 00000000..60fa5ba6 --- /dev/null +++ b/example/common/vk/window_vk.hpp @@ -0,0 +1,43 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SKITY_EXAMPLE_COMMON_VK_WINDOW_VK_HPP +#define SKITY_EXAMPLE_COMMON_VK_WINDOW_VK_HPP + +#include + +#include + +#include "common/window.hpp" + +namespace skity { +class VkInterface; + +namespace example { + +class WindowVK : public Window { + public: + WindowVK(int width, int height, std::string title); + ~WindowVK() override = default; + + Backend GetBackend() const override { return Backend::kVulkan; } + + protected: + bool OnInit() override; + GLFWwindow* CreateWindowHandler() override; + std::unique_ptr CreateGPUContext() override; + void OnShow() override; + skity::Canvas* AquireCanvas() override; + void OnPresent() override; + void OnTerminate() override; + + private: + VkSurfaceKHR vk_surface_ = VK_NULL_HANDLE; + std::unique_ptr window_surface_; +}; + +} // namespace example +} // namespace skity + +#endif // SKITY_EXAMPLE_COMMON_VK_WINDOW_VK_HPP \ No newline at end of file diff --git a/example/common/window.cc b/example/common/window.cc index 0ce971a8..1f105a70 100644 --- a/example/common/window.cc +++ b/example/common/window.cc @@ -19,6 +19,10 @@ #include "common/sw/window_sw.hpp" #endif +#ifdef SKITY_EXAMPLE_VK_BACKEND +#include "common/vk/window_vk.hpp" +#endif + namespace skity { namespace example { @@ -44,6 +48,12 @@ std::unique_ptr Window::CreateWindow(Backend backend, uint32_t width, window = std::make_unique(width, height, std::move(title)); #else std::cerr << "Software backend is not supported." << std::endl; +#endif + } else if (backend == Backend::kVulkan) { +#ifdef SKITY_EXAMPLE_VK_BACKEND + window = std::make_unique(width, height, std::move(title)); +#else + std::cerr << "Vulkan backend is not supported." << std::endl; #endif } diff --git a/hab/DEPS b/hab/DEPS index fae89d38..e31fb733 100644 --- a/hab/DEPS +++ b/hab/DEPS @@ -57,4 +57,16 @@ deps = { "ignore_in_git": True, "commit": "ee86beb30e4973f5feffe3ce63bfa4fbadf72f38", }, + "third_party/SPIRV-Cross": { + "type": "git", + "url": "https://github.com/KhronosGroup/SPIRV-Cross.git", + "ignore_in_git": True, + "commit": "ebe2aa0cd80f5eb5cd8a605da604cacf72205f3b", + }, + "third_party/glslang": { + "type": "git", + "url": "https://github.com/KhronosGroup/glslang.git", + "ignore_in_git": True, + "commit": "d1517d64cfca91f573af1bf7341dc3a5113349c0", + }, } \ No newline at end of file diff --git a/include/skity/gpu/gpu_context_vk.hpp b/include/skity/gpu/gpu_context_vk.hpp new file mode 100644 index 00000000..c421addf --- /dev/null +++ b/include/skity/gpu/gpu_context_vk.hpp @@ -0,0 +1,162 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef INCLUDE_SKITY_GPU_GPU_CONTEXT_VK_HPP +#define INCLUDE_SKITY_GPU_GPU_CONTEXT_VK_HPP + +#include +#include +#include +#include + +namespace skity { + +/** + * @enum VkSurfaceType indicates which type the Vulkan backend Surface targets + */ +enum class VkSurfaceType { + /** + * empty type, default value + */ + kInvalid, + /** + * Indicate the Surface targets a Vulkan image + */ + kImage, + /** + * Indicate the Surface targets a Vulkan swapchain for on-screen rendering + */ + kSwapchain, +}; + +struct GPUSurfaceDescriptorVk : public GPUSurfaceDescriptor { + VkSurfaceType surface_type = VkSurfaceType::kInvalid; + + /** + * Platform-specific surface handle for swapchain creation + * This should be set when surface_type is kSwapchain + */ + void* native_surface = nullptr; + + /** + * Vulkan image handle when surface_type is kImage + */ + uint64_t vk_image = 0; + + /** + * Vulkan image format + */ + uint32_t vk_format = 0; +}; + +struct GPUBackendTextureInfoVk : public GPUBackendTextureInfo { + /** + * Vulkan image handle + */ + uint64_t vk_image = 0; + + /** + * Vulkan image format + */ + uint32_t vk_format = 0; + + /** + * Indicate whether the engine is responsible for destroying the image + */ + bool owned_by_engine = false; +}; + +/** + * Vulkan device selection preferences + */ +struct VkDevicePreferences { + /** + * Preferred device type (integrated, discrete, etc.) + * VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2 + * VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1 + */ + uint32_t preferred_device_type = 2; // Discrete GPU by default + + /** + * Require specific Vulkan API version (encoded as VK_MAKE_VERSION) + * 0 means use whatever is available + */ + uint32_t required_api_version = 0; + + /** + * Enable validation layers for debugging + */ + bool enable_validation = false; + + /** + * Custom instance extensions to enable + */ + std::vector instance_extensions = {}; + + /** + * Custom device extensions to enable + */ + std::vector device_extensions = {}; +}; + +/** + * Create a GPUContext instance targeting Vulkan backend with default settings. + * Uses automatic device selection and standard configuration. + * + * @return GPUContext instance or null if creation failed + */ +std::unique_ptr SKITY_API VkContextCreate(); + +/** + * Create a GPUContext instance targeting Vulkan backend with custom + * preferences. Allows fine-grained control over Vulkan instance and device + * selection. + * + * @param preferences Device selection and configuration preferences + * @return GPUContext instance or null if creation failed + */ +std::unique_ptr SKITY_API +VkContextCreate(const VkDevicePreferences& preferences); + +/** + * Create a GPUContext instance targeting Vulkan backend using existing Vulkan + * objects. Useful for integration with external Vulkan applications. + * + * @param instance Existing VkInstance handle (as uint64_t to avoid Vulkan + * headers) + * @param device Existing VkDevice handle + * @param queue Existing VkQueue handle for graphics operations + * @param queue_family_index Graphics queue family index + * @return GPUContext instance or null if creation failed + */ +std::unique_ptr SKITY_API +VkContextCreateWithExisting(uint64_t instance, uint64_t device, uint64_t queue, + uint32_t queue_family_index); + +/** + * Check if Vulkan is available on the current system + * + * @return true if Vulkan is available, false otherwise + */ +bool SKITY_API IsVulkanAvailable(); + +/** + * Get information about available Vulkan devices + * + * @param device_count Output parameter for number of devices found + * @return Array of device names, or null if Vulkan is not available + */ +const char** SKITY_API VkGetAvailableDevices(uint32_t* device_count); + +/** + * Get the Vulkan instance handle from a Vulkan context + * + * @param context GPU context (must be a Vulkan context) + * @return VkInstance handle as uint64_t, or 0 if context is not Vulkan + */ +uint64_t SKITY_API VkGetInstance(GPUContext* context); + +} // namespace skity + +#endif // INCLUDE_SKITY_GPU_GPU_CONTEXT_VK_HPP \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dcbcfde5..3b813fad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -438,6 +438,74 @@ if(${SKITY_HW_RENDERER}) "-framework Foundation" ) endif() + + # Vulkan backend + if(${SKITY_VK_BACKEND}) + target_sources( + skity + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/formats_vk.h + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/vk_interface.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/vk_interface.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_blit_pass_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_blit_pass_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_buffer_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_buffer_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_command_buffer_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_command_buffer_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_context_impl_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_context_impl_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_device_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_device_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_descriptor_set_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_descriptor_set_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_pipeline_cache_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_pipeline_cache_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_render_pass_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_render_pass_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_render_pipeline_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_render_pipeline_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_sampler_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_sampler_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_shader_function_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_shader_function_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_surface_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_surface_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_window_surface_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_window_surface_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_texture_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/gpu_texture_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/sync_objects_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/sync_objects_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/spirv_compiler_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/spirv_compiler_vk.hpp + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/formats_vk.cc + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/formats_vk.h + ${CMAKE_CURRENT_LIST_DIR}/gpu/vk/vma_impl.cc + ${CMAKE_CURRENT_LIST_DIR}/render/hw/vk/vk_root_layer.cc + ${CMAKE_CURRENT_LIST_DIR}/render/hw/vk/vk_root_layer.hpp + ) + + # Add Vulkan dependencies + target_include_directories(skity PRIVATE + ${CMAKE_SOURCE_DIR}/third_party + ${CMAKE_SOURCE_DIR}/third_party/Vulkan-Headers/include + ${CMAKE_SOURCE_DIR}/third_party/VulkanMemoryAllocator/include + ) + + # Add glslang subdirectory and link libraries + add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/glslang third_party/glslang) + add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/SPIRV-Cross third_party/SPIRV-Cross) + + target_link_libraries(skity PRIVATE + glslang + SPIRV + glslang-default-resource-limits + spirv-cross-core + spirv-cross-glsl + volk + ) + endif() endif() if(${SKITY_SW_RENDERER}) diff --git a/src/gpu/gpu_surface_impl.cc b/src/gpu/gpu_surface_impl.cc index df6643a6..a8081325 100644 --- a/src/gpu/gpu_surface_impl.cc +++ b/src/gpu/gpu_surface_impl.cc @@ -4,6 +4,7 @@ #include "src/gpu/gpu_surface_impl.hpp" +#include "src/logging.hpp" #include "src/render/hw/hw_canvas.hpp" #include "src/render/hw/hw_stage_buffer.hpp" #include "src/utils/arena_allocator.hpp" @@ -41,6 +42,11 @@ Canvas* GPUSurfaceImpl::LockCanvas(bool clear) { auto root_layer = OnBeginNextFrame(clear); + if (!root_layer) { + LOGE("OnBeginNextFrame failed to create root layer"); + return nullptr; + } + root_layer->SetEnableMergingDrawCall(ctx_->IsEnableMergingDrawCall()); canvas_->BeginNewFrame(root_layer); @@ -59,4 +65,10 @@ void GPUSurfaceImpl::Flush() { } } +void GPUSurfaceImpl::FlushCanvas() { + if (canvas_) { + canvas_->Flush(); + } +} + } // namespace skity diff --git a/src/gpu/gpu_surface_impl.hpp b/src/gpu/gpu_surface_impl.hpp index 7cb0ddbf..0dad2fc7 100644 --- a/src/gpu/gpu_surface_impl.hpp +++ b/src/gpu/gpu_surface_impl.hpp @@ -49,6 +49,8 @@ class GPUSurfaceImpl : public GPUSurface { virtual void OnFlush() = 0; + void FlushCanvas(); + private: uint32_t width_; uint32_t height_; diff --git a/src/gpu/vk/formats_vk.cc b/src/gpu/vk/formats_vk.cc new file mode 100644 index 00000000..ef8b02ed --- /dev/null +++ b/src/gpu/vk/formats_vk.cc @@ -0,0 +1,149 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/formats_vk.h" + +namespace skity { + +VkFormat GPUTextureFormatToVkFormat(GPUTextureFormat format) { + switch (format) { + case GPUTextureFormat::kR8Unorm: + return VK_FORMAT_R8_UNORM; + case GPUTextureFormat::kRGB8Unorm: + return VK_FORMAT_R8G8B8_UNORM; + case GPUTextureFormat::kRGB565Unorm: + return VK_FORMAT_R5G6B5_UNORM_PACK16; + case GPUTextureFormat::kRGBA8Unorm: + return VK_FORMAT_R8G8B8A8_UNORM; + case GPUTextureFormat::kBGRA8Unorm: + return VK_FORMAT_B8G8R8A8_UNORM; + case GPUTextureFormat::kStencil8: + return VK_FORMAT_S8_UINT; + case GPUTextureFormat::kDepth24Stencil8: + return VK_FORMAT_D24_UNORM_S8_UINT; + case GPUTextureFormat::kInvalid: + default: + return VK_FORMAT_UNDEFINED; + } +} + +VkFormat ColorTypeToVkFormat(ColorType color_type) { + switch (color_type) { + case ColorType::kRGBA: + return VK_FORMAT_R8G8B8A8_UNORM; + case ColorType::kBGRA: + return VK_FORMAT_B8G8R8A8_UNORM; + case ColorType::kRGB565: + return VK_FORMAT_R5G6B5_UNORM_PACK16; + case ColorType::kA8: + return VK_FORMAT_R8_UNORM; + case ColorType::kUnknown: + default: + return VK_FORMAT_UNDEFINED; + } +} + +uint32_t VkFormatBytesPerPixel(VkFormat format) { + switch (format) { + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_S8_UINT: + return 1; + case VK_FORMAT_R5G6B5_UNORM_PACK16: + return 2; + case VK_FORMAT_R8G8B8_UNORM: + return 3; + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_D24_UNORM_S8_UINT: + return 4; + default: + return 4; // Default to 4 bytes + } +} + +bool IsVkFormatRenderTargetSupported(VkFormat format, + VkPhysicalDevice physical_device) { + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); + + return (properties.optimalTilingFeatures & + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) || + (properties.optimalTilingFeatures & + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); +} + +VkImageTiling GetOptimalTiling(VkFormat format, + VkPhysicalDevice physical_device, + VkFormatFeatureFlags features) { + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); + + if ((properties.optimalTilingFeatures & features) == features) { + return VK_IMAGE_TILING_OPTIMAL; + } else if ((properties.linearTilingFeatures & features) == features) { + return VK_IMAGE_TILING_LINEAR; + } else { + return VK_IMAGE_TILING_OPTIMAL; // Default to optimal, may require format + // fallback + } +} + +VkImageUsageFlags GPUTextureUsageToVkImageUsage(GPUTextureUsageMask usage) { + VkImageUsageFlags vk_usage = 0; + + if (usage & static_cast(GPUTextureUsage::kCopySrc)) { + vk_usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + if (usage & static_cast(GPUTextureUsage::kCopyDst)) { + vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + if (usage & static_cast(GPUTextureUsage::kTextureBinding)) { + vk_usage |= VK_IMAGE_USAGE_SAMPLED_BIT; + } + if (usage & static_cast(GPUTextureUsage::kStorageBinding)) { + vk_usage |= VK_IMAGE_USAGE_STORAGE_BIT; + } + if (usage & static_cast(GPUTextureUsage::kRenderAttachment)) { + vk_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + // Always add transfer destination for data uploads + vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + return vk_usage; +} + +VkImageUsageFlags GPUTextureUsageToVkImageUsage(GPUTextureUsageMask usage, + GPUTextureFormat format) { + VkImageUsageFlags vk_usage = 0; + + if (usage & static_cast(GPUTextureUsage::kCopySrc)) { + vk_usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + if (usage & static_cast(GPUTextureUsage::kCopyDst)) { + vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + if (usage & static_cast(GPUTextureUsage::kTextureBinding)) { + vk_usage |= VK_IMAGE_USAGE_SAMPLED_BIT; + } + if (usage & static_cast(GPUTextureUsage::kStorageBinding)) { + vk_usage |= VK_IMAGE_USAGE_STORAGE_BIT; + } + if (usage & static_cast(GPUTextureUsage::kRenderAttachment)) { + // Check if this is a depth/stencil format + if (format == GPUTextureFormat::kDepth24Stencil8 || + format == GPUTextureFormat::kStencil8) { + vk_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } else { + vk_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + } + + // Always add transfer destination for data uploads + vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + return vk_usage; +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/formats_vk.h b/src/gpu/vk/formats_vk.h new file mode 100644 index 00000000..a2a1f959 --- /dev/null +++ b/src/gpu/vk/formats_vk.h @@ -0,0 +1,56 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_FORMATS_VK_H +#define SRC_GPU_VK_FORMATS_VK_H + +#include + +#include "skity/graphic/color_type.hpp" +#include "src/gpu/gpu_texture.hpp" + +namespace skity { + +/** + * Convert Skity GPU texture format to Vulkan format + */ +VkFormat GPUTextureFormatToVkFormat(GPUTextureFormat format); + +/** + * Convert Skity color type to Vulkan format + */ +VkFormat ColorTypeToVkFormat(ColorType color_type); + +/** + * Get the number of bytes per pixel for a Vulkan format + */ +uint32_t VkFormatBytesPerPixel(VkFormat format); + +/** + * Check if a Vulkan format is supported as a render target + */ +bool IsVkFormatRenderTargetSupported(VkFormat format, + VkPhysicalDevice physical_device); + +/** + * Get optimal tiling mode for a format + */ +VkImageTiling GetOptimalTiling(VkFormat format, + VkPhysicalDevice physical_device, + VkFormatFeatureFlags features); + +/** + * Convert GPU texture usage to Vulkan image usage flags + */ +VkImageUsageFlags GPUTextureUsageToVkImageUsage(GPUTextureUsageMask usage); + +/** + * Convert GPU texture usage to Vulkan image usage flags with format information + */ +VkImageUsageFlags GPUTextureUsageToVkImageUsage(GPUTextureUsageMask usage, + GPUTextureFormat format); + +} // namespace skity + +#endif // SRC_GPU_VK_FORMATS_VK_H \ No newline at end of file diff --git a/src/gpu/vk/gpu_blit_pass_vk.cc b/src/gpu/vk/gpu_blit_pass_vk.cc new file mode 100644 index 00000000..1c11ef36 --- /dev/null +++ b/src/gpu/vk/gpu_blit_pass_vk.cc @@ -0,0 +1,67 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_blit_pass_vk.hpp" + +#include "src/gpu/vk/gpu_buffer_vk.hpp" +#include "src/gpu/vk/gpu_command_buffer_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUBlitPassVk::GPUBlitPassVk(GPUCommandBufferVk* command_buffer) + : command_buffer_(command_buffer) {} + +GPUBlitPassVk::~GPUBlitPassVk() = default; + +void GPUBlitPassVk::UploadTextureData(std::shared_ptr texture, + uint32_t offset_x, uint32_t offset_y, + uint32_t width, uint32_t height, + void* data) { + if (!texture || !data) { + LOGE("Invalid parameters for texture data upload"); + return; + } + + auto* texture_vk = static_cast(texture.get()); + auto* device = command_buffer_->GetDevice(); + + if (!texture_vk || !device) { + LOGE("Invalid texture or device for upload"); + return; + } + + // Use the texture's built-in upload functionality + texture_vk->UploadData(device, offset_x, offset_y, width, height, data); + LOGI("Uploaded texture data: %ux%u at (%u, %u)", width, height, offset_x, + offset_y); +} + +void GPUBlitPassVk::UploadBufferData(GPUBuffer* buffer, void* data, + size_t size) { + if (!buffer || !data || size == 0) { + LOGE("Invalid parameters for buffer data upload"); + return; + } + + auto* buffer_vk = static_cast(buffer); + if (!buffer_vk) { + LOGE("Invalid buffer type for upload"); + return; + } + + // Use the buffer's built-in upload functionality + buffer_vk->UploadData(data, size); + LOGI("Uploaded buffer data: %zu bytes", size); +} + +void GPUBlitPassVk::End() { + // The blit pass operations are immediately executed, so no cleanup is needed + // In a more sophisticated implementation, we might batch operations here + LOGI("Blit pass completed"); +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_blit_pass_vk.hpp b/src/gpu/vk/gpu_blit_pass_vk.hpp new file mode 100644 index 00000000..f48eed10 --- /dev/null +++ b/src/gpu/vk/gpu_blit_pass_vk.hpp @@ -0,0 +1,35 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_BLIT_PASS_VK_HPP +#define SRC_GPU_VK_GPU_BLIT_PASS_VK_HPP + +#include + +#include "src/gpu/gpu_blit_pass.hpp" + +namespace skity { + +class GPUCommandBufferVk; + +class GPUBlitPassVk : public GPUBlitPass { + public: + explicit GPUBlitPassVk(GPUCommandBufferVk* command_buffer); + ~GPUBlitPassVk(); + + void UploadTextureData(std::shared_ptr texture, uint32_t offset_x, + uint32_t offset_y, uint32_t width, uint32_t height, + void* data) override; + + void UploadBufferData(GPUBuffer* buffer, void* data, size_t size) override; + + void End() override; + + private: + GPUCommandBufferVk* command_buffer_ = nullptr; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_BLIT_PASS_VK_HPP diff --git a/src/gpu/vk/gpu_buffer_vk.cc b/src/gpu/vk/gpu_buffer_vk.cc new file mode 100644 index 00000000..28f5ae54 --- /dev/null +++ b/src/gpu/vk/gpu_buffer_vk.cc @@ -0,0 +1,143 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_buffer_vk.hpp" + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/logging.hpp" +#include "src/tracing.hpp" + +namespace skity { + +GPUBufferVk::GPUBufferVk(GPUDeviceVk* device, GPUBufferUsageMask usage) + : GPUBuffer(usage), device_(device) {} + +GPUBufferVk::~GPUBufferVk() { + if (is_mapped_) { + Unmap(); + } + + if (buffer_ != VK_NULL_HANDLE && device_) { + vmaDestroyBuffer(device_->GetAllocator(), buffer_, allocation_); + } +} + +void GPUBufferVk::UploadData(void* data, size_t size) { + SKITY_TRACE_EVENT("GPUBufferVk::UploadData"); + + if (size == 0 || data == nullptr) { + return; + } + + if (buffer_ != VK_NULL_HANDLE && size_ < size) { + vmaDestroyBuffer(device_->GetAllocator(), buffer_, allocation_); + buffer_ = VK_NULL_HANDLE; + } + + if (buffer_ == VK_NULL_HANDLE) { + size_ = size; + VkBufferCreateInfo buffer_info{}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = size_; + buffer_info.usage = GetVulkanUsageFlags(); + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo alloc_info{}; + alloc_info.usage = GetVmaMemoryUsage(); + + VkResult result = + vmaCreateBuffer(device_->GetAllocator(), &buffer_info, &alloc_info, + &buffer_, &allocation_, nullptr); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan buffer: {}", result); + return; + } + } + + void* mapped_data = GetMappedPtr(); + if (mapped_data) { + memcpy(mapped_data, data, size); + FlushMappedRange(0, size); + Unmap(); + } else { + LOGE("Failed to map buffer for data upload"); + } +} + +void* GPUBufferVk::GetMappedPtr() { + if (is_mapped_) { + return mapped_ptr_; + } + + VkResult result = + vmaMapMemory(device_->GetAllocator(), allocation_, &mapped_ptr_); + if (result != VK_SUCCESS) { + LOGE("Failed to map buffer memory: {}", result); + return nullptr; + } + + is_mapped_ = true; + return mapped_ptr_; +} + +void GPUBufferVk::Unmap() { + if (is_mapped_) { + vmaUnmapMemory(device_->GetAllocator(), allocation_); + mapped_ptr_ = nullptr; + is_mapped_ = false; + } +} + +void GPUBufferVk::FlushMappedRange(uint64_t offset, uint64_t size) { + if (!is_mapped_) { + return; + } + + VkResult result = + vmaFlushAllocation(device_->GetAllocator(), allocation_, offset, size); + if (result != VK_SUCCESS) { + LOGE("Failed to flush buffer range: {}", result); + } +} + +VkBufferUsageFlags GPUBufferVk::GetVulkanUsageFlags() const { + VkBufferUsageFlags usage = 0; + + if (GetUsage() & GPUBufferUsage::kVertexBuffer) { + usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + } + if (GetUsage() & GPUBufferUsage::kIndexBuffer) { + usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + } + if (GetUsage() & GPUBufferUsage::kUniformBuffer) { + usage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + } + + // Always add transfer usage for data uploads + usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + return usage; +} + +VmaMemoryUsage GPUBufferVk::GetVmaMemoryUsage() const { + auto usage = GetUsage(); + + // Optimize memory usage based on buffer type + if (usage & GPUBufferUsage::kIndexBuffer || + usage & GPUBufferUsage::kVertexBuffer) { + // Vertex/index buffers: prefer GPU-optimized memory, but allow CPU write + // access + return VMA_MEMORY_USAGE_CPU_TO_GPU; // Upload from CPU, then GPU access + } + + if (usage & GPUBufferUsage::kUniformBuffer) { + // Uniform buffers: frequently updated, need fast CPU write access + return VMA_MEMORY_USAGE_CPU_TO_GPU; + } + + // Default: CPU-to-GPU for general buffers + return VMA_MEMORY_USAGE_CPU_TO_GPU; +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_buffer_vk.hpp b/src/gpu/vk/gpu_buffer_vk.hpp new file mode 100644 index 00000000..fbdaddd4 --- /dev/null +++ b/src/gpu/vk/gpu_buffer_vk.hpp @@ -0,0 +1,49 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_BUFFER_VK_HPP +#define SRC_GPU_VK_GPU_BUFFER_VK_HPP + +#include +#include + +#include "src/gpu/gpu_buffer.hpp" + +namespace skity { + +class GPUDeviceVk; + +class GPUBufferVk : public GPUBuffer { + public: + explicit GPUBufferVk(GPUDeviceVk* device, GPUBufferUsageMask usage); + ~GPUBufferVk() override; + + void UploadData(void* data, size_t size); + + // Vulkan-specific methods + void* GetMappedPtr(); + void Unmap(); + void FlushMappedRange(uint64_t offset, uint64_t size); + + // Vulkan-specific getters + VkBuffer GetBuffer() const { return buffer_; } + VmaAllocation GetAllocation() const { return allocation_; } + size_t GetSize() const { return size_; } + + private: + VkBufferUsageFlags GetVulkanUsageFlags() const; + VmaMemoryUsage GetVmaMemoryUsage() const; + + private: + GPUDeviceVk* device_ = nullptr; + VkBuffer buffer_ = VK_NULL_HANDLE; + VmaAllocation allocation_ = VK_NULL_HANDLE; + void* mapped_ptr_ = nullptr; + size_t size_ = 0; + bool is_mapped_ = false; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_BUFFER_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/gpu_command_buffer_vk.cc b/src/gpu/vk/gpu_command_buffer_vk.cc new file mode 100644 index 00000000..258a4183 --- /dev/null +++ b/src/gpu/vk/gpu_command_buffer_vk.cc @@ -0,0 +1,189 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_command_buffer_vk.hpp" + +#include "src/gpu/vk/gpu_blit_pass_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_render_pass_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUCommandBufferVk::GPUCommandBufferVk(GPUDeviceVk* device) + : device_(device), sync_manager_(std::make_unique(device)) {} + +GPUCommandBufferVk::~GPUCommandBufferVk() { + if (command_buffer_ != VK_NULL_HANDLE && device_) { + vkFreeCommandBuffers(device_->GetDevice(), device_->GetCommandPool(), 1, + &command_buffer_); + } +} + +bool GPUCommandBufferVk::Initialize() { + if (!device_) { + LOGE("Invalid device for command buffer initialization"); + return false; + } + + command_pool_ = device_->GetCommandPool(); + + VkCommandBufferAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.commandPool = command_pool_; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = 1; + + VkResult result = vkAllocateCommandBuffers(device_->GetDevice(), &alloc_info, + &command_buffer_); + if (result != VK_SUCCESS) { + LOGE("Failed to allocate command buffer: %d", result); + return false; + } + + return true; +} + +void GPUCommandBufferVk::Reset() { + if (command_buffer_ != VK_NULL_HANDLE) { + vkResetCommandBuffer(command_buffer_, 0); + } + + render_passes_.clear(); + blit_passes_.clear(); + is_recording_ = false; +} + +bool GPUCommandBufferVk::BeginRecording() { + if (is_recording_) { + return true; + } + + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + VkResult result = vkBeginCommandBuffer(command_buffer_, &begin_info); + if (result != VK_SUCCESS) { + LOGE("Failed to begin command buffer recording: %d", result); + return false; + } + + is_recording_ = true; + return true; +} + +bool GPUCommandBufferVk::EndRecording() { + if (!is_recording_) { + return true; + } + + VkResult result = vkEndCommandBuffer(command_buffer_); + if (result != VK_SUCCESS) { + LOGE("Failed to end command buffer recording: %d", result); + return false; + } + + is_recording_ = false; + return true; +} + +std::shared_ptr GPUCommandBufferVk::BeginRenderPass( + const GPURenderPassDescriptor& desc) { + if (!BeginRecording()) { + LOGE("Failed to begin command buffer recording"); + return nullptr; + } + + auto render_pass = std::make_shared(this, desc); + render_passes_.push_back(render_pass); + return render_pass; +} + +std::shared_ptr GPUCommandBufferVk::BeginBlitPass() { + if (!BeginRecording()) { + LOGE("Failed to begin command buffer recording"); + return nullptr; + } + + auto blit_pass = std::make_shared(this); + blit_passes_.push_back(blit_pass); + return blit_pass; +} + +bool GPUCommandBufferVk::Submit() { + if (!EndRecording()) { + LOGE("Failed to end command buffer recording"); + return false; + } + + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer_; + + VkQueue graphics_queue = device_->GetGraphicsQueue(); + VkResult result = + vkQueueSubmit(graphics_queue, 1, &submit_info, VK_NULL_HANDLE); + if (result != VK_SUCCESS) { + LOGE("Failed to submit command buffer: %d", result); + return false; + } + + // Wait for queue to complete (synchronous for now) + vkQueueWaitIdle(graphics_queue); + + // Reset for next use + Reset(); + + return true; +} + +void GPUCommandBufferVk::AddMemoryBarrier(const VkMemoryBarrier& barrier) { + if (sync_manager_) { + sync_manager_->AddMemoryBarrier(barrier); + } +} + +void GPUCommandBufferVk::AddImageBarrier(const VkImageBarrier& barrier) { + if (sync_manager_) { + sync_manager_->AddImageBarrier(barrier); + } +} + +void GPUCommandBufferVk::AddBufferBarrier(const VkBufferBarrier& barrier) { + if (sync_manager_) { + sync_manager_->AddBufferBarrier(barrier); + } +} + +void GPUCommandBufferVk::ExecuteBarriers() { + if (sync_manager_ && command_buffer_ != VK_NULL_HANDLE) { + sync_manager_->ExecuteBarriers(command_buffer_); + sync_manager_->Reset(); + } +} + +void GPUCommandBufferVk::TransitionImageLayout(VkImage image, + VkImageLayout old_layout, + VkImageLayout new_layout, + VkImageAspectFlags aspect_mask) { + if (!sync_manager_) { + LOGW("No sync manager for image transition"); + return; + } + + if (image == VK_NULL_HANDLE) { + LOGE("Cannot transition null image layout"); + return; + } + + auto barrier = VkSyncManager::CreateImageTransitionBarrier( + image, old_layout, new_layout, aspect_mask); + sync_manager_->AddImageBarrier(barrier); + sync_manager_->ExecuteBarriers(command_buffer_); + sync_manager_->Reset(); +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_command_buffer_vk.hpp b/src/gpu/vk/gpu_command_buffer_vk.hpp new file mode 100644 index 00000000..2b7e8488 --- /dev/null +++ b/src/gpu/vk/gpu_command_buffer_vk.hpp @@ -0,0 +1,66 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_COMMAND_BUFFER_VK_HPP +#define SRC_GPU_VK_GPU_COMMAND_BUFFER_VK_HPP + +#include + +#include + +#include "src/gpu/gpu_command_buffer.hpp" +#include "src/gpu/vk/sync_objects_vk.hpp" + +namespace skity { + +class GPUDeviceVk; +class GPURenderPassVk; +class GPUBlitPassVk; + +class GPUCommandBufferVk : public GPUCommandBuffer { + public: + explicit GPUCommandBufferVk(GPUDeviceVk* device); + ~GPUCommandBufferVk() override; + + bool Initialize(); + void Reset(); + + std::shared_ptr BeginRenderPass( + const GPURenderPassDescriptor& desc) override; + + std::shared_ptr BeginBlitPass() override; + + bool Submit() override; + + VkCommandBuffer GetVkCommandBuffer() const { return command_buffer_; } + GPUDeviceVk* GetDevice() const { return device_; } + + // Synchronization support + void AddMemoryBarrier(const VkMemoryBarrier& barrier); + void AddImageBarrier(const VkImageBarrier& barrier); + void AddBufferBarrier(const VkBufferBarrier& barrier); + void ExecuteBarriers(); + void TransitionImageLayout( + VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, + VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT); + + private: + bool BeginRecording(); + bool EndRecording(); + + GPUDeviceVk* device_ = nullptr; + VkCommandBuffer command_buffer_ = VK_NULL_HANDLE; + VkCommandPool command_pool_ = VK_NULL_HANDLE; + + bool is_recording_ = false; + std::vector> render_passes_; + std::vector> blit_passes_; + + // Synchronization manager + std::unique_ptr sync_manager_; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_COMMAND_BUFFER_VK_HPP diff --git a/src/gpu/vk/gpu_context_impl_vk.cc b/src/gpu/vk/gpu_context_impl_vk.cc new file mode 100644 index 00000000..5ba003fb --- /dev/null +++ b/src/gpu/vk/gpu_context_impl_vk.cc @@ -0,0 +1,258 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_context_impl_vk.hpp" + +#include +#include + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_surface_vk.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/gpu/vk/gpu_window_surface_vk.hpp" +#include "src/gpu/vk/vk_interface.hpp" +#include "src/logging.hpp" + +namespace skity { + +std::unique_ptr VkContextCreate() { + // Use default preferences for backward compatibility + VkDevicePreferences preferences{}; + return VkContextCreate(preferences); +} + +std::unique_ptr VkContextCreate( + const VkDevicePreferences& preferences) { + auto* vk_interface = GetVkInterface(); + if (!vk_interface) { + LOGE("Failed to initialize Vulkan interface"); + return nullptr; + } + + auto ctx = std::make_unique(); + if (!ctx->InitWithPreferences(preferences)) { + LOGE("Failed to initialize Vulkan context with preferences"); + return nullptr; + } + + return ctx; +} + +std::unique_ptr VkContextCreateWithExisting( + uint64_t instance, uint64_t device, uint64_t queue, + uint32_t queue_family_index) { + auto* vk_interface = GetVkInterface(); + if (!vk_interface) { + LOGE("Failed to initialize Vulkan interface"); + return nullptr; + } + + auto ctx = std::make_unique(); + if (!ctx->InitWithExistingObjects(instance, device, queue, + queue_family_index)) { + LOGE("Failed to initialize Vulkan context with existing objects"); + return nullptr; + } + + return ctx; +} + +bool IsVulkanAvailable() { + auto* vk_interface = GetVkInterface(); + return vk_interface != nullptr; +} + +const char** VkGetAvailableDevices(uint32_t* device_count) { + if (!device_count) { + return nullptr; + } + + auto* vk_interface = GetVkInterface(); + if (!vk_interface) { + *device_count = 0; + return nullptr; + } + + // TODO: Implement device enumeration + // For now, return a placeholder indicating implementation needed + static const char* placeholder_device = + "Vulkan Device (enumeration not implemented)"; + static const char* device_list[] = {placeholder_device}; + + *device_count = 1; + return device_list; +} + +uint64_t VkGetInstance(GPUContext* context) { + if (!context || context->GetBackendType() != GPUBackendType::kVulkan) { + return 0; + } + + auto* vk_interface = GetVkInterface(); + if (!vk_interface) { + return 0; + } + + return reinterpret_cast(vk_interface->GetInstance()); +} + +GPUContextImplVk::GPUContextImplVk() + : GPUContextImpl(GPUBackendType::kVulkan) {} + +bool GPUContextImplVk::InitWithPreferences( + const VkDevicePreferences& preferences) { + // For now, delegate to the base Init() method + // In a full implementation, this would use the preferences to select + // specific devices, enable validation layers, etc. + LOGI( + "Initializing Vulkan context with preferences (validation: %s, " + "device_type: %u)", + preferences.enable_validation ? "enabled" : "disabled", + preferences.preferred_device_type); + + // TODO: Use preferences to configure device selection + // TODO: Enable validation layers if requested + // TODO: Enable custom extensions + + return Init(); +} + +bool GPUContextImplVk::InitWithExistingObjects(uint64_t instance, + uint64_t device, uint64_t queue, + uint32_t queue_family_index) { + // For now, log the external objects and delegate to base Init() + // In a full implementation, this would wrap the existing Vulkan objects + LOGI( + "Initializing Vulkan context with existing objects (instance: 0x%llx, " + "device: 0x%llx, queue: 0x%llx, family: %u)", + instance, device, queue, queue_family_index); + + // TODO: Wrap existing VkInstance, VkDevice, and VkQueue + // TODO: Skip device creation and use provided objects + // TODO: Set up context to use external objects + + return Init(); +} + +std::unique_ptr GPUContextImplVk::CreateGPUDevice() { + auto device = std::make_unique(); + if (!device->Init()) { + LOGE("Failed to initialize Vulkan device"); + return nullptr; + } + return std::move(device); +} + +std::unique_ptr GPUContextImplVk::CreateSurface( + GPUSurfaceDescriptor* desc) { + if (!desc) { + LOGE("Invalid surface descriptor"); + return nullptr; + } + + // Cast to Vulkan-specific descriptor (caller guarantees this is correct) + auto* vk_desc = static_cast(desc); + + LOGI("Creating Vulkan surface: type=%d, size=%dx%d", + static_cast(vk_desc->surface_type), vk_desc->width, + vk_desc->height); + + switch (vk_desc->surface_type) { + case VkSurfaceType::kSwapchain: { + // Create window surface with swapchain + if (!vk_desc->native_surface) { + LOGE("No native surface provided for swapchain creation"); + return nullptr; + } + + auto window_surface = std::make_unique( + this, vk_desc->width, vk_desc->height, vk_desc->sample_count, + vk_desc->content_scale); + + VkSurfaceKHR vk_surface = + reinterpret_cast(vk_desc->native_surface); + auto* vk_interface = GetVkInterface(); + + if (!window_surface->InitWithSurface(vk_surface, vk_interface)) { + LOGE("Failed to initialize window surface with swapchain"); + return nullptr; + } + + LOGI("Successfully created Vulkan window surface with swapchain"); + return window_surface; + } + + case VkSurfaceType::kImage: { + // Create surface targeting a specific Vulkan image + LOGE("VkSurfaceType::kImage not yet implemented"); + return nullptr; + } + + default: + LOGE("Unknown Vulkan surface type: %d", + static_cast(vk_desc->surface_type)); + return nullptr; + } +} + +std::unique_ptr GPUContextImplVk::CreateFxaaSurface( + GPUSurfaceDescriptor* desc) { + // TODO: Implement Vulkan FXAA surface creation + LOGE("Vulkan FXAA surface creation not yet implemented"); + return nullptr; +} + +std::shared_ptr GPUContextImplVk::OnWrapTexture( + GPUBackendTextureInfo* info, ReleaseCallback callback, + ReleaseUserData user_data) { + // TODO: Implement Vulkan texture wrapping + LOGE("Vulkan texture wrapping not yet implemented"); + return nullptr; +} + +std::shared_ptr GPUContextImplVk::OnReadPixels( + const std::shared_ptr& texture) const { + // TODO: Implement Vulkan pixel reading + LOGE("Vulkan pixel reading not yet implemented"); + return nullptr; +} + +std::unique_ptr GPUContextImplVk::OnCreateRenderTarget( + const GPURenderTargetDescriptor& desc, std::shared_ptr texture) { + if (!texture || !texture->GetGPUTexture()) { + LOGE("Invalid texture provided for Vulkan render target creation"); + return nullptr; + } + + auto* gpu_texture_vk = + static_cast(texture->GetGPUTexture().get()); + if (!gpu_texture_vk) { + LOGE("Failed to cast texture to Vulkan texture"); + return nullptr; + } + + // Create surface descriptor for Vulkan + GPUSurfaceDescriptorVk surface_desc{}; + surface_desc.backend = GetBackendType(); + surface_desc.width = desc.width; + surface_desc.height = desc.height; + surface_desc.content_scale = 1.0f; + surface_desc.sample_count = desc.sample_count; + surface_desc.surface_type = VkSurfaceType::kImage; + surface_desc.vk_format = VK_FORMAT_R8G8B8A8_UNORM; // Default format + + // Create the Vulkan surface + auto surface = GPUSurfaceVk::Create(this, surface_desc); + if (!surface) { + LOGE("Failed to create Vulkan surface for render target"); + return nullptr; + } + + // Set the target texture for the surface + surface->SetTargetTexture(texture->GetGPUTexture()); + + return std::make_unique(std::move(surface), texture); +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_context_impl_vk.hpp b/src/gpu/vk/gpu_context_impl_vk.hpp new file mode 100644 index 00000000..40626735 --- /dev/null +++ b/src/gpu/vk/gpu_context_impl_vk.hpp @@ -0,0 +1,53 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_CONTEXT_IMPL_VK_HPP +#define SRC_GPU_VK_GPU_CONTEXT_IMPL_VK_HPP + +#include + +#include "src/gpu/gpu_context_impl.hpp" + +namespace skity { + +class GPUContextImplVk : public GPUContextImpl { + public: + GPUContextImplVk(); + ~GPUContextImplVk() override = default; + + /** + * Initialize the context with custom device preferences + */ + bool InitWithPreferences(const VkDevicePreferences& preferences); + + /** + * Initialize the context using existing Vulkan objects + */ + bool InitWithExistingObjects(uint64_t instance, uint64_t device, + uint64_t queue, uint32_t queue_family_index); + + std::unique_ptr CreateSurface( + GPUSurfaceDescriptor* desc) override; + + std::unique_ptr CreateFxaaSurface( + GPUSurfaceDescriptor* desc) override; + + protected: + std::unique_ptr CreateGPUDevice() override; + + std::shared_ptr OnWrapTexture(GPUBackendTextureInfo* info, + ReleaseCallback callback, + ReleaseUserData user_data) override; + + std::shared_ptr OnReadPixels( + const std::shared_ptr& texture) const override; + + std::unique_ptr OnCreateRenderTarget( + const GPURenderTargetDescriptor& desc, + std::shared_ptr texture) override; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_CONTEXT_IMPL_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/gpu_descriptor_set_vk.cc b/src/gpu/vk/gpu_descriptor_set_vk.cc new file mode 100644 index 00000000..24c4c303 --- /dev/null +++ b/src/gpu/vk/gpu_descriptor_set_vk.cc @@ -0,0 +1,438 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_descriptor_set_vk.hpp" + +#include "src/gpu/vk/gpu_buffer_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_sampler_vk.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/gpu/vk/spirv_compiler_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUDescriptorSetVk::GPUDescriptorSetVk(GPUDeviceVk* device) : device_(device) {} + +GPUDescriptorSetVk::~GPUDescriptorSetVk() { Destroy(); } + +bool GPUDescriptorSetVk::Initialize( + const std::vector& bindings) { + if (bindings.empty()) { + LOGE("No descriptor bindings provided"); + return false; + } + + bindings_ = bindings; + + if (!CreateDescriptorSetLayout(bindings)) { + LOGE("Failed to create descriptor set layout"); + return false; + } + + if (!CreateDescriptorPool()) { + LOGE("Failed to create descriptor pool"); + Destroy(); + return false; + } + + if (!AllocateDescriptorSet()) { + LOGE("Failed to allocate descriptor set"); + Destroy(); + return false; + } + + initialized_ = true; + LOGI("Successfully initialized descriptor set with %zu bindings", + bindings.size()); + return true; +} + +bool GPUDescriptorSetVk::CreateDescriptorSetLayout( + const std::vector& bindings) { + std::vector layout_bindings; + layout_bindings.reserve(bindings.size()); + + for (const auto& binding : bindings) { + VkDescriptorSetLayoutBinding layout_binding = {}; + layout_binding.binding = binding.binding; + layout_binding.descriptorType = binding.type; + layout_binding.descriptorCount = binding.count; + layout_binding.stageFlags = binding.stage_flags; + layout_binding.pImmutableSamplers = nullptr; + + layout_bindings.push_back(layout_binding); + } + + VkDescriptorSetLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.bindingCount = static_cast(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + VkResult result = vkCreateDescriptorSetLayout( + device_->GetDevice(), &layout_info, nullptr, &descriptor_set_layout_); + if (result != VK_SUCCESS) { + LOGE("Failed to create descriptor set layout: %d", result); + return false; + } + + return true; +} + +bool GPUDescriptorSetVk::CreateDescriptorPool() { + // Count descriptor types needed + std::unordered_map type_counts; + for (const auto& binding : bindings_) { + type_counts[binding.type] += binding.count; + } + + // Create pool sizes + std::vector pool_sizes; + pool_sizes.reserve(type_counts.size()); + + for (const auto& [type, count] : type_counts) { + VkDescriptorPoolSize pool_size = {}; + pool_size.type = type; + pool_size.descriptorCount = count; + pool_sizes.push_back(pool_size); + } + + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 1; + pool_info.poolSizeCount = static_cast(pool_sizes.size()); + pool_info.pPoolSizes = pool_sizes.data(); + + VkResult result = vkCreateDescriptorPool(device_->GetDevice(), &pool_info, + nullptr, &descriptor_pool_); + if (result != VK_SUCCESS) { + LOGE("Failed to create descriptor pool: %d", result); + return false; + } + + return true; +} + +bool GPUDescriptorSetVk::AllocateDescriptorSet() { + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = descriptor_pool_; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &descriptor_set_layout_; + + VkResult result = vkAllocateDescriptorSets(device_->GetDevice(), &alloc_info, + &descriptor_set_); + if (result != VK_SUCCESS) { + LOGE("Failed to allocate descriptor set: %d", result); + return false; + } + + return true; +} + +void GPUDescriptorSetVk::BindBuffer(uint32_t binding, GPUBuffer* buffer, + size_t offset, size_t range) { + if (!buffer) { + LOGE("Invalid buffer for binding %u", binding); + return; + } + + auto* buffer_vk = static_cast(buffer); + + // IMPORTANT: Get the buffer handle at binding time, not creation time + // This ensures we get the current handle after any uploads that might have + // recreated the buffer + VkBuffer vk_buffer = buffer_vk->GetBuffer(); + + if (vk_buffer == VK_NULL_HANDLE) { + LOGE( + "Buffer VkBuffer handle is null for binding %u - this indicates a " + "buffer connection issue", + binding); + return; + } + + // Validate the buffer handle is reasonable (not obviously corrupted) + if ((uintptr_t)vk_buffer < 0x1000) { + LOGE("Buffer VkBuffer handle %p looks invalid for binding %u", + (void*)vk_buffer, binding); + return; + } + + // Align offset to device requirements (16 bytes for uniform buffers) + size_t aligned_offset = + (offset + 15) & ~15; // Round up to nearest 16-byte boundary + + VkDescriptorBufferInfo buffer_info = {}; + buffer_info.buffer = vk_buffer; + buffer_info.offset = aligned_offset; + buffer_info.range = range; + + // Store buffer info and defer write set creation until UpdateDescriptorSet + buffer_infos_.push_back(buffer_info); + + // Store binding info for later write set creation + VkWriteDescriptorSet write_set = {}; + write_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_set.dstSet = descriptor_set_; + write_set.dstBinding = binding; + write_set.dstArrayElement = 0; + write_set.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_set.descriptorCount = 1; + write_set.pBufferInfo = nullptr; // Will be set in UpdateDescriptorSet + + write_descriptor_sets_.push_back(write_set); + LOGI( + "Bound buffer to binding %u (VkBuffer: %p, offset: %zu->%zu, range: %zu)", + binding, (void*)vk_buffer, offset, aligned_offset, range); +} + +void GPUDescriptorSetVk::BindTexture(uint32_t binding, GPUTexture* texture, + GPUSampler* sampler) { + if (!texture) { + LOGE("Invalid texture for binding %u", binding); + return; + } + + auto* texture_vk = static_cast(texture); + + VkDescriptorImageInfo image_info = {}; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info.imageView = texture_vk->GetVkImageView(); + + if (sampler) { + auto* sampler_vk = static_cast(sampler); + image_info.sampler = sampler_vk->GetVkSampler(); + } else { + image_info.sampler = VK_NULL_HANDLE; + } + + image_infos_.push_back(image_info); + + VkWriteDescriptorSet write_set = {}; + write_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_set.dstSet = descriptor_set_; + write_set.dstBinding = binding; + write_set.dstArrayElement = 0; + write_set.descriptorType = sampler ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write_set.descriptorCount = 1; + write_set.pImageInfo = &image_infos_.back(); + + write_descriptor_sets_.push_back(write_set); + LOGI("Bound texture to binding %u", binding); +} + +void GPUDescriptorSetVk::BindSampler(uint32_t binding, GPUSampler* sampler) { + if (!sampler) { + LOGE("Invalid sampler for binding %u", binding); + return; + } + + auto* sampler_vk = static_cast(sampler); + + VkDescriptorImageInfo image_info = {}; + image_info.sampler = sampler_vk->GetVkSampler(); + image_info.imageView = VK_NULL_HANDLE; + image_info.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + image_infos_.push_back(image_info); + + VkWriteDescriptorSet write_set = {}; + write_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_set.dstSet = descriptor_set_; + write_set.dstBinding = binding; + write_set.dstArrayElement = 0; + write_set.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + write_set.descriptorCount = 1; + write_set.pImageInfo = &image_infos_.back(); + + write_descriptor_sets_.push_back(write_set); + LOGI("Bound sampler to binding %u", binding); +} + +bool GPUDescriptorSetVk::UpdateDescriptorSet() { + if (write_descriptor_sets_.empty()) { + LOGI("No descriptor set updates needed"); + return true; + } + + // Fix pointer assignments after all buffers are bound + size_t buffer_index = 0; + for (size_t i = 0; i < write_descriptor_sets_.size(); ++i) { + auto& write_set = write_descriptor_sets_[i]; + if (write_set.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER && + buffer_index < buffer_infos_.size()) { + write_set.pBufferInfo = &buffer_infos_[buffer_index]; + buffer_index++; + } + } + + vkUpdateDescriptorSets(device_->GetDevice(), + static_cast(write_descriptor_sets_.size()), + write_descriptor_sets_.data(), 0, nullptr); + + LOGI("Updated descriptor set with %zu writes", write_descriptor_sets_.size()); + return true; +} + +void GPUDescriptorSetVk::Destroy() { + VkDevice vk_device = device_->GetDevice(); + + if (descriptor_set_ != VK_NULL_HANDLE && descriptor_pool_ != VK_NULL_HANDLE) { + vkFreeDescriptorSets(vk_device, descriptor_pool_, 1, &descriptor_set_); + descriptor_set_ = VK_NULL_HANDLE; + } + + if (descriptor_pool_ != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(vk_device, descriptor_pool_, nullptr); + descriptor_pool_ = VK_NULL_HANDLE; + } + + if (descriptor_set_layout_ != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(vk_device, descriptor_set_layout_, nullptr); + descriptor_set_layout_ = VK_NULL_HANDLE; + } + + write_descriptor_sets_.clear(); + buffer_infos_.clear(); + image_infos_.clear(); + initialized_ = false; +} + +// GPUDescriptorManagerVk implementation +GPUDescriptorManagerVk::GPUDescriptorManagerVk(GPUDeviceVk* device) + : device_(device) {} + +GPUDescriptorManagerVk::~GPUDescriptorManagerVk() { descriptor_sets_.clear(); } + +std::shared_ptr GPUDescriptorManagerVk::CreateDescriptorSet( + const std::vector& bindings) { + auto descriptor_set = std::make_shared(device_); + + if (!descriptor_set->Initialize(bindings)) { + LOGE("Failed to initialize descriptor set"); + return nullptr; + } + + descriptor_sets_.push_back(descriptor_set); + LOGI("Created descriptor set, total: %zu", descriptor_sets_.size()); + return descriptor_set; +} + +VkDescriptorSetLayout GPUDescriptorManagerVk::CreateDescriptorSetLayout( + const std::vector& bindings) { + // Create descriptor set layout directly without creating a descriptor set + std::vector layout_bindings; + layout_bindings.reserve(bindings.size()); + + for (const auto& binding : bindings) { + VkDescriptorSetLayoutBinding layout_binding = {}; + layout_binding.binding = binding.binding; + layout_binding.descriptorType = binding.type; + layout_binding.descriptorCount = binding.count; + layout_binding.stageFlags = binding.stage_flags; + layout_binding.pImmutableSamplers = nullptr; + + layout_bindings.push_back(layout_binding); + } + + VkDescriptorSetLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.bindingCount = static_cast(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + VkDescriptorSetLayout descriptor_layout = VK_NULL_HANDLE; + VkResult result = vkCreateDescriptorSetLayout( + device_->GetDevice(), &layout_info, nullptr, &descriptor_layout); + if (result != VK_SUCCESS) { + LOGE("Failed to create descriptor set layout: %d", result); + return VK_NULL_HANDLE; + } + + LOGI("Created standalone descriptor set layout with %zu bindings", + bindings.size()); + return descriptor_layout; +} + +std::shared_ptr +GPUDescriptorManagerVk::CreateDescriptorSetFromReflection( + const SPIRVReflectionInfo& reflection) { + auto bindings = ExtractBindingsFromReflection(reflection); + if (bindings.empty()) { + LOGW("No descriptor bindings found in shader reflection"); + return nullptr; + } + + LOGI("Creating descriptor set from reflection with %zu bindings", + bindings.size()); + return CreateDescriptorSet(bindings); +} + +std::vector +GPUDescriptorManagerVk::ExtractBindingsFromReflection( + const SPIRVReflectionInfo& reflection) { + std::vector bindings; + + // Convert GPU shader stage to Vulkan stage flags + VkShaderStageFlags stage_flags = 0; + switch (reflection.stage) { + case GPUShaderStage::kVertex: + stage_flags = VK_SHADER_STAGE_VERTEX_BIT; + break; + case GPUShaderStage::kFragment: + stage_flags = VK_SHADER_STAGE_FRAGMENT_BIT; + break; + default: + stage_flags = VK_SHADER_STAGE_ALL; + break; + } + + // Extract uniform buffer bindings + for (const auto& uniform_binding : reflection.uniform_bindings) { + DescriptorBinding binding; + binding.binding = uniform_binding.binding; + binding.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + binding.count = 1; + binding.stage_flags = stage_flags; + + bindings.push_back(binding); + LOGI("Added uniform buffer binding: set=%d, binding=%d, name=%s", + uniform_binding.set, uniform_binding.binding, + uniform_binding.name.c_str()); + } + + // Extract texture bindings + for (const auto& texture_binding : reflection.texture_bindings) { + DescriptorBinding binding; + binding.binding = texture_binding.binding; + binding.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + binding.count = 1; + binding.stage_flags = stage_flags; + + bindings.push_back(binding); + LOGI("Added texture binding: set=%d, binding=%d, name=%s", + texture_binding.set, texture_binding.binding, + texture_binding.name.c_str()); + } + + // Extract sampler bindings + for (const auto& sampler_binding : reflection.sampler_bindings) { + DescriptorBinding binding; + binding.binding = sampler_binding.binding; + binding.type = VK_DESCRIPTOR_TYPE_SAMPLER; + binding.count = 1; + binding.stage_flags = stage_flags; + + bindings.push_back(binding); + LOGI("Added sampler binding: set=%d, binding=%d, name=%s", + sampler_binding.set, sampler_binding.binding, + sampler_binding.name.c_str()); + } + + return bindings; +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_descriptor_set_vk.hpp b/src/gpu/vk/gpu_descriptor_set_vk.hpp new file mode 100644 index 00000000..857b6273 --- /dev/null +++ b/src/gpu/vk/gpu_descriptor_set_vk.hpp @@ -0,0 +1,99 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_DESCRIPTOR_SET_VK_HPP +#define SRC_GPU_VK_GPU_DESCRIPTOR_SET_VK_HPP + +#include + +#include +#include +#include + +#include "src/gpu/gpu_buffer.hpp" +#include "src/gpu/gpu_sampler.hpp" +#include "src/gpu/gpu_texture.hpp" + +namespace skity { + +class GPUDeviceVk; +struct SPIRVReflectionInfo; + +struct DescriptorBinding { + uint32_t binding; + VkDescriptorType type; + uint32_t count; + VkShaderStageFlags stage_flags; +}; + +class GPUDescriptorSetVk { + public: + GPUDescriptorSetVk(GPUDeviceVk* device); + ~GPUDescriptorSetVk(); + + bool Initialize(const std::vector& bindings); + void Destroy(); + + // Bind resources to descriptor set + void BindBuffer(uint32_t binding, GPUBuffer* buffer, size_t offset = 0, + size_t range = VK_WHOLE_SIZE); + void BindTexture(uint32_t binding, GPUTexture* texture, + GPUSampler* sampler = nullptr); + void BindSampler(uint32_t binding, GPUSampler* sampler); + + // Update all bindings and prepare for use + bool UpdateDescriptorSet(); + + VkDescriptorSetLayout GetLayout() const { return descriptor_set_layout_; } + VkDescriptorSet GetDescriptorSet() const { return descriptor_set_; } + + private: + bool CreateDescriptorSetLayout( + const std::vector& bindings); + bool CreateDescriptorPool(); + bool AllocateDescriptorSet(); + + GPUDeviceVk* device_ = nullptr; + VkDescriptorSetLayout descriptor_set_layout_ = VK_NULL_HANDLE; + VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE; + VkDescriptorSet descriptor_set_ = VK_NULL_HANDLE; + + std::vector bindings_; + std::vector write_descriptor_sets_; + std::vector buffer_infos_; + std::vector image_infos_; + + bool initialized_ = false; +}; + +// Utility class for managing multiple descriptor sets +class GPUDescriptorManagerVk { + public: + GPUDescriptorManagerVk(GPUDeviceVk* device); + ~GPUDescriptorManagerVk(); + + // Create a descriptor set with specified bindings + std::shared_ptr CreateDescriptorSet( + const std::vector& bindings); + + // Create descriptor set layout for pipeline creation + VkDescriptorSetLayout CreateDescriptorSetLayout( + const std::vector& bindings); + + // Create descriptor set from SPIRV reflection information + std::shared_ptr CreateDescriptorSetFromReflection( + const SPIRVReflectionInfo& reflection); + + // Convert SPIRV reflection to descriptor bindings + static std::vector ExtractBindingsFromReflection( + const SPIRVReflectionInfo& reflection); + + private: + GPUDeviceVk* device_ = nullptr; + std::vector> descriptor_sets_; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_DESCRIPTOR_SET_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/gpu_device_vk.cc b/src/gpu/vk/gpu_device_vk.cc new file mode 100644 index 00000000..8b66b879 --- /dev/null +++ b/src/gpu/vk/gpu_device_vk.cc @@ -0,0 +1,531 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_device_vk.hpp" + +#include +#include + +#include "src/gpu/vk/gpu_buffer_vk.hpp" +#include "src/gpu/vk/gpu_command_buffer_vk.hpp" +#include "src/gpu/vk/gpu_pipeline_cache_vk.hpp" +#include "src/gpu/vk/gpu_render_pipeline_vk.hpp" +#include "src/gpu/vk/gpu_sampler_vk.hpp" +#include "src/gpu/vk/gpu_shader_function_vk.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/gpu/vk/vk_interface.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUDeviceVk::GPUDeviceVk() = default; + +GPUDeviceVk::~GPUDeviceVk() { + // Destroy pipeline cache first (before device) + pipeline_cache_.reset(); + + if (allocator_ != VK_NULL_HANDLE) { + vmaDestroyAllocator(allocator_); + } + + if (command_pool_ != VK_NULL_HANDLE) { + vkDestroyCommandPool(device_, command_pool_, nullptr); + } + + if (default_render_pass_ != VK_NULL_HANDLE) { + vkDestroyRenderPass(device_, default_render_pass_, nullptr); + } + if (depth_stencil_render_pass_ != VK_NULL_HANDLE) { + vkDestroyRenderPass(device_, depth_stencil_render_pass_, nullptr); + } + + if (device_ != VK_NULL_HANDLE) { + vkDestroyDevice(device_, nullptr); + } +} + +bool GPUDeviceVk::Init() { + auto* vk_interface = GetVkInterface(); + if (!vk_interface) { + LOGE("Failed to get Vulkan interface"); + return false; + } + + physical_device_ = vk_interface->SelectBestPhysicalDevice(); + if (physical_device_ == VK_NULL_HANDLE) { + LOGE("Failed to find suitable physical device"); + return false; + } + + // Get device properties and features + vkGetPhysicalDeviceProperties(physical_device_, &device_properties_); + vkGetPhysicalDeviceFeatures(physical_device_, &device_features_); + + LOGI("Using Vulkan device: {}", device_properties_.deviceName); + + if (!CreateLogicalDevice()) { + return false; + } + + if (!CreateCommandPool()) { + return false; + } + + if (!CreateVmaAllocator()) { + return false; + } + + // Initialize pipeline cache + pipeline_cache_ = std::make_unique(this); + if (!pipeline_cache_->Initialize()) { + LOGE("Failed to initialize pipeline cache"); + return false; + } + + // Create default render pass for pipeline compatibility + if (!CreateDefaultRenderPass()) { + LOGE("Failed to create default render pass"); + return false; + } + + return true; +} + +bool GPUDeviceVk::CreateLogicalDevice() { + queue_family_indices_ = FindQueueFamilies(physical_device_); + + if (!queue_family_indices_.IsComplete()) { + LOGE("Failed to find required queue families"); + return false; + } + + std::vector queue_create_infos; + std::set unique_queue_families = { + queue_family_indices_.graphics_family, + queue_family_indices_.present_family}; + + float queue_priority = 1.0f; + for (uint32_t queue_family : unique_queue_families) { + VkDeviceQueueCreateInfo queue_create_info{}; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info.queueFamilyIndex = queue_family; + queue_create_info.queueCount = 1; + queue_create_info.pQueuePriorities = &queue_priority; + queue_create_infos.push_back(queue_create_info); + } + + VkPhysicalDeviceFeatures device_features{}; + device_features.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_info.queueCreateInfoCount = + static_cast(queue_create_infos.size()); + create_info.pQueueCreateInfos = queue_create_infos.data(); + create_info.pEnabledFeatures = &device_features; + + auto* vk_interface = GetVkInterface(); + auto device_extensions = + vk_interface->GetRequiredDeviceExtensions(physical_device_); + create_info.enabledExtensionCount = + static_cast(device_extensions.size()); + create_info.ppEnabledExtensionNames = device_extensions.data(); + + // Note: Device-level validation layers are deprecated since Vulkan 1.0.13 + // Validation layers are now only enabled at instance level + create_info.enabledLayerCount = 0; + create_info.ppEnabledLayerNames = nullptr; + + VkResult result = + vkCreateDevice(physical_device_, &create_info, nullptr, &device_); + if (result != VK_SUCCESS) { + LOGE("Failed to create logical device: {}", result); + return false; + } + + // Load device functions + volkLoadDevice(device_); + + // Check for extension support + CheckExtensionSupport(); + + vkGetDeviceQueue(device_, queue_family_indices_.graphics_family, 0, + &graphics_queue_); + vkGetDeviceQueue(device_, queue_family_indices_.present_family, 0, + &present_queue_); + + return true; +} + +bool GPUDeviceVk::CreateCommandPool() { + VkCommandPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + pool_info.queueFamilyIndex = queue_family_indices_.graphics_family; + + VkResult result = + vkCreateCommandPool(device_, &pool_info, nullptr, &command_pool_); + if (result != VK_SUCCESS) { + LOGE("Failed to create command pool: {}", result); + return false; + } + + return true; +} + +bool GPUDeviceVk::CreateVmaAllocator() { + VmaVulkanFunctions vulkan_functions = {}; + vulkan_functions.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + vulkan_functions.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocator_info = {}; + allocator_info.physicalDevice = physical_device_; + allocator_info.device = device_; + allocator_info.instance = GetVkInterface()->GetInstance(); + allocator_info.pVulkanFunctions = &vulkan_functions; + + VkResult result = vmaCreateAllocator(&allocator_info, &allocator_); + if (result != VK_SUCCESS) { + LOGE("Failed to create VMA allocator: {}", result); + return false; + } + + return true; +} + +QueueFamilyIndices GPUDeviceVk::FindQueueFamilies( + VkPhysicalDevice device) const { + QueueFamilyIndices indices; + + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, + nullptr); + + std::vector queue_families(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, + queue_families.data()); + + int i = 0; + for (const auto& queue_family : queue_families) { + if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphics_family = i; + } + + if (queue_family.queueFlags & VK_QUEUE_COMPUTE_BIT) { + indices.compute_family = i; + } + + if (queue_family.queueFlags & VK_QUEUE_TRANSFER_BIT) { + indices.transfer_family = i; + } + + // For now, assume graphics queue can also present + // In a real implementation, we'd check actual surface support + if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.present_family = i; + } + + if (indices.IsComplete()) { + break; + } + + i++; + } + + return indices; +} + +bool GPUDeviceVk::CheckDeviceExtensionSupport(VkPhysicalDevice device) const { + uint32_t extension_count; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, + nullptr); + + std::vector available_extensions(extension_count); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, + available_extensions.data()); + + auto* vk_interface = GetVkInterface(); + auto required_extensions = vk_interface->GetRequiredDeviceExtensions(); + + std::set required_extensions_set(required_extensions.begin(), + required_extensions.end()); + + for (const auto& extension : available_extensions) { + required_extensions_set.erase(extension.extensionName); + } + + return required_extensions_set.empty(); +} + +std::unique_ptr GPUDeviceVk::CreateBuffer(GPUBufferUsageMask usage) { + auto buffer = std::make_unique(this, usage); + return std::move(buffer); +} + +std::shared_ptr GPUDeviceVk::CreateShaderFunction( + const GPUShaderFunctionDescriptor& desc) { + return GPUShaderFunctionVk::Create(this, desc); +} + +std::unique_ptr GPUDeviceVk::CreateRenderPipeline( + const GPURenderPipelineDescriptor& desc) { + if (pipeline_cache_) { + return pipeline_cache_->GetOrCreatePipeline(desc); + } + return GPURenderPipelineVk::Create(this, desc); +} + +std::unique_ptr GPUDeviceVk::ClonePipeline( + GPURenderPipeline* base, const GPURenderPipelineDescriptor& desc) { + if (!base->IsValid()) { + return nullptr; + } + + auto variant_desc = desc; + + // Clone the pipeline with new descriptor - this will create a new Vulkan + // pipeline with the same shaders but different render state + if (pipeline_cache_) { + return pipeline_cache_->GetOrCreatePipeline(variant_desc); + } + + return GPURenderPipelineVk::Create(this, variant_desc); +} + +std::shared_ptr GPUDeviceVk::CreateCommandBuffer() { + auto command_buffer = std::make_shared(this); + if (!command_buffer->Initialize()) { + LOGE("Failed to initialize command buffer"); + return nullptr; + } + return command_buffer; +} + +std::shared_ptr GPUDeviceVk::CreateSampler( + const GPUSamplerDescriptor& desc) { + return GPUSamplerVk::Create(this, desc); +} + +std::shared_ptr GPUDeviceVk::CreateTexture( + const GPUTextureDescriptor& desc) { + return GPUTextureVk::Create(this, desc); +} + +bool GPUDeviceVk::CanUseMSAA() { + return true; // Vulkan generally supports MSAA +} + +uint32_t GPUDeviceVk::GetBufferAlignment() { + return static_cast( + device_properties_.limits.minUniformBufferOffsetAlignment); +} + +uint32_t GPUDeviceVk::GetMaxTextureSize() { + return device_properties_.limits.maxImageDimension2D; +} + +VkCommandBuffer GPUDeviceVk::BeginSingleTimeCommands() { + VkCommandBufferAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandPool = command_pool_; + alloc_info.commandBufferCount = 1; + + VkCommandBuffer command_buffer; + vkAllocateCommandBuffers(device_, &alloc_info, &command_buffer); + + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(command_buffer, &begin_info); + + return command_buffer; +} + +void GPUDeviceVk::EndSingleTimeCommands(VkCommandBuffer command_buffer) { + vkEndCommandBuffer(command_buffer); + + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer; + + vkQueueSubmit(graphics_queue_, 1, &submit_info, VK_NULL_HANDLE); + vkQueueWaitIdle(graphics_queue_); + + vkFreeCommandBuffers(device_, command_pool_, 1, &command_buffer); +} + +void GPUDeviceVk::CheckExtensionSupport() { + // Check for VK_KHR_synchronization2 support + uint32_t extension_count; + vkEnumerateDeviceExtensionProperties(physical_device_, nullptr, + &extension_count, nullptr); + + std::vector available_extensions(extension_count); + vkEnumerateDeviceExtensionProperties( + physical_device_, nullptr, &extension_count, available_extensions.data()); + + for (const auto& extension : available_extensions) { + if (strcmp(extension.extensionName, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) == 0) { + synchronization2_supported_ = true; + break; + } + } + + if (synchronization2_supported_) { + LOGI("VK_KHR_synchronization2 extension is supported"); + } else { + LOGI( + "VK_KHR_synchronization2 extension not supported, using legacy " + "barriers"); + } +} + +bool GPUDeviceVk::CreateDefaultRenderPass() { + // Create a basic render pass compatible with most common surface formats + // This will be used for pipeline creation to satisfy Vulkan's requirement + // that pipelines must be compatible with a render pass + + VkAttachmentDescription color_attachment{}; + color_attachment.format = + VK_FORMAT_B8G8R8A8_UNORM; // Use linear format to match GL + LOGI("Default render pass format: %d (VK_FORMAT_B8G8R8A8_UNORM)", + VK_FORMAT_B8G8R8A8_UNORM); + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference color_attachment_ref{}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = 1; + render_pass_info.pAttachments = &color_attachment; + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + VkResult result = vkCreateRenderPass(device_, &render_pass_info, nullptr, + &default_render_pass_); + if (result != VK_SUCCESS) { + LOGE("Failed to create default render pass: %d", result); + return false; + } + + LOGI("Created default render pass for pipeline compatibility"); + return true; +} + +bool GPUDeviceVk::CreateDepthStencilRenderPass() { + // Color attachment (match actual render pass format) + VkAttachmentDescription color_attachment{}; + color_attachment.format = + VK_FORMAT_B8G8R8A8_UNORM; // Match actual render pass format + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + // Depth/stencil attachment for pipeline compatibility + VkAttachmentDescription depth_stencil_attachment{}; + depth_stencil_attachment.format = + VK_FORMAT_D24_UNORM_S8_UINT; // Match actual render pass format (even if + // not supported on this device) + depth_stencil_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + depth_stencil_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_stencil_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_stencil_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_stencil_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_stencil_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depth_stencil_attachment.finalLayout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + // References + VkAttachmentReference color_ref{}; + color_ref.attachment = 0; + color_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depth_stencil_ref{}; + depth_stencil_ref.attachment = 1; + depth_stencil_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + // Subpass + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_ref; + subpass.pDepthStencilAttachment = &depth_stencil_ref; + + // Dependency (match actual render pass exactly) + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkAttachmentDescription attachments[] = {color_attachment, + depth_stencil_attachment}; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = 2; // Color + depth/stencil + render_pass_info.pAttachments = attachments; + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + VkResult result = vkCreateRenderPass(device_, &render_pass_info, nullptr, + &depth_stencil_render_pass_); + if (result != VK_SUCCESS) { + LOGE("Failed to create depth/stencil render pass: %d", result); + return false; + } + + return true; +} + +VkRenderPass GPUDeviceVk::GetCompatibleRenderPass(VkFormat format, + bool needsDepthStencil) { + if (needsDepthStencil) { + if (depth_stencil_render_pass_ == VK_NULL_HANDLE) { + if (!CreateDepthStencilRenderPass()) { + return default_render_pass_; + } + } + return depth_stencil_render_pass_; + } else { + return default_render_pass_; + } +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_device_vk.hpp b/src/gpu/vk/gpu_device_vk.hpp new file mode 100644 index 00000000..16a841c1 --- /dev/null +++ b/src/gpu/vk/gpu_device_vk.hpp @@ -0,0 +1,125 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_DEVICE_VK_HPP +#define SRC_GPU_VK_GPU_DEVICE_VK_HPP + +#include +#include + +#include +#include + +#include "src/gpu/gpu_device.hpp" + +namespace skity { + +class GPUPipelineCacheVk; + +struct QueueFamilyIndices { + uint32_t graphics_family = UINT32_MAX; + uint32_t present_family = UINT32_MAX; + uint32_t compute_family = UINT32_MAX; + uint32_t transfer_family = UINT32_MAX; + + bool IsComplete() const { + return graphics_family != UINT32_MAX && present_family != UINT32_MAX; + } +}; + +class GPUDeviceVk : public GPUDevice { + public: + GPUDeviceVk(); + ~GPUDeviceVk() override; + + bool Init(); + + std::unique_ptr CreateBuffer(GPUBufferUsageMask usage) override; + + std::shared_ptr CreateShaderFunction( + const GPUShaderFunctionDescriptor& desc) override; + + std::unique_ptr CreateRenderPipeline( + const GPURenderPipelineDescriptor& desc) override; + + std::unique_ptr ClonePipeline( + GPURenderPipeline* base, + const GPURenderPipelineDescriptor& desc) override; + + std::shared_ptr CreateCommandBuffer() override; + + std::shared_ptr CreateSampler( + const GPUSamplerDescriptor& desc) override; + + std::shared_ptr CreateTexture( + const GPUTextureDescriptor& desc) override; + + bool CanUseMSAA() override; + + uint32_t GetBufferAlignment() override; + + uint32_t GetMaxTextureSize() override; + + // Vulkan-specific getters + VkDevice GetDevice() const { return device_; } + VkPhysicalDevice GetPhysicalDevice() const { return physical_device_; } + VkQueue GetGraphicsQueue() const { return graphics_queue_; } + VkQueue GetPresentQueue() const { return present_queue_; } + VkCommandPool GetCommandPool() const { return command_pool_; } + VmaAllocator GetAllocator() const { return allocator_; } + const QueueFamilyIndices& GetQueueFamilyIndices() const { + return queue_family_indices_; + } + + // Command buffer utilities + VkCommandBuffer BeginSingleTimeCommands(); + void EndSingleTimeCommands(VkCommandBuffer command_buffer); + + // Pipeline cache access + GPUPipelineCacheVk* GetPipelineCache() const { return pipeline_cache_.get(); } + + // Extension support queries + bool HasSynchronization2Support() const { + return synchronization2_supported_; + } + + // Render pass creation for pipeline compatibility + VkRenderPass GetCompatibleRenderPass( + VkFormat format = VK_FORMAT_B8G8R8A8_SRGB, + bool needsDepthStencil = false); + + private: + bool CreateLogicalDevice(); + bool CreateCommandPool(); + bool CreateVmaAllocator(); + QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device) const; + bool CheckDeviceExtensionSupport(VkPhysicalDevice device) const; + void CheckExtensionSupport(); + + private: + VkPhysicalDevice physical_device_ = VK_NULL_HANDLE; + VkDevice device_ = VK_NULL_HANDLE; + VkQueue graphics_queue_ = VK_NULL_HANDLE; + VkQueue present_queue_ = VK_NULL_HANDLE; + VkCommandPool command_pool_ = VK_NULL_HANDLE; + VmaAllocator allocator_ = VK_NULL_HANDLE; + std::unique_ptr pipeline_cache_; + + QueueFamilyIndices queue_family_indices_; + VkPhysicalDeviceProperties device_properties_; + VkPhysicalDeviceFeatures device_features_; + + // Extension support flags + bool synchronization2_supported_ = false; + + // Cache for compatible render passes + VkRenderPass default_render_pass_ = VK_NULL_HANDLE; + VkRenderPass depth_stencil_render_pass_ = VK_NULL_HANDLE; + bool CreateDefaultRenderPass(); + bool CreateDepthStencilRenderPass(); +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_DEVICE_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/gpu_pipeline_cache_vk.cc b/src/gpu/vk/gpu_pipeline_cache_vk.cc new file mode 100644 index 00000000..a5d19c38 --- /dev/null +++ b/src/gpu/vk/gpu_pipeline_cache_vk.cc @@ -0,0 +1,259 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_pipeline_cache_vk.hpp" + +#include +#include + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_render_pipeline_vk.hpp" +#include "src/gpu/vk/gpu_shader_function_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUPipelineCacheVk::GPUPipelineCacheVk(GPUDeviceVk* device) : device_(device) {} + +GPUPipelineCacheVk::~GPUPipelineCacheVk() { Destroy(); } + +bool GPUPipelineCacheVk::Initialize() { + if (initialized_) { + return true; + } + + VkDevice vk_device = device_->GetDevice(); + + // Create Vulkan pipeline cache + VkPipelineCacheCreateInfo cache_info = {}; + cache_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + cache_info.initialDataSize = 0; + cache_info.pInitialData = nullptr; + + VkResult result = vkCreatePipelineCache(vk_device, &cache_info, nullptr, + &vk_pipeline_cache_); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan pipeline cache: %d", result); + return false; + } + + initialized_ = true; + LOGI("Pipeline cache initialized successfully"); + return true; +} + +void GPUPipelineCacheVk::Destroy() { + if (!initialized_) { + return; + } + + VkDevice vk_device = device_->GetDevice(); + + if (vk_pipeline_cache_ != VK_NULL_HANDLE) { + vkDestroyPipelineCache(vk_device, vk_pipeline_cache_, nullptr); + vk_pipeline_cache_ = VK_NULL_HANDLE; + } + + pipeline_cache_.clear(); + initialized_ = false; + + LOGI( + "Pipeline cache destroyed - Final stats: %zu hits, %zu misses, %zu total " + "pipelines", + cache_hits_, cache_misses_, pipeline_cache_.size()); +} + +std::unique_ptr GPUPipelineCacheVk::GetOrCreatePipeline( + const GPURenderPipelineDescriptor& desc) { + // Create key for this pipeline configuration + PipelineKey key = CreatePipelineKey(desc); + + // Check if pipeline already exists in cache + auto it = pipeline_cache_.find(key); + if (it != pipeline_cache_.end()) { + cache_hits_++; + LOGI("Pipeline cache hit! (Total hits: %zu)", cache_hits_); + // For caching, we need to clone the pipeline since we can't return the same + // unique_ptr In practice, this would involve proper pipeline sharing or + // using a different caching strategy + return GPURenderPipelineVk::Create(device_, desc); + } + + // Cache miss - create new pipeline + cache_misses_++; + LOGI("Pipeline cache miss - creating new pipeline (Total misses: %zu)", + cache_misses_); + + auto pipeline = GPURenderPipelineVk::Create(device_, desc); + if (!pipeline) { + LOGE("Failed to create pipeline for caching"); + return nullptr; + } + + // Store a reference in cache for future hit detection + // Note: This is a simplified caching approach + pipeline_cache_[key] = std::shared_ptr( + pipeline.get(), [](GPURenderPipelineVk*) {}); + + LOGI("Pipeline cached successfully. Cache size: %zu", pipeline_cache_.size()); + return pipeline; +} + +bool GPUPipelineCacheVk::SaveCache(const std::string& file_path) { + if (!initialized_ || vk_pipeline_cache_ == VK_NULL_HANDLE) { + LOGE("Pipeline cache not initialized for saving"); + return false; + } + + VkDevice vk_device = device_->GetDevice(); + + // Get cache data size + size_t cache_size = 0; + VkResult result = vkGetPipelineCacheData(vk_device, vk_pipeline_cache_, + &cache_size, nullptr); + if (result != VK_SUCCESS || cache_size == 0) { + LOGW("No pipeline cache data to save"); + return false; + } + + // Get cache data + std::vector cache_data(cache_size); + result = vkGetPipelineCacheData(vk_device, vk_pipeline_cache_, &cache_size, + cache_data.data()); + if (result != VK_SUCCESS) { + LOGE("Failed to get pipeline cache data: %d", result); + return false; + } + + // Write to file + std::ofstream file(file_path, std::ios::binary); + if (!file.is_open()) { + LOGE("Failed to open cache file for writing: %s", file_path.c_str()); + return false; + } + + file.write(reinterpret_cast(cache_data.data()), cache_size); + file.close(); + + LOGI("Pipeline cache saved to %s (%zu bytes)", file_path.c_str(), cache_size); + return true; +} + +bool GPUPipelineCacheVk::LoadCache(const std::string& file_path) { + std::ifstream file(file_path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + LOGW("Pipeline cache file not found: %s", file_path.c_str()); + return false; + } + + size_t file_size = file.tellg(); + if (file_size == 0) { + LOGW("Empty pipeline cache file: %s", file_path.c_str()); + return false; + } + + file.seekg(0); + std::vector cache_data(file_size); + file.read(reinterpret_cast(cache_data.data()), file_size); + file.close(); + + // Destroy existing cache and create new one with loaded data + if (vk_pipeline_cache_ != VK_NULL_HANDLE) { + vkDestroyPipelineCache(device_->GetDevice(), vk_pipeline_cache_, nullptr); + } + + VkPipelineCacheCreateInfo cache_info = {}; + cache_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + cache_info.initialDataSize = file_size; + cache_info.pInitialData = cache_data.data(); + + VkResult result = vkCreatePipelineCache(device_->GetDevice(), &cache_info, + nullptr, &vk_pipeline_cache_); + if (result != VK_SUCCESS) { + LOGE("Failed to create pipeline cache with loaded data: %d", result); + return false; + } + + LOGI("Pipeline cache loaded from %s (%zu bytes)", file_path.c_str(), + file_size); + return true; +} + +void GPUPipelineCacheVk::ClearCache() { + pipeline_cache_.clear(); + cache_hits_ = 0; + cache_misses_ = 0; + LOGI("Pipeline cache cleared"); +} + +PipelineKey GPUPipelineCacheVk::CreatePipelineKey( + const GPURenderPipelineDescriptor& desc) { + PipelineKey key; + + // Hash shader functions + if (desc.vertex_function) { + key.vertex_shader_hash = desc.vertex_function->GetLabel(); + } + if (desc.fragment_function) { + key.fragment_shader_hash = desc.fragment_function->GetLabel(); + } + + // Hash vertex layout + key.vertex_layout_hash = HashVertexLayout(desc); + + // Hash render state + key.render_state_hash = HashRenderState(desc); + + return key; +} + +uint32_t GPUPipelineCacheVk::HashRenderState( + const GPURenderPipelineDescriptor& desc) { + // Simple hash of render state properties + uint32_t hash = 0; + hash ^= std::hash{}(static_cast(desc.target.format)); + hash ^= std::hash{}(static_cast(desc.target.src_blend_factor)) << 1; + hash ^= std::hash{}(static_cast(desc.target.dst_blend_factor)) << 2; + hash ^= std::hash{}(desc.target.write_mask) << 3; + hash ^= std::hash{}(desc.sample_count) << 4; + hash ^= std::hash{}(desc.depth_stencil.enable_depth) << 5; + hash ^= std::hash{}(desc.depth_stencil.enable_stencil) << 6; + return hash; +} + +std::vector GPUPipelineCacheVk::HashVertexLayout( + const GPURenderPipelineDescriptor& desc) { + std::vector hash_data; + + for (const auto& buffer : desc.buffers) { + // Hash stride + auto stride_bytes = reinterpret_cast(&buffer.array_stride); + hash_data.insert(hash_data.end(), stride_bytes, + stride_bytes + sizeof(buffer.array_stride)); + + // Hash step mode + uint8_t step_mode = static_cast(buffer.step_mode); + hash_data.push_back(step_mode); + + // Hash attributes + for (const auto& attr : buffer.attributes) { + auto format_bytes = reinterpret_cast(&attr.format); + hash_data.insert(hash_data.end(), format_bytes, + format_bytes + sizeof(attr.format)); + + auto offset_bytes = reinterpret_cast(&attr.offset); + hash_data.insert(hash_data.end(), offset_bytes, + offset_bytes + sizeof(attr.offset)); + + auto location_bytes = + reinterpret_cast(&attr.shader_location); + hash_data.insert(hash_data.end(), location_bytes, + location_bytes + sizeof(attr.shader_location)); + } + } + + return hash_data; +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_pipeline_cache_vk.hpp b/src/gpu/vk/gpu_pipeline_cache_vk.hpp new file mode 100644 index 00000000..0af3cb59 --- /dev/null +++ b/src/gpu/vk/gpu_pipeline_cache_vk.hpp @@ -0,0 +1,97 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_PIPELINE_CACHE_VK_HPP +#define SRC_GPU_VK_GPU_PIPELINE_CACHE_VK_HPP + +#include + +#include +#include +#include +#include + +namespace skity { + +class GPUDeviceVk; +class GPURenderPipelineVk; +struct GPURenderPipelineDescriptor; + +// Hash key for pipeline caching +struct PipelineKey { + std::string vertex_shader_hash; + std::string fragment_shader_hash; + std::vector vertex_layout_hash; + uint32_t render_state_hash; + + bool operator==(const PipelineKey& other) const { + return vertex_shader_hash == other.vertex_shader_hash && + fragment_shader_hash == other.fragment_shader_hash && + vertex_layout_hash == other.vertex_layout_hash && + render_state_hash == other.render_state_hash; + } +}; + +struct PipelineKeyHash { + size_t operator()(const PipelineKey& key) const { + size_t h1 = std::hash{}(key.vertex_shader_hash); + size_t h2 = std::hash{}(key.fragment_shader_hash); + size_t h3 = std::hash{}(key.render_state_hash); + + // Simple hash combination + return h1 ^ (h2 << 1) ^ (h3 << 2); + } +}; + +class GPUPipelineCacheVk { + public: + explicit GPUPipelineCacheVk(GPUDeviceVk* device); + ~GPUPipelineCacheVk(); + + bool Initialize(); + void Destroy(); + + // Get or create a cached pipeline + std::unique_ptr GetOrCreatePipeline( + const GPURenderPipelineDescriptor& desc); + + // Save cache to disk + bool SaveCache(const std::string& file_path); + + // Load cache from disk + bool LoadCache(const std::string& file_path); + + // Clear all cached pipelines + void ClearCache(); + + // Get cache statistics + size_t GetCacheSize() const { return pipeline_cache_.size(); } + size_t GetHitCount() const { return cache_hits_; } + size_t GetMissCount() const { return cache_misses_; } + + VkPipelineCache GetVkPipelineCache() const { return vk_pipeline_cache_; } + + private: + PipelineKey CreatePipelineKey(const GPURenderPipelineDescriptor& desc); + uint32_t HashRenderState(const GPURenderPipelineDescriptor& desc); + std::vector HashVertexLayout( + const GPURenderPipelineDescriptor& desc); + + GPUDeviceVk* device_ = nullptr; + VkPipelineCache vk_pipeline_cache_ = VK_NULL_HANDLE; + + std::unordered_map, + PipelineKeyHash> + pipeline_cache_; + + // Statistics + mutable size_t cache_hits_ = 0; + mutable size_t cache_misses_ = 0; + + bool initialized_ = false; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_PIPELINE_CACHE_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/gpu_render_pass_vk.cc b/src/gpu/vk/gpu_render_pass_vk.cc new file mode 100644 index 00000000..89c5ba76 --- /dev/null +++ b/src/gpu/vk/gpu_render_pass_vk.cc @@ -0,0 +1,471 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_render_pass_vk.hpp" + +#include "src/gpu/vk/gpu_buffer_vk.hpp" +#include "src/gpu/vk/gpu_command_buffer_vk.hpp" +#include "src/gpu/vk/gpu_descriptor_set_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_render_pipeline_vk.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPURenderPassVk::GPURenderPassVk(GPUCommandBufferVk* command_buffer, + const GPURenderPassDescriptor& desc) + : GPURenderPass(desc), + command_buffer_(command_buffer), + vk_render_pass_(VK_NULL_HANDLE), + vk_framebuffer_(VK_NULL_HANDLE), + render_pass_created_(false) {} + +GPURenderPassVk::~GPURenderPassVk() { + auto* device = command_buffer_->GetDevice(); + VkDevice vk_device = device->GetDevice(); + + if (vk_framebuffer_ != VK_NULL_HANDLE) { + vkDestroyFramebuffer(vk_device, vk_framebuffer_, nullptr); + } + + if (vk_render_pass_ != VK_NULL_HANDLE) { + vkDestroyRenderPass(vk_device, vk_render_pass_, nullptr); + } +} + +void GPURenderPassVk::EncodeCommands(std::optional viewport, + std::optional scissor) { + const auto& commands = GetCommands(); + LOGI("Encoding %zu render commands", commands.size()); + + // For now, we'll implement a basic render pass without actual attachments + // In a full implementation, we would: + // 1. Create VkRenderPass and VkFramebuffer based on descriptor + // 2. Begin render pass with vkCmdBeginRenderPass + // 3. Set viewport and scissor + // 4. Execute each render command + // 5. End render pass with vkCmdEndRenderPass + + if (!render_pass_created_) { + if (!CreateVkRenderPass()) { + LOGE("Failed to create Vulkan render pass"); + return; + } + render_pass_created_ = true; + } + + // Get the command buffer + VkCommandBuffer cmd_buffer = command_buffer_->GetVkCommandBuffer(); + + // Set viewport and scissor + SetupViewportAndScissor(viewport, scissor); + + // Execute render commands + ExecuteCommands(); + + LOGI("Render pass encoding completed"); +} + +bool GPURenderPassVk::CreateVkRenderPass() { + const auto& desc = GetDescriptor(); + auto* device = command_buffer_->GetDevice(); + VkDevice vk_device = device->GetDevice(); + + // Check if we have a color attachment + if (!desc.color_attachment.texture) { + LOGE("No color attachment texture provided for render pass"); + return false; + } + + // Get the Vulkan texture from the color attachment + auto* texture_vk = + static_cast(desc.color_attachment.texture.get()); + if (!texture_vk) { + LOGE("Failed to cast to Vulkan texture"); + return false; + } + + // Get texture format from the actual texture + VkFormat texture_format = texture_vk->GetVkFormat(); + LOGI("Render pass texture format: %d (expected swapchain compatible format)", + texture_format); + + // Create render pass with color attachment + VkAttachmentDescription color_attachment{}; + color_attachment.format = texture_format; + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = (desc.color_attachment.load_op == GPULoadOp::kClear) + ? VK_ATTACHMENT_LOAD_OP_CLEAR + : VK_ATTACHMENT_LOAD_OP_LOAD; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + // For swapchain images, final layout must be PRESENT_SRC_KHR for presentation + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference color_attachment_ref{}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + // Add depth/stencil attachment for path rendering (like GL backend) + VkAttachmentDescription depth_stencil_attachment{}; + VkAttachmentReference depth_stencil_ref{}; + std::vector attachments = {color_attachment}; + + bool has_depth_stencil = desc.stencil_attachment.texture != nullptr || + desc.depth_attachment.texture != nullptr; + if (has_depth_stencil) { + depth_stencil_attachment.format = + VK_FORMAT_D24_UNORM_S8_UINT; // Combined depth-stencil format + depth_stencil_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + depth_stencil_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_stencil_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_stencil_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_stencil_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_stencil_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depth_stencil_attachment.finalLayout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + depth_stencil_ref.attachment = 1; // Second attachment + depth_stencil_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + attachments.push_back(depth_stencil_attachment); + } + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + if (has_depth_stencil) { + subpass.pDepthStencilAttachment = &depth_stencil_ref; + } + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = static_cast(attachments.size()); + render_pass_info.pAttachments = attachments.data(); + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + VkResult result = vkCreateRenderPass(vk_device, &render_pass_info, nullptr, + &vk_render_pass_); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan render pass: %d", result); + return false; + } + + // Create framebuffer + VkImageView image_view = texture_vk->GetVkImageView(); + if (image_view == VK_NULL_HANDLE) { + LOGE("Texture has no image view for framebuffer creation"); + return false; + } + + std::vector framebuffer_attachments = {image_view}; + + // Add depth/stencil attachment if available + if (has_depth_stencil && desc.stencil_attachment.texture) { + auto* depth_stencil_texture_vk = + static_cast(desc.stencil_attachment.texture.get()); + if (depth_stencil_texture_vk) { + VkImageView depth_stencil_view = + depth_stencil_texture_vk->GetVkImageView(); + if (depth_stencil_view != VK_NULL_HANDLE) { + framebuffer_attachments.push_back(depth_stencil_view); + } + } + } + + VkFramebufferCreateInfo framebuffer_info{}; + framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_info.renderPass = vk_render_pass_; + framebuffer_info.attachmentCount = + static_cast(framebuffer_attachments.size()); + framebuffer_info.pAttachments = framebuffer_attachments.data(); + framebuffer_info.width = desc.GetTargetWidth(); + framebuffer_info.height = desc.GetTargetHeight(); + framebuffer_info.layers = 1; + + result = vkCreateFramebuffer(vk_device, &framebuffer_info, nullptr, + &vk_framebuffer_); + if (result != VK_SUCCESS) { + LOGE("Failed to create framebuffer: %d", result); + return false; + } + + LOGI("Successfully created Vulkan render pass and framebuffer (%ux%u)", + desc.GetTargetWidth(), desc.GetTargetHeight()); + return true; +} + +void GPURenderPassVk::SetupViewportAndScissor( + std::optional viewport, + std::optional scissor) { + VkCommandBuffer cmd_buffer = command_buffer_->GetVkCommandBuffer(); + + // Set viewport if provided + if (viewport.has_value()) { + VkViewport vk_viewport = {}; + vk_viewport.x = viewport->x; + // Vulkan viewport Y is from top-left, but we need to handle flipping + // properly + vk_viewport.y = viewport->y; + vk_viewport.width = viewport->width; + vk_viewport.height = viewport->height; + vk_viewport.minDepth = viewport->min_depth; + vk_viewport.maxDepth = viewport->max_depth; + + vkCmdSetViewport(cmd_buffer, 0, 1, &vk_viewport); + } + + // Set scissor if provided + if (scissor.has_value()) { + VkRect2D vk_scissor = {}; + vk_scissor.offset.x = static_cast(scissor->x); + vk_scissor.offset.y = static_cast(scissor->y); + vk_scissor.extent.width = scissor->width; + vk_scissor.extent.height = scissor->height; + + vkCmdSetScissor(cmd_buffer, 0, 1, &vk_scissor); + LOGI("Set scissor: %ux%u at (%u, %u)", scissor->width, scissor->height, + scissor->x, scissor->y); + } +} + +void GPURenderPassVk::ExecuteCommands() { + auto& commands = const_cast&>(GetCommands()); + VkCommandBuffer cmd_buffer = command_buffer_->GetVkCommandBuffer(); + + if (commands.empty()) { + LOGI("No render commands to execute"); + return; + } + + LOGI("Executing %zu render commands", commands.size()); + + // Begin render pass if we have a valid one + if (vk_render_pass_ != VK_NULL_HANDLE && vk_framebuffer_ != VK_NULL_HANDLE) { + const auto& desc = GetDescriptor(); + + // For window surfaces (swapchain), image layout transitions are handled + // differently The swapchain images are already managed by the window + // surface + if (desc.color_attachment.texture) { + auto* texture_vk = + static_cast(desc.color_attachment.texture.get()); + if (texture_vk && texture_vk->GetVkImage() != VK_NULL_HANDLE) { + // Only transition non-swapchain textures + LOGI( + "Skipping layout transition for render target (likely swapchain " + "image)"); + } else { + LOGI( + "No color attachment texture or invalid texture for layout " + "transition"); + } + } + + VkRenderPassBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + begin_info.renderPass = vk_render_pass_; + begin_info.framebuffer = vk_framebuffer_; + begin_info.renderArea.offset = {0, 0}; + begin_info.renderArea.extent = {desc.GetTargetWidth(), + desc.GetTargetHeight()}; + + // Set up clear values for all attachments that need clearing + std::vector clear_values; + + // Color attachment clear value + VkClearValue color_clear = {}; + if (desc.color_attachment.load_op == GPULoadOp::kClear) { + // Use the actual clear color from the attachment + color_clear.color = { + {static_cast(desc.color_attachment.clear_value.r), + static_cast(desc.color_attachment.clear_value.g), + static_cast(desc.color_attachment.clear_value.b), + static_cast(desc.color_attachment.clear_value.a)}}; + } + clear_values.push_back(color_clear); + + // Depth/stencil attachment clear value if present + bool has_depth_stencil = desc.stencil_attachment.texture != nullptr || + desc.depth_attachment.texture != nullptr; + if (has_depth_stencil) { + VkClearValue depth_stencil_clear = {}; + depth_stencil_clear.depthStencil.depth = + desc.depth_attachment.clear_value; // 0.0f + depth_stencil_clear.depthStencil.stencil = + desc.stencil_attachment.clear_value; // 0 + clear_values.push_back(depth_stencil_clear); + } + + begin_info.clearValueCount = static_cast(clear_values.size()); + begin_info.pClearValues = clear_values.data(); + + vkCmdBeginRenderPass(cmd_buffer, &begin_info, VK_SUBPASS_CONTENTS_INLINE); + LOGI("Started render pass (%ux%u)", desc.GetTargetWidth(), + desc.GetTargetHeight()); + } + + // Execute each command + for (size_t i = 0; i < commands.size(); ++i) { + const Command* command = commands[i]; + if (!command || !command->IsValid()) { + LOGW("Skipping invalid command %zu", i); + continue; + } + + ExecuteSingleCommand(cmd_buffer, command); + } + + // End render pass if we began one + if (vk_render_pass_ != VK_NULL_HANDLE && vk_framebuffer_ != VK_NULL_HANDLE) { + vkCmdEndRenderPass(cmd_buffer); + LOGI("Ended render pass"); + } + + LOGI("Completed executing %zu render commands", commands.size()); +} + +void GPURenderPassVk::ExecuteSingleCommand(VkCommandBuffer cmd_buffer, + const Command* command) { + LOGI("ExecuteSingleCommand called"); + if (!command || !command->pipeline) { + LOGW("Invalid command or pipeline"); + return; + } + + // Get the Vulkan pipeline from the command + auto* pipeline_vk = static_cast(command->pipeline); + if (!pipeline_vk || !pipeline_vk->IsValid()) { + LOGW("Invalid Vulkan pipeline"); + return; + } + + // Bind graphics pipeline + VkPipeline vk_pipeline = pipeline_vk->GetVkPipeline(); + vkCmdBindPipeline(cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vk_pipeline); + + // Bind vertex buffer + if (command->vertex_buffer.buffer) { + auto* vertex_buffer_vk = + static_cast(command->vertex_buffer.buffer); + VkBuffer vertex_buffer = vertex_buffer_vk->GetBuffer(); + VkDeviceSize offset = command->vertex_buffer.offset; + vkCmdBindVertexBuffers(cmd_buffer, 0, 1, &vertex_buffer, &offset); + } + + // Bind index buffer + if (command->index_buffer.buffer) { + auto* index_buffer_vk = + static_cast(command->index_buffer.buffer); + VkBuffer index_buffer = index_buffer_vk->GetBuffer(); + VkDeviceSize offset = command->index_buffer.offset; + VkIndexType index_type = VK_INDEX_TYPE_UINT32; // Indices are uint32_t + vkCmdBindIndexBuffer(cmd_buffer, index_buffer, offset, index_type); + } + + // Bind descriptor sets (uniform buffers, textures, samplers) + LOGI( + "Command has %zu uniform bindings, %zu texture_sampler bindings, %zu " + "sampler bindings", + command->uniform_bindings.size(), + command->texture_sampler_bindings.size(), + command->sampler_bindings.size()); + + if (!command->uniform_bindings.empty() || + !command->texture_sampler_bindings.empty() || + !command->sampler_bindings.empty()) { + // Use the pipeline's pre-computed descriptor layout from shader reflection + // But also handle cases where commands have more bindings than shader + // reflection found + + // Use the pipeline's stored bindings that match the SPIRV layout (0,1,2,3) + // This ensures descriptor set layout matches pipeline layout exactly + auto descriptor_set = pipeline_vk->CreateDescriptorSetUsingPipelineLayout(); + if (descriptor_set) { + // Update descriptor set with actual resources from command bindings + for (const auto& uniform_binding : command->uniform_bindings) { + if (uniform_binding.buffer.buffer) { + auto* buffer_vk = + static_cast(uniform_binding.buffer.buffer); + descriptor_set->BindBuffer(uniform_binding.index, buffer_vk, + uniform_binding.buffer.offset, + uniform_binding.buffer.range); + } + } + + for (const auto& texture_binding : command->texture_sampler_bindings) { + if (texture_binding.texture) { + auto* texture_vk = + static_cast(texture_binding.texture.get()); + descriptor_set->BindTexture(texture_binding.index, texture_vk, + texture_binding.sampler.get()); + } + } + + // CRITICAL: Update the descriptor set with bound resources + if (!descriptor_set->UpdateDescriptorSet()) { + LOGE("Failed to update descriptor set"); + } + + // Bind the descriptor set to the command buffer + pipeline_vk->BindDescriptorSet(cmd_buffer, descriptor_set); + LOGI("Bound descriptor set using pipeline's reflection-based layout"); + } else { + LOGW("Failed to create descriptor set using pipeline layout"); + } + } else { + // If there are no bindings, this indicates a problem in the rendering + // pipeline The shaders expect CommonSlot uniform data but none was provided + LOGW( + "No uniform or texture bindings provided - this indicates missing " + "CommonSlot data"); + LOGW("This will likely cause descriptor set validation errors"); + } + + // Set scissor for this command if specified + if (command->scissor_rect.width > 0 && command->scissor_rect.height > 0) { + VkRect2D scissor = {}; + scissor.offset.x = static_cast(command->scissor_rect.x); + scissor.offset.y = static_cast(command->scissor_rect.y); + scissor.extent.width = static_cast(command->scissor_rect.width); + scissor.extent.height = static_cast(command->scissor_rect.height); + + vkCmdSetScissor(cmd_buffer, 0, 1, &scissor); + } + + // Set stencil reference value from command (like GL and Metal backends) + // Only set it if the pipeline has stencil testing enabled and dynamic stencil + // reference + if (pipeline_vk->HasStencilTesting()) { + vkCmdSetStencilReference(cmd_buffer, VK_STENCIL_FACE_FRONT_AND_BACK, + command->stencil_reference); + } + + // Draw indexed + if (command->index_count > 0) { + vkCmdDrawIndexed(cmd_buffer, command->index_count, 1, 0, 0, 0); + LOGI("Drew %u indices", command->index_count); + } else { + LOGW( + "Command has no indices to draw - this means geometry was not " + "generated properly"); + } +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_render_pass_vk.hpp b/src/gpu/vk/gpu_render_pass_vk.hpp new file mode 100644 index 00000000..53c7c313 --- /dev/null +++ b/src/gpu/vk/gpu_render_pass_vk.hpp @@ -0,0 +1,42 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_RENDER_PASS_VK_HPP +#define SRC_GPU_VK_GPU_RENDER_PASS_VK_HPP + +#include + +#include "src/gpu/gpu_render_pass.hpp" + +namespace skity { + +class GPUCommandBufferVk; +class GPUDeviceVk; + +class GPURenderPassVk : public GPURenderPass { + public: + GPURenderPassVk(GPUCommandBufferVk* command_buffer, + const GPURenderPassDescriptor& desc); + ~GPURenderPassVk() override; + + void EncodeCommands( + std::optional viewport = std::nullopt, + std::optional scissor = std::nullopt) override; + + private: + bool CreateVkRenderPass(); + void SetupViewportAndScissor(std::optional viewport, + std::optional scissor); + void ExecuteCommands(); + void ExecuteSingleCommand(VkCommandBuffer cmd_buffer, const Command* command); + + GPUCommandBufferVk* command_buffer_ = nullptr; + VkRenderPass vk_render_pass_ = VK_NULL_HANDLE; + VkFramebuffer vk_framebuffer_ = VK_NULL_HANDLE; + bool render_pass_created_ = false; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_RENDER_PASS_VK_HPP diff --git a/src/gpu/vk/gpu_render_pipeline_vk.cc b/src/gpu/vk/gpu_render_pipeline_vk.cc new file mode 100644 index 00000000..c7df2895 --- /dev/null +++ b/src/gpu/vk/gpu_render_pipeline_vk.cc @@ -0,0 +1,516 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_render_pipeline_vk.hpp" + +#include "src/gpu/vk/gpu_descriptor_set_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_shader_function_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPURenderPipelineVk::GPURenderPipelineVk( + GPUDeviceVk* device, const GPURenderPipelineDescriptor& desc) + : GPURenderPipeline(desc), device_(device) { + descriptor_manager_ = std::make_unique(device); +} + +GPURenderPipelineVk::~GPURenderPipelineVk() { + VkDevice vk_device = device_->GetDevice(); + + if (pipeline_ != VK_NULL_HANDLE) { + vkDestroyPipeline(vk_device, pipeline_, nullptr); + } + + if (pipeline_layout_ != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(vk_device, pipeline_layout_, nullptr); + } + + // Clean up descriptor set layouts + for (auto layout : descriptor_set_layouts_) { + if (layout != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(vk_device, layout, nullptr); + } + } +} + +bool GPURenderPipelineVk::IsValid() const { + return valid_ && pipeline_ != VK_NULL_HANDLE && + pipeline_layout_ != VK_NULL_HANDLE; +} + +std::unique_ptr GPURenderPipelineVk::Create( + GPUDeviceVk* device, const GPURenderPipelineDescriptor& desc) { + auto pipeline = std::make_unique(device, desc); + + if (!pipeline->CreatePipelineLayout()) { + LOGE("Failed to create pipeline layout"); + return nullptr; + } + + if (!pipeline->CreateGraphicsPipeline()) { + LOGE("Failed to create graphics pipeline"); + return nullptr; + } + + pipeline->valid_ = true; + LOGI("Successfully created Vulkan render pipeline"); + return pipeline; +} + +bool GPURenderPipelineVk::CreatePipelineLayout() { + // Extract descriptor bindings from both vertex and fragment shader bind + // groups + std::vector shader_bindings; + + // TODO: use reflection + for (uint32_t binding_idx = 0; binding_idx <= 3; ++binding_idx) { + DescriptorBinding binding; + binding.binding = binding_idx; + binding.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + binding.count = 1; + binding.stage_flags = + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + shader_bindings.push_back(binding); + } + + // Store the corrected shader bindings for later use in descriptor set + // creation + shader_bindings_ = shader_bindings; + + // Create descriptor set layout manually instead of using the manager to avoid + // recursion + std::vector layout_bindings; + layout_bindings.reserve(shader_bindings.size()); + + for (const auto& binding : shader_bindings) { + VkDescriptorSetLayoutBinding layout_binding = {}; + layout_binding.binding = binding.binding; + layout_binding.descriptorType = binding.type; + layout_binding.descriptorCount = binding.count; + layout_binding.stageFlags = binding.stage_flags; + layout_binding.pImmutableSamplers = nullptr; + + layout_bindings.push_back(layout_binding); + } + + VkDescriptorSetLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.bindingCount = static_cast(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + VkDevice vk_device = device_->GetDevice(); + VkDescriptorSetLayout descriptor_layout = VK_NULL_HANDLE; + VkResult layout_result = vkCreateDescriptorSetLayout( + vk_device, &layout_info, nullptr, &descriptor_layout); + if (layout_result == VK_SUCCESS && descriptor_layout != VK_NULL_HANDLE) { + descriptor_set_layouts_.push_back(descriptor_layout); + LOGI("Created descriptor set layout with %zu bindings", + shader_bindings.size()); + } else { + LOGW("Failed to create descriptor set layout: %d, proceeding without it", + layout_result); + } + + VkPipelineLayoutCreateInfo pipeline_layout_info = {}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = + static_cast(descriptor_set_layouts_.size()); + pipeline_layout_info.pSetLayouts = descriptor_set_layouts_.data(); + pipeline_layout_info.pushConstantRangeCount = 0; + pipeline_layout_info.pPushConstantRanges = nullptr; + + VkResult result = vkCreatePipelineLayout(vk_device, &pipeline_layout_info, + nullptr, &pipeline_layout_); + if (result != VK_SUCCESS) { + LOGE("Failed to create pipeline layout: %d", result); + return false; + } + + LOGI("Successfully created pipeline layout with %zu descriptor sets", + descriptor_set_layouts_.size()); + return true; +} + +bool GPURenderPipelineVk::CreateGraphicsPipeline() { + const auto& desc = GetDescriptor(); + + // Get shader modules from shader functions + auto* vertex_shader_vk = + static_cast(desc.vertex_function.get()); + auto* fragment_shader_vk = + static_cast(desc.fragment_function.get()); + + if (!vertex_shader_vk || !fragment_shader_vk) { + LOGE("Invalid shader functions provided"); + return false; + } + + // Shader stages + std::vector shader_stages; + + VkPipelineShaderStageCreateInfo vertex_stage_info = {}; + vertex_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertex_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertex_stage_info.module = vertex_shader_vk->GetShaderModule(); + vertex_stage_info.pName = "main"; + shader_stages.push_back(vertex_stage_info); + + VkPipelineShaderStageCreateInfo fragment_stage_info = {}; + fragment_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragment_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragment_stage_info.module = fragment_shader_vk->GetShaderModule(); + fragment_stage_info.pName = "main"; + shader_stages.push_back(fragment_stage_info); + + // Vertex input state + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + // Convert vertex buffer layouts to Vulkan format + std::vector binding_descriptions; + std::vector attribute_descriptions; + + const auto& vertex_buffers = desc.buffers; + binding_descriptions.reserve(vertex_buffers.size()); + + for (uint32_t binding = 0; binding < vertex_buffers.size(); ++binding) { + const auto& buffer_layout = vertex_buffers[binding]; + + VkVertexInputBindingDescription binding_desc = {}; + binding_desc.binding = binding; + binding_desc.stride = static_cast(buffer_layout.array_stride); + binding_desc.inputRate = + (buffer_layout.step_mode == GPUVertexStepMode::kInstance) + ? VK_VERTEX_INPUT_RATE_INSTANCE + : VK_VERTEX_INPUT_RATE_VERTEX; + + binding_descriptions.push_back(binding_desc); + + // Add attributes for this binding + for (const auto& attribute : buffer_layout.attributes) { + VkVertexInputAttributeDescription attr_desc = {}; + attr_desc.binding = binding; + attr_desc.location = static_cast(attribute.shader_location); + attr_desc.format = ConvertVertexFormat(attribute.format); + attr_desc.offset = static_cast(attribute.offset); + + attribute_descriptions.push_back(attr_desc); + } + } + + vertex_input_info.vertexBindingDescriptionCount = + static_cast(binding_descriptions.size()); + vertex_input_info.pVertexBindingDescriptions = binding_descriptions.data(); + vertex_input_info.vertexAttributeDescriptionCount = + static_cast(attribute_descriptions.size()); + vertex_input_info.pVertexAttributeDescriptions = + attribute_descriptions.data(); + + LOGI("Pipeline vertex input: %u bindings, %u attributes", + static_cast(binding_descriptions.size()), + static_cast(attribute_descriptions.size())); + + // Input assembly state + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + // Viewport state + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.viewportCount = 1; + viewport_state.pViewports = nullptr; // Dynamic + viewport_state.scissorCount = 1; + viewport_state.pScissors = nullptr; // Dynamic + + // Rasterization state + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; // FIXED: Disable culling to ensure + // triangles are visible + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + // Multisampling state + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + // Depth stencil state + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + + // Follow the same depth testing logic as GL backend + bool enable_depth = desc.depth_stencil.enable_depth; + depth_stencil.depthTestEnable = enable_depth ? VK_TRUE : VK_FALSE; + depth_stencil.depthWriteEnable = + (enable_depth && desc.depth_stencil.depth_state.enableWrite) ? VK_TRUE + : VK_FALSE; + depth_stencil.depthCompareOp = + ConvertCompareFunction(desc.depth_stencil.depth_state.compare); + + depth_stencil.depthBoundsTestEnable = VK_FALSE; + + // Enable stencil testing to match GL backend behavior + bool enable_stencil = desc.depth_stencil.enable_stencil; + depth_stencil.stencilTestEnable = enable_stencil ? VK_TRUE : VK_FALSE; + has_stencil_testing_ = enable_stencil; // Remember for later use + + if (enable_stencil) { + // Set up stencil state from pipeline descriptor + const auto& stencil_state = desc.depth_stencil.stencil_state; + + // Front face stencil + depth_stencil.front.failOp = + ConvertStencilOperation(stencil_state.front.fail_op); + depth_stencil.front.passOp = + ConvertStencilOperation(stencil_state.front.pass_op); + depth_stencil.front.depthFailOp = + ConvertStencilOperation(stencil_state.front.depth_fail_op); + depth_stencil.front.compareOp = + ConvertCompareFunction(stencil_state.front.compare); + depth_stencil.front.compareMask = stencil_state.front.stencil_read_mask; + depth_stencil.front.writeMask = stencil_state.front.stencil_write_mask; + depth_stencil.front.reference = 0; // Will be set dynamically + + // Back face stencil + depth_stencil.back.failOp = + ConvertStencilOperation(stencil_state.back.fail_op); + depth_stencil.back.passOp = + ConvertStencilOperation(stencil_state.back.pass_op); + depth_stencil.back.depthFailOp = + ConvertStencilOperation(stencil_state.back.depth_fail_op); + depth_stencil.back.compareOp = + ConvertCompareFunction(stencil_state.back.compare); + depth_stencil.back.compareMask = stencil_state.back.stencil_read_mask; + depth_stencil.back.writeMask = stencil_state.back.stencil_write_mask; + depth_stencil.back.reference = 0; // Will be set dynamically + } + + // Color blending - should match the blend factors from the pipeline + // descriptor + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + color_blend_attachment.blendEnable = VK_TRUE; + + // Use the blend factors from the pipeline descriptor + const auto& target = desc.target; + color_blend_attachment.srcColorBlendFactor = + ConvertBlendFactor(target.src_blend_factor); + color_blend_attachment.dstColorBlendFactor = + ConvertBlendFactor(target.dst_blend_factor); + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = + ConvertBlendFactor(target.src_blend_factor); + color_blend_attachment.dstAlphaBlendFactor = + ConvertBlendFactor(target.dst_blend_factor); + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + + // Dynamic state + std::vector dynamic_states = {VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR}; + + // Add dynamic stencil reference if stencil testing is enabled + if (enable_stencil) { + dynamic_states.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE); + } + + VkPipelineDynamicStateCreateInfo dynamic_state = {}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = + static_cast(dynamic_states.size()); + dynamic_state.pDynamicStates = dynamic_states.data(); + + // Create graphics pipeline + VkGraphicsPipelineCreateInfo pipeline_info = {}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.stageCount = static_cast(shader_stages.size()); + pipeline_info.pStages = shader_stages.data(); + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = &depth_stencil; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state; + pipeline_info.layout = pipeline_layout_; + pipeline_info.renderPass = device_->GetCompatibleRenderPass( + VK_FORMAT_B8G8R8A8_SRGB, + true); // Always use depth/stencil render pass for compatibility + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + VkDevice vk_device = device_->GetDevice(); + VkResult result = vkCreateGraphicsPipelines( + vk_device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &pipeline_); + if (result != VK_SUCCESS) { + LOGE("Failed to create graphics pipeline: %d", result); + // Note: This may fail due to placeholder SPIRV shaders, but the pipeline + // system is implemented + return false; + } + + LOGI("Successfully created Vulkan graphics pipeline"); + return true; +} + +// Helper conversion functions +VkShaderStageFlags GPURenderPipelineVk::ConvertShaderStageFlags( + GPUShaderStageMask stages) { + VkShaderStageFlags vk_stages = 0; + if (stages & static_cast(GPUShaderStage::kVertex)) { + vk_stages |= VK_SHADER_STAGE_VERTEX_BIT; + } + if (stages & static_cast(GPUShaderStage::kFragment)) { + vk_stages |= VK_SHADER_STAGE_FRAGMENT_BIT; + } + return vk_stages; +} + +VkFormat GPURenderPipelineVk::ConvertVertexFormat(GPUVertexFormat format) { + switch (format) { + case GPUVertexFormat::kFloat32: + return VK_FORMAT_R32_SFLOAT; + case GPUVertexFormat::kFloat32x2: + return VK_FORMAT_R32G32_SFLOAT; + case GPUVertexFormat::kFloat32x3: + return VK_FORMAT_R32G32B32_SFLOAT; + case GPUVertexFormat::kFloat32x4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + default: + return VK_FORMAT_UNDEFINED; + } +} + +VkCompareOp GPURenderPipelineVk::ConvertCompareFunction( + GPUCompareFunction func) { + switch (func) { + case GPUCompareFunction::kNever: + return VK_COMPARE_OP_NEVER; + case GPUCompareFunction::kLess: + return VK_COMPARE_OP_LESS; + case GPUCompareFunction::kEqual: + return VK_COMPARE_OP_EQUAL; + case GPUCompareFunction::kLessEqual: + return VK_COMPARE_OP_LESS_OR_EQUAL; + case GPUCompareFunction::kGreater: + return VK_COMPARE_OP_GREATER; + case GPUCompareFunction::kNotEqual: + return VK_COMPARE_OP_NOT_EQUAL; + case GPUCompareFunction::kGreaterEqual: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + case GPUCompareFunction::kAlways: + return VK_COMPARE_OP_ALWAYS; + default: + return VK_COMPARE_OP_ALWAYS; + } +} + +VkStencilOp GPURenderPipelineVk::ConvertStencilOperation( + GPUStencilOperation op) { + switch (op) { + case GPUStencilOperation::kKeep: + return VK_STENCIL_OP_KEEP; + case GPUStencilOperation::kZero: + return VK_STENCIL_OP_ZERO; + case GPUStencilOperation::kReplace: + return VK_STENCIL_OP_REPLACE; + case GPUStencilOperation::kInvert: + return VK_STENCIL_OP_INVERT; + case GPUStencilOperation::kIncrementClamp: + return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case GPUStencilOperation::kDecrementClamp: + return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case GPUStencilOperation::kIncrementWrap: + return VK_STENCIL_OP_INCREMENT_AND_WRAP; + case GPUStencilOperation::kDecrementWrap: + return VK_STENCIL_OP_DECREMENT_AND_WRAP; + default: + return VK_STENCIL_OP_KEEP; + } +} + +VkBlendFactor GPURenderPipelineVk::ConvertBlendFactor(GPUBlendFactor factor) { + switch (factor) { + case GPUBlendFactor::kZero: + return VK_BLEND_FACTOR_ZERO; + case GPUBlendFactor::kOne: + return VK_BLEND_FACTOR_ONE; + case GPUBlendFactor::kSrc: + return VK_BLEND_FACTOR_SRC_COLOR; + case GPUBlendFactor::kOneMinusSrc: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case GPUBlendFactor::kSrcAlpha: + return VK_BLEND_FACTOR_SRC_ALPHA; + case GPUBlendFactor::kOneMinusSrcAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case GPUBlendFactor::kDst: + return VK_BLEND_FACTOR_DST_COLOR; + case GPUBlendFactor::kOneMinusDst: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case GPUBlendFactor::kDstAlpha: + return VK_BLEND_FACTOR_DST_ALPHA; + case GPUBlendFactor::kOneMinusDstAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case GPUBlendFactor::kSrcAlphaSaturated: + return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; + default: + return VK_BLEND_FACTOR_ONE; + } +} + +std::shared_ptr GPURenderPipelineVk::CreateDescriptorSet( + const std::vector& bindings) { + return descriptor_manager_->CreateDescriptorSet(bindings); +} + +void GPURenderPipelineVk::BindDescriptorSet( + VkCommandBuffer command_buffer, + std::shared_ptr descriptor_set) { + if (!descriptor_set) { + LOGE("Invalid descriptor set for binding"); + return; + } + + VkDescriptorSet vk_descriptor_set = descriptor_set->GetDescriptorSet(); + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline_layout_, 0, 1, &vk_descriptor_set, 0, + nullptr); + + LOGI("Bound descriptor set to pipeline"); +} + +std::shared_ptr +GPURenderPipelineVk::CreateDescriptorSetUsingPipelineLayout() { + // Use the stored shader bindings from reflection to create a descriptor set + // that matches the pipeline's layout + return descriptor_manager_->CreateDescriptorSet(shader_bindings_); +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_render_pipeline_vk.hpp b/src/gpu/vk/gpu_render_pipeline_vk.hpp new file mode 100644 index 00000000..1af794f8 --- /dev/null +++ b/src/gpu/vk/gpu_render_pipeline_vk.hpp @@ -0,0 +1,72 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_RENDER_PIPELINE_VK_HPP +#define SRC_GPU_VK_GPU_RENDER_PIPELINE_VK_HPP + +#include + +#include "src/gpu/gpu_render_pipeline.hpp" +#include "src/gpu/vk/gpu_descriptor_set_vk.hpp" + +namespace skity { + +class GPUDeviceVk; + +class GPURenderPipelineVk : public GPURenderPipeline { + public: + GPURenderPipelineVk(GPUDeviceVk* device, + const GPURenderPipelineDescriptor& desc); + ~GPURenderPipelineVk() override; + + bool IsValid() const override; + + static std::unique_ptr Create( + GPUDeviceVk* device, const GPURenderPipelineDescriptor& desc); + + VkPipeline GetVkPipeline() const { return pipeline_; } + VkPipelineLayout GetVkPipelineLayout() const { return pipeline_layout_; } + + // Descriptor set management + std::shared_ptr CreateDescriptorSet( + const std::vector& bindings); + void BindDescriptorSet(VkCommandBuffer command_buffer, + std::shared_ptr descriptor_set); + + // Create descriptor set using the pipeline's own descriptor layout from + // shader reflection + std::shared_ptr CreateDescriptorSetUsingPipelineLayout(); + + GPUDescriptorManagerVk* GetDescriptorManager() { + return descriptor_manager_.get(); + } + + // Check if this pipeline has stencil testing enabled + bool HasStencilTesting() const { return has_stencil_testing_; } + + private: + bool CreatePipelineLayout(); + bool CreateGraphicsPipeline(); + + VkShaderStageFlags ConvertShaderStageFlags(GPUShaderStageMask stages); + VkFormat ConvertVertexFormat(GPUVertexFormat format); + VkCompareOp ConvertCompareFunction(GPUCompareFunction func); + VkStencilOp ConvertStencilOperation(GPUStencilOperation op); + VkBlendFactor ConvertBlendFactor(GPUBlendFactor factor); + + GPUDeviceVk* device_ = nullptr; + VkPipeline pipeline_ = VK_NULL_HANDLE; + VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE; + std::unique_ptr descriptor_manager_; + std::vector descriptor_set_layouts_; + std::vector + shader_bindings_; // Store bindings from shader reflection + bool has_stencil_testing_ = + false; // Track if this pipeline uses stencil testing + bool valid_ = false; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_RENDER_PIPELINE_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/gpu_sampler_vk.cc b/src/gpu/vk/gpu_sampler_vk.cc new file mode 100644 index 00000000..86e7a476 --- /dev/null +++ b/src/gpu/vk/gpu_sampler_vk.cc @@ -0,0 +1,117 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_sampler_vk.hpp" + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUSamplerVk::GPUSamplerVk(const GPUSamplerDescriptor& descriptor) + : GPUSampler(descriptor) {} + +GPUSamplerVk::~GPUSamplerVk() { Destroy(); } + +std::shared_ptr GPUSamplerVk::Create( + GPUDeviceVk* device, const GPUSamplerDescriptor& descriptor) { + if (!device) { + LOGE("Invalid device for sampler creation"); + return nullptr; + } + + auto sampler = std::make_shared(descriptor); + if (!sampler->Initialize(device)) { + LOGE("Failed to initialize Vulkan sampler"); + return nullptr; + } + + return sampler; +} + +bool GPUSamplerVk::Initialize(GPUDeviceVk* device) { + if (!device) { + LOGE("Invalid device for sampler initialization"); + return false; + } + + device_ = device; + + VkSamplerCreateInfo sampler_info{}; + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.magFilter = ConvertFilter(desc_.mag_filter); + sampler_info.minFilter = ConvertFilter(desc_.min_filter); + sampler_info.addressModeU = ConvertAddressMode(desc_.address_mode_u); + sampler_info.addressModeV = ConvertAddressMode(desc_.address_mode_v); + sampler_info.addressModeW = ConvertAddressMode(desc_.address_mode_w); + sampler_info.anisotropyEnable = VK_FALSE; + sampler_info.maxAnisotropy = 1.0f; + sampler_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + sampler_info.unnormalizedCoordinates = VK_FALSE; + sampler_info.compareEnable = VK_FALSE; + sampler_info.compareOp = VK_COMPARE_OP_ALWAYS; + sampler_info.mipmapMode = ConvertMipmapMode(desc_.mipmap_filter); + sampler_info.mipLodBias = 0.0f; + sampler_info.minLod = 0.0f; + sampler_info.maxLod = VK_LOD_CLAMP_NONE; + + VkResult result = + vkCreateSampler(device_->GetDevice(), &sampler_info, nullptr, &sampler_); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan sampler: %d", result); + return false; + } + + return true; +} + +void GPUSamplerVk::Destroy() { + if (sampler_ != VK_NULL_HANDLE && device_) { + vkDestroySampler(device_->GetDevice(), sampler_, nullptr); + sampler_ = VK_NULL_HANDLE; + } + device_ = nullptr; +} + +VkFilter GPUSamplerVk::ConvertFilter(GPUFilterMode filter) const { + switch (filter) { + case GPUFilterMode::kNearest: + return VK_FILTER_NEAREST; + case GPUFilterMode::kLinear: + return VK_FILTER_LINEAR; + default: + return VK_FILTER_NEAREST; + } +} + +VkSamplerMipmapMode GPUSamplerVk::ConvertMipmapMode( + GPUMipmapMode mipmap_mode) const { + switch (mipmap_mode) { + case GPUMipmapMode::kNone: + // When no mipmapping, use nearest for best performance + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case GPUMipmapMode::kNearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case GPUMipmapMode::kLinear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + default: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + } +} + +VkSamplerAddressMode GPUSamplerVk::ConvertAddressMode( + GPUAddressMode address_mode) const { + switch (address_mode) { + case GPUAddressMode::kClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case GPUAddressMode::kRepeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case GPUAddressMode::kMirrorRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + default: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + } +} + +} // namespace skity diff --git a/src/gpu/vk/gpu_sampler_vk.hpp b/src/gpu/vk/gpu_sampler_vk.hpp new file mode 100644 index 00000000..a87eda4e --- /dev/null +++ b/src/gpu/vk/gpu_sampler_vk.hpp @@ -0,0 +1,43 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_SAMPLER_VK_HPP +#define SRC_GPU_VK_GPU_SAMPLER_VK_HPP + +#include + +#include "src/gpu/backend_cast.hpp" +#include "src/gpu/gpu_sampler.hpp" + +namespace skity { + +class GPUDeviceVk; + +class GPUSamplerVk : public GPUSampler { + public: + explicit GPUSamplerVk(const GPUSamplerDescriptor& descriptor); + ~GPUSamplerVk() override; + + static std::shared_ptr Create( + GPUDeviceVk* device, const GPUSamplerDescriptor& descriptor); + + bool Initialize(GPUDeviceVk* device); + void Destroy(); + + VkSampler GetVkSampler() const { return sampler_; } + + SKT_BACKEND_CAST(GPUSamplerVk, GPUSampler) + + private: + VkFilter ConvertFilter(GPUFilterMode filter) const; + VkSamplerMipmapMode ConvertMipmapMode(GPUMipmapMode mipmap_mode) const; + VkSamplerAddressMode ConvertAddressMode(GPUAddressMode address_mode) const; + + VkSampler sampler_ = VK_NULL_HANDLE; + GPUDeviceVk* device_ = nullptr; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_SAMPLER_VK_HPP diff --git a/src/gpu/vk/gpu_shader_function_vk.cc b/src/gpu/vk/gpu_shader_function_vk.cc new file mode 100644 index 00000000..a51f96ac --- /dev/null +++ b/src/gpu/vk/gpu_shader_function_vk.cc @@ -0,0 +1,186 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_shader_function_vk.hpp" + +#include + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/spirv_compiler_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUShaderFunctionVk::GPUShaderFunctionVk( + std::string label, GPUShaderStage stage, + const std::vector& spirv_code, + const std::vector& constant_values, + GPUShaderFunctionErrorCallback error_callback) + : GPUShaderFunction(std::move(label)), + spirv_code_(spirv_code), + constant_values_(constant_values) { + switch (stage) { + case GPUShaderStage::kVertex: + stage_ = VK_SHADER_STAGE_VERTEX_BIT; + break; + case GPUShaderStage::kFragment: + stage_ = VK_SHADER_STAGE_FRAGMENT_BIT; + break; + default: + stage_ = VK_SHADER_STAGE_VERTEX_BIT; + } +} + +GPUShaderFunctionVk::~GPUShaderFunctionVk() { Destroy(); } + +std::shared_ptr GPUShaderFunctionVk::Create( + GPUDeviceVk* device, const GPUShaderFunctionDescriptor& desc) { + if (!device) { + LOGE("Invalid device for shader function creation"); + return nullptr; + } + + std::vector spirv_code; + + // Create SPIRV compiler instance + SPIRVCompilerVk spirv_compiler(device); + + if (desc.source_type == GPUShaderSourceType::kRaw) { + auto* raw_source = static_cast(desc.shader_source); + if (!raw_source || !raw_source->source) { + LOGE("Invalid shader source"); + return nullptr; + } + + // Treat raw source as GLSL and compile to SPIRV + SPIRVCompileOptions options = SPIRVUtils::GetDefaultOptions(desc.stage); + auto compile_result = spirv_compiler.CompileGLSLToSPIRV( + raw_source->source, desc.stage, options); + + if (!compile_result) { + LOGE("GLSL to SPIRV compilation failed: %s", + compile_result.error_message.c_str()); + return nullptr; + } + + spirv_code = compile_result.spirv_code; + LOGI("Successfully compiled GLSL to SPIRV (%zu words)", spirv_code.size()); + + } else if (desc.source_type == GPUShaderSourceType::kWGX) { + auto* wgx_source = static_cast(desc.shader_source); + if (!wgx_source || !wgx_source->module || !wgx_source->entry_point) { + LOGE("Invalid WGX shader source"); + return nullptr; + } + + // Get WGX program and convert to GLSL first + auto* program = wgx_source->module->GetProgram(); + if (!program) { + LOGE("Invalid WGX program"); + return nullptr; + } + + // Configure GLSL options for Vulkan (GLSL 450) + wgx::GlslOptions glsl_options; + glsl_options.standard = wgx::GlslOptions::Standard::kDesktop; + glsl_options.major_version = 4; + glsl_options.minor_version = 5; + + // Convert WGX to GLSL + auto wgx_result = program->WriteToGlsl(wgx_source->entry_point, + glsl_options, wgx_source->context); + if (!wgx_result.success) { + LOGE("WGX to GLSL translation failed"); + return nullptr; + } + + LOGI("WGX shader module (%s) translated function (%s) to GLSL successfully", + wgx_source->module->GetLabel().c_str(), wgx_source->entry_point); + + // Compile GLSL to SPIRV + SPIRVCompileOptions options = SPIRVUtils::GetDefaultOptions(desc.stage); + auto compile_result = spirv_compiler.CompileGLSLToSPIRV( + wgx_result.content, desc.stage, options); + + if (!compile_result) { + LOGE("WGSL to SPIRV compilation failed: %s", + compile_result.error_message.c_str()); + return nullptr; + } + + spirv_code = compile_result.spirv_code; + LOGI("Successfully compiled WGSL to SPIRV (%zu words)", spirv_code.size()); + + } else { + LOGE("Unsupported shader source type"); + return nullptr; + } + + auto shader = std::make_shared( + desc.label, desc.stage, spirv_code, desc.constant_values, + desc.error_callback); + + // Set bind groups and WGX context for Vulkan shaders (like OpenGL version) + if (desc.source_type == GPUShaderSourceType::kWGX) { + auto* wgx_source = static_cast(desc.shader_source); + auto* program = wgx_source->module->GetProgram(); + wgx::GlslOptions glsl_options; + glsl_options.standard = wgx::GlslOptions::Standard::kDesktop; + glsl_options.major_version = 4; + glsl_options.minor_version = 5; + + auto wgx_result = program->WriteToGlsl(wgx_source->entry_point, + glsl_options, wgx_source->context); + if (wgx_result.success) { + shader->SetBindGroups(wgx_result.bind_groups); + shader->SetWGXContext(wgx_result.context); + wgx_source->context = wgx_result.context; + } + } + + if (!shader->Initialize(device)) { + LOGE("Failed to initialize Vulkan shader function"); + return nullptr; + } + + return shader; +} + +bool GPUShaderFunctionVk::Initialize(GPUDeviceVk* device) { + if (!device) { + LOGE("Invalid device for shader initialization"); + return false; + } + + if (spirv_code_.empty()) { + LOGE("Empty SPIRV code"); + return false; + } + + device_ = device; + + VkShaderModuleCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = spirv_code_.size() * sizeof(uint32_t); + create_info.pCode = spirv_code_.data(); + + VkResult result = vkCreateShaderModule(device_->GetDevice(), &create_info, + nullptr, &shader_module_); + if (result != VK_SUCCESS) { + LOGE("Failed to create shader module: %d", result); + return false; + } + + return true; +} + +void GPUShaderFunctionVk::Destroy() { + if (shader_module_ != VK_NULL_HANDLE && device_) { + vkDestroyShaderModule(device_->GetDevice(), shader_module_, nullptr); + shader_module_ = VK_NULL_HANDLE; + } + device_ = nullptr; +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_shader_function_vk.hpp b/src/gpu/vk/gpu_shader_function_vk.hpp new file mode 100644 index 00000000..87b1a430 --- /dev/null +++ b/src/gpu/vk/gpu_shader_function_vk.hpp @@ -0,0 +1,48 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_SHADER_FUNCTION_VK_HPP +#define SRC_GPU_VK_GPU_SHADER_FUNCTION_VK_HPP + +#include + +#include + +#include "src/gpu/gpu_shader_function.hpp" + +namespace skity { + +class GPUDeviceVk; + +class GPUShaderFunctionVk : public GPUShaderFunction { + public: + GPUShaderFunctionVk(std::string label, GPUShaderStage stage, + const std::vector& spirv_code, + const std::vector& constant_values, + GPUShaderFunctionErrorCallback error_callback); + + ~GPUShaderFunctionVk() override; + + static std::shared_ptr Create( + GPUDeviceVk* device, const GPUShaderFunctionDescriptor& desc); + + bool Initialize(GPUDeviceVk* device); + void Destroy(); + + VkShaderModule GetShaderModule() const { return shader_module_; } + VkShaderStageFlagBits GetStage() const { return stage_; } + + bool IsValid() const override { return shader_module_ != VK_NULL_HANDLE; } + + private: + VkShaderModule shader_module_ = VK_NULL_HANDLE; + VkShaderStageFlagBits stage_; + std::vector spirv_code_; + std::vector constant_values_; + GPUDeviceVk* device_ = nullptr; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_SHADER_FUNCTION_VK_HPP diff --git a/src/gpu/vk/gpu_surface_vk.cc b/src/gpu/vk/gpu_surface_vk.cc new file mode 100644 index 00000000..64b6f01e --- /dev/null +++ b/src/gpu/vk/gpu_surface_vk.cc @@ -0,0 +1,130 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_surface_vk.hpp" + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/logging.hpp" +#include "src/render/hw/layer/hw_root_layer.hpp" +#include "src/render/hw/vk/vk_root_layer.hpp" + +namespace skity { + +GPUSurfaceVk::GPUSurfaceVk(const GPUSurfaceDescriptor& desc, + GPUContextImpl* ctx) + : GPUSurfaceImpl(desc, ctx) {} + +GPUSurfaceVk::~GPUSurfaceVk() { DestroyFramebuffer(); } + +bool GPUSurfaceVk::Initialize(const GPUSurfaceDescriptorVk& desc) { + format_ = static_cast(desc.vk_format); + + if (desc.surface_type == VkSurfaceType::kImage) { + // For image-based surfaces, we need a render target texture + // This will be implemented as needed + return true; + } + + LOGE("Unsupported Vulkan surface type"); + return false; +} + +void GPUSurfaceVk::SetTargetTexture(std::shared_ptr texture) { + target_texture_ = std::move(texture); +} + +std::unique_ptr GPUSurfaceVk::Create( + GPUContextImpl* ctx, const GPUSurfaceDescriptorVk& desc) { + if (!ctx) { + LOGE("Invalid context for Vulkan surface creation"); + return nullptr; + } + + // Convert the Vulkan descriptor to the base descriptor + GPUSurfaceDescriptor base_desc{}; + base_desc.backend = ctx->GetBackendType(); + base_desc.width = desc.width; + base_desc.height = desc.height; + base_desc.sample_count = desc.sample_count; + base_desc.content_scale = desc.content_scale; + + auto surface = std::make_unique(base_desc, ctx); + if (!surface->Initialize(desc)) { + LOGE("Failed to initialize Vulkan surface"); + return nullptr; + } + + // Create render target texture for the surface + if (!surface->CreateRenderTarget()) { + LOGE("Failed to create render target for Vulkan surface"); + return nullptr; + } + + return surface; +} + +HWRootLayer* GPUSurfaceVk::OnBeginNextFrame(bool clear) { + if (!target_texture_) { + LOGE("No target texture available for Vulkan surface"); + return nullptr; + } + + LOGI("Creating VkExternTextureLayer for frame %dx%d", GetWidth(), + GetHeight()); + auto root_layer = GetArenaAllocator()->Make( + target_texture_, Rect::MakeWH(GetWidth(), GetHeight())); + + root_layer->SetClearSurface(clear); + root_layer->SetSampleCount(GetSampleCount()); + root_layer->SetArenaAllocator(GetArenaAllocator()); + + LOGI("VkExternTextureLayer created successfully"); + return root_layer; +} + +void GPUSurfaceVk::OnFlush() { + // OnFlush is called after all drawing commands have been recorded + // The actual command submission happens in the layer's draw methods + LOGI("GPUSurfaceVk::OnFlush() called - starting flush sequence"); +} + +std::shared_ptr GPUSurfaceVk::ReadPixels(const Rect& rect) { + // TODO: Implement Vulkan surface pixel reading + return nullptr; +} + +void GPUSurfaceVk::CreateFramebuffer() { + // TODO: Implement Vulkan framebuffer creation +} + +bool GPUSurfaceVk::CreateRenderTarget() { + auto* context = GetGPUContext(); + if (!context) { + LOGE("No context available for creating render target"); + return false; + } + + // Create a texture to serve as the render target + GPUTextureDescriptor tex_desc; + tex_desc.width = GetWidth(); + tex_desc.height = GetHeight(); + tex_desc.format = GPUTextureFormat::kRGBA8Unorm; + tex_desc.usage = + static_cast(GPUTextureUsage::kRenderAttachment) | + static_cast(GPUTextureUsage::kTextureBinding); + tex_desc.sample_count = GetSampleCount(); + + target_texture_ = context->GetGPUDevice()->CreateTexture(tex_desc); + if (!target_texture_) { + LOGE("Failed to create target texture for Vulkan surface"); + return false; + } + + LOGI("Created Vulkan render target texture: %dx%d", GetWidth(), GetHeight()); + return true; +} + +void GPUSurfaceVk::DestroyFramebuffer() { target_texture_.reset(); } + +} // namespace skity diff --git a/src/gpu/vk/gpu_surface_vk.hpp b/src/gpu/vk/gpu_surface_vk.hpp new file mode 100644 index 00000000..5cab1464 --- /dev/null +++ b/src/gpu/vk/gpu_surface_vk.hpp @@ -0,0 +1,65 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_SURFACE_VK_HPP +#define SRC_GPU_VK_GPU_SURFACE_VK_HPP + +#include + +#include +#include +#include + +#include "src/gpu/gpu_surface_impl.hpp" + +namespace skity { + +class GPUDeviceVk; +class GPUTextureVk; +class Canvas; +class Pixmap; +class HWRootLayer; + +// Using the public GPUSurfaceDescriptorVk from gpu_context_vk.hpp + +class GPUSurfaceVk : public GPUSurfaceImpl { + public: + GPUSurfaceVk(const GPUSurfaceDescriptor& desc, GPUContextImpl* ctx); + ~GPUSurfaceVk() override; + + bool Initialize(const GPUSurfaceDescriptorVk& vk_desc); + void SetTargetTexture(std::shared_ptr texture); + + std::shared_ptr ReadPixels(const Rect& rect) override; + + GPUTextureFormat GetGPUFormat() const override { + return GPUTextureFormat::kRGBA8Unorm; + } + + static std::unique_ptr Create( + GPUContextImpl* ctx, const GPUSurfaceDescriptorVk& desc); + + protected: + HWRootLayer* OnBeginNextFrame(bool clear) override; + void OnFlush() override; + + std::shared_ptr GetTargetTexture() const { + return target_texture_; + } + + private: + bool CreateRenderTarget(); + void CreateFramebuffer(); + void DestroyFramebuffer(); + + std::shared_ptr target_texture_; + VkFormat format_ = VK_FORMAT_R8G8B8A8_UNORM; + + VkFramebuffer framebuffer_ = VK_NULL_HANDLE; + VkRenderPass render_pass_ = VK_NULL_HANDLE; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_SURFACE_VK_HPP diff --git a/src/gpu/vk/gpu_texture_vk.cc b/src/gpu/vk/gpu_texture_vk.cc new file mode 100644 index 00000000..63d97bc1 --- /dev/null +++ b/src/gpu/vk/gpu_texture_vk.cc @@ -0,0 +1,289 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_texture_vk.hpp" + +#include +#include + +#include "src/gpu/vk/formats_vk.h" +#include "src/gpu/vk/gpu_buffer_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/sync_objects_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +GPUTextureVk::GPUTextureVk(const GPUTextureDescriptor& descriptor) + : GPUTexture(descriptor) {} + +GPUTextureVk::~GPUTextureVk() { Destroy(); } + +std::shared_ptr GPUTextureVk::Create( + GPUDeviceVk* device, const GPUTextureDescriptor& descriptor) { + if (!device) { + LOGE("Invalid device for texture creation"); + return nullptr; + } + + auto texture = std::make_shared(descriptor); + if (!texture->Initialize(device)) { + LOGE("Failed to initialize Vulkan texture"); + return nullptr; + } + + return texture; +} + +std::shared_ptr GPUTextureVk::CreateFromVkImage( + GPUDeviceVk* device, VkImage vk_image, VkFormat vk_format, uint32_t width, + uint32_t height) { + if (!device || vk_image == VK_NULL_HANDLE) { + LOGE("Invalid device or VkImage for texture wrapping"); + return nullptr; + } + + // Create a descriptor for the wrapper texture + GPUTextureDescriptor desc; + desc.width = width; + desc.height = height; + // Map the actual swapchain format correctly + if (vk_format == VK_FORMAT_B8G8R8A8_SRGB) { + desc.format = GPUTextureFormat::kBGRA8Unorm; // Match swapchain format + } else { + desc.format = GPUTextureFormat::kRGBA8Unorm; // Fallback + } + desc.usage = + static_cast(GPUTextureUsage::kRenderAttachment); + + auto texture = std::make_shared(desc); + texture->device_ = device; + texture->format_ = vk_format; + texture->image_ = vk_image; // Use the provided VkImage + texture->allocation_ = VK_NULL_HANDLE; // No allocation for external images + + // Only create the image view for the wrapped image + if (!texture->CreateImageView(device)) { + LOGE("Failed to create image view for wrapped VkImage"); + return nullptr; + } + + return texture; +} + +bool GPUTextureVk::Initialize(GPUDeviceVk* device) { + if (!device) { + LOGE("Invalid device for texture initialization"); + return false; + } + + device_ = device; + format_ = GPUTextureFormatToVkFormat(desc_.format); + + if (format_ == VK_FORMAT_UNDEFINED) { + LOGE("Unsupported texture format"); + return false; + } + + if (!CreateImage(device)) { + LOGE("Failed to create Vulkan image"); + return false; + } + + if (!CreateImageView(device)) { + LOGE("Failed to create Vulkan image view"); + Destroy(); + return false; + } + + return true; +} + +bool GPUTextureVk::CreateImage(GPUDeviceVk* device) { + VkImageCreateInfo image_info{}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.extent.width = desc_.width; + image_info.extent.height = desc_.height; + image_info.extent.depth = 1; + image_info.mipLevels = desc_.mip_level_count; + image_info.arrayLayers = 1; + image_info.format = format_; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.usage = GPUTextureUsageToVkImageUsage(desc_.usage, desc_.format); + image_info.samples = static_cast(desc_.sample_count); + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo alloc_info{}; + alloc_info.usage = GetOptimalMemoryUsage(); + + VkResult result = vmaCreateImage(device->GetAllocator(), &image_info, + &alloc_info, &image_, &allocation_, nullptr); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan image: %d", result); + return false; + } + + current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; + return true; +} + +bool GPUTextureVk::CreateImageView(GPUDeviceVk* device) { + VkImageViewCreateInfo view_info{}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = image_; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = format_; + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_info.subresourceRange.baseMipLevel = 0; + view_info.subresourceRange.levelCount = desc_.mip_level_count; + view_info.subresourceRange.baseArrayLayer = 0; + view_info.subresourceRange.layerCount = 1; + + // Handle depth/stencil formats + if (desc_.format == GPUTextureFormat::kStencil8) { + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + } else if (desc_.format == GPUTextureFormat::kDepth24Stencil8) { + view_info.subresourceRange.aspectMask = + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } + + VkResult result = + vkCreateImageView(device->GetDevice(), &view_info, nullptr, &image_view_); + if (result != VK_SUCCESS) { + LOGE("Failed to create image view: %d", result); + return false; + } + + return true; +} + +void GPUTextureVk::TransitionImageLayout(GPUDeviceVk* device, + VkImageLayout old_layout, + VkImageLayout new_layout) { + VkCommandBuffer command_buffer = device->BeginSingleTimeCommands(); + + // Use the new synchronization system for better barrier management + VkSyncManager sync_manager(device); + + auto barrier = VkSyncManager::CreateImageTransitionBarrier( + image_, old_layout, new_layout, VK_IMAGE_ASPECT_COLOR_BIT); + + // Set proper subresource range for this texture + barrier.subresource_range.baseMipLevel = 0; + barrier.subresource_range.levelCount = desc_.mip_level_count; + barrier.subresource_range.baseArrayLayer = 0; + barrier.subresource_range.layerCount = 1; + + sync_manager.AddImageBarrier(barrier); + sync_manager.ExecuteBarriers(command_buffer); + + device->EndSingleTimeCommands(command_buffer); + current_layout_ = new_layout; +} + +void GPUTextureVk::UploadData(GPUDeviceVk* device, uint32_t offset_x, + uint32_t offset_y, uint32_t width, + uint32_t height, const void* data) { + if (!data || !device) { + LOGE("Invalid parameters for texture data upload"); + return; + } + + size_t data_size = + width * height * GetTextureFormatBytesPerPixel(desc_.format); + + // Create staging buffer + auto staging_buffer_unique = + device->CreateBuffer(GPUBufferUsage::kVertexBuffer); + if (!staging_buffer_unique) { + LOGE("Failed to create staging buffer for texture upload"); + return; + } + auto* staging_buffer = static_cast(staging_buffer_unique.get()); + + // Upload data to staging buffer + staging_buffer->UploadData(const_cast(data), data_size); + + // Transition image layout for transfer + TransitionImageLayout(device, current_layout_, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + // Copy buffer to image + VkCommandBuffer command_buffer = device->BeginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {static_cast(offset_x), + static_cast(offset_y), 0}; + region.imageExtent = {width, height, 1}; + + vkCmdCopyBufferToImage(command_buffer, staging_buffer->GetBuffer(), image_, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + device->EndSingleTimeCommands(command_buffer); + + // Transition to shader read optimal layout + TransitionImageLayout(device, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +} + +size_t GPUTextureVk::GetBytes() const { + return desc_.width * desc_.height * + GetTextureFormatBytesPerPixel(desc_.format) * desc_.mip_level_count; +} + +void GPUTextureVk::Destroy() { + if (device_) { + VkDevice vk_device = device_->GetDevice(); + + if (image_view_ != VK_NULL_HANDLE) { + vkDestroyImageView(vk_device, image_view_, nullptr); + image_view_ = VK_NULL_HANDLE; + } + + if (image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE) { + vmaDestroyImage(device_->GetAllocator(), image_, allocation_); + image_ = VK_NULL_HANDLE; + allocation_ = VK_NULL_HANDLE; + } + } + + device_ = nullptr; + current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; +} + +VmaMemoryUsage GPUTextureVk::GetOptimalMemoryUsage() const { + auto usage = desc_.usage; + auto storage_mode = desc_.storage_mode; + + // Optimize memory usage based on texture usage and storage mode + if (storage_mode == GPUTextureStorageMode::kPrivate) { + return VMA_MEMORY_USAGE_GPU_ONLY; // GPU-only for private textures + } + + if (usage & + static_cast(GPUTextureUsage::kRenderAttachment)) { + return VMA_MEMORY_USAGE_GPU_ONLY; // Render targets stay on GPU + } + + if ((usage & + static_cast(GPUTextureUsage::kTextureBinding)) && + storage_mode == GPUTextureStorageMode::kHostVisible) { + // Textures that might need CPU access for updates + return VMA_MEMORY_USAGE_CPU_TO_GPU; + } + + // Default: GPU-only for most textures + return VMA_MEMORY_USAGE_GPU_ONLY; +} + +} // namespace skity diff --git a/src/gpu/vk/gpu_texture_vk.hpp b/src/gpu/vk/gpu_texture_vk.hpp new file mode 100644 index 00000000..33797735 --- /dev/null +++ b/src/gpu/vk/gpu_texture_vk.hpp @@ -0,0 +1,64 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_TEXTURE_VK_HPP +#define SRC_GPU_VK_GPU_TEXTURE_VK_HPP + +#include +#include + +#include "src/gpu/backend_cast.hpp" +#include "src/gpu/gpu_texture.hpp" + +namespace skity { + +class GPUDeviceVk; + +class GPUTextureVk : public GPUTexture { + public: + explicit GPUTextureVk(const GPUTextureDescriptor& descriptor); + ~GPUTextureVk() override; + + static std::shared_ptr Create( + GPUDeviceVk* device, const GPUTextureDescriptor& descriptor); + + // Create a texture wrapper around an existing VkImage (e.g., swapchain image) + static std::shared_ptr CreateFromVkImage(GPUDeviceVk* device, + VkImage vk_image, + VkFormat vk_format, + uint32_t width, + uint32_t height); + + bool Initialize(GPUDeviceVk* device); + void Destroy(); + + VkImage GetVkImage() const { return image_; } + VkImageView GetVkImageView() const { return image_view_; } + VkFormat GetVkFormat() const { return format_; } + + void UploadData(GPUDeviceVk* device, uint32_t offset_x, uint32_t offset_y, + uint32_t width, uint32_t height, const void* data); + + size_t GetBytes() const override; + + SKT_BACKEND_CAST(GPUTextureVk, GPUTexture) + + private: + bool CreateImage(GPUDeviceVk* device); + bool CreateImageView(GPUDeviceVk* device); + void TransitionImageLayout(GPUDeviceVk* device, VkImageLayout old_layout, + VkImageLayout new_layout); + VmaMemoryUsage GetOptimalMemoryUsage() const; + + VkImage image_ = VK_NULL_HANDLE; + VkImageView image_view_ = VK_NULL_HANDLE; + VmaAllocation allocation_ = VK_NULL_HANDLE; + VkFormat format_ = VK_FORMAT_UNDEFINED; + VkImageLayout current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; + GPUDeviceVk* device_ = nullptr; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_TEXTURE_VK_HPP diff --git a/src/gpu/vk/gpu_window_surface_vk.cc b/src/gpu/vk/gpu_window_surface_vk.cc new file mode 100644 index 00000000..3324222d --- /dev/null +++ b/src/gpu/vk/gpu_window_surface_vk.cc @@ -0,0 +1,557 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/gpu_window_surface_vk.hpp" + +#include +#include + +#include "src/gpu/vk/gpu_context_impl_vk.hpp" +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/gpu/vk/vk_interface.hpp" +#include "src/logging.hpp" +#include "src/render/hw/vk/vk_root_layer.hpp" + +namespace skity { + +GPUWindowSurfaceVk::GPUWindowSurfaceVk(GPUContextImpl* ctx, uint32_t width, + uint32_t height, uint32_t sample_count, + float content_scale) + : GPUSurfaceVk(GPUSurfaceDescriptor{GPUBackendType::kVulkan, width, height, + sample_count, content_scale}, + ctx) { + vk_device_ = static_cast(ctx->GetGPUDevice()); +} + +GPUWindowSurfaceVk::~GPUWindowSurfaceVk() { + if (vk_device_ && vk_device_->GetDevice()) { + vkDeviceWaitIdle(vk_device_->GetDevice()); + } + + CleanupSwapchain(); + + // Destroy sync objects + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (render_finished_semaphores_[i]) { + vkDestroySemaphore(vk_device_->GetDevice(), + render_finished_semaphores_[i], nullptr); + } + if (image_available_semaphores_[i]) { + vkDestroySemaphore(vk_device_->GetDevice(), + image_available_semaphores_[i], nullptr); + } + if (in_flight_fences_[i]) { + vkDestroyFence(vk_device_->GetDevice(), in_flight_fences_[i], nullptr); + } + } + + // Surface is owned and destroyed by the window, not by this class + // Do not destroy the surface here to avoid double destruction +} + +bool GPUWindowSurfaceVk::InitWithSurface(VkSurfaceKHR surface, + VkInterface* vk_interface) { + if (surface == VK_NULL_HANDLE || !vk_interface) { + LOGE("Invalid surface or Vulkan interface"); + return false; + } + + surface_ = surface; + instance_ = vk_interface->GetInstance(); + + // Create swapchain + if (!CreateSwapchain()) { + return false; + } + + // Create image views + if (!CreateSwapchainImageViews()) { + return false; + } + + // Create render pass + if (!CreateRenderPass()) { + return false; + } + + // Create framebuffers + if (!CreateFramebuffers()) { + return false; + } + + // Create sync objects + if (!CreateSyncObjects()) { + return false; + } + + LOGI("Vulkan window surface initialized successfully"); + return true; +} + +bool GPUWindowSurfaceVk::CreateSwapchain() { + // Check for presentation support + VkBool32 present_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR( + vk_device_->GetPhysicalDevice(), + vk_device_->GetQueueFamilyIndices().present_family, surface_, + &present_support); + + if (!present_support) { + LOGE("Surface does not support presentation"); + return false; + } + + SwapChainSupportDetails swap_chain_support = + QuerySwapChainSupport(vk_device_->GetPhysicalDevice()); + + VkSurfaceFormatKHR surface_format = + ChooseSwapSurfaceFormat(swap_chain_support.formats); + VkPresentModeKHR present_mode = + ChooseSwapPresentMode(swap_chain_support.present_modes); + VkExtent2D extent = ChooseSwapExtent(swap_chain_support.capabilities); + + uint32_t image_count = swap_chain_support.capabilities.minImageCount + 1; + if (swap_chain_support.capabilities.maxImageCount > 0 && + image_count > swap_chain_support.capabilities.maxImageCount) { + image_count = swap_chain_support.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + create_info.surface = surface_; + create_info.minImageCount = image_count; + create_info.imageFormat = surface_format.format; + create_info.imageColorSpace = surface_format.colorSpace; + create_info.imageExtent = extent; + create_info.imageArrayLayers = 1; + create_info.imageUsage = + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + QueueFamilyIndices indices = vk_device_->GetQueueFamilyIndices(); + uint32_t queue_family_indices[] = {indices.graphics_family, + indices.present_family}; + + if (indices.graphics_family != indices.present_family) { + create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + create_info.queueFamilyIndexCount = 2; + create_info.pQueueFamilyIndices = queue_family_indices; + } else { + create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + create_info.preTransform = swap_chain_support.capabilities.currentTransform; + create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + create_info.presentMode = present_mode; + create_info.clipped = VK_TRUE; + create_info.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(vk_device_->GetDevice(), &create_info, nullptr, + &swapchain_) != VK_SUCCESS) { + LOGE("Failed to create swap chain"); + return false; + } + + // Get swapchain images + vkGetSwapchainImagesKHR(vk_device_->GetDevice(), swapchain_, &image_count, + nullptr); + swapchain_images_.resize(image_count); + vkGetSwapchainImagesKHR(vk_device_->GetDevice(), swapchain_, &image_count, + swapchain_images_.data()); + + swapchain_image_format_ = surface_format.format; + swapchain_extent_ = extent; + + LOGI("Swapchain created with %u images", image_count); + return true; +} + +bool GPUWindowSurfaceVk::CreateSwapchainImageViews() { + swapchain_image_views_.resize(swapchain_images_.size()); + + for (size_t i = 0; i < swapchain_images_.size(); i++) { + VkImageViewCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + create_info.image = swapchain_images_[i]; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + create_info.format = swapchain_image_format_; + create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + create_info.subresourceRange.baseMipLevel = 0; + create_info.subresourceRange.levelCount = 1; + create_info.subresourceRange.baseArrayLayer = 0; + create_info.subresourceRange.layerCount = 1; + + if (vkCreateImageView(vk_device_->GetDevice(), &create_info, nullptr, + &swapchain_image_views_[i]) != VK_SUCCESS) { + LOGE("Failed to create image views"); + return false; + } + } + + return true; +} + +bool GPUWindowSurfaceVk::CreateRenderPass() { + VkAttachmentDescription color_attachment{}; + color_attachment.format = swapchain_image_format_; + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference color_attachment_ref{}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = 1; + render_pass_info.pAttachments = &color_attachment; + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + if (vkCreateRenderPass(vk_device_->GetDevice(), &render_pass_info, nullptr, + &render_pass_) != VK_SUCCESS) { + LOGE("Failed to create render pass"); + return false; + } + + return true; +} + +bool GPUWindowSurfaceVk::CreateFramebuffers() { + swapchain_framebuffers_.resize(swapchain_image_views_.size()); + + for (size_t i = 0; i < swapchain_image_views_.size(); i++) { + VkImageView attachments[] = {swapchain_image_views_[i]}; + + VkFramebufferCreateInfo framebuffer_info{}; + framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_info.renderPass = render_pass_; + framebuffer_info.attachmentCount = 1; + framebuffer_info.pAttachments = attachments; + framebuffer_info.width = swapchain_extent_.width; + framebuffer_info.height = swapchain_extent_.height; + framebuffer_info.layers = 1; + + if (vkCreateFramebuffer(vk_device_->GetDevice(), &framebuffer_info, nullptr, + &swapchain_framebuffers_[i]) != VK_SUCCESS) { + LOGE("Failed to create framebuffer"); + return false; + } + } + + return true; +} + +bool GPUWindowSurfaceVk::CreateSyncObjects() { + image_available_semaphores_.resize(MAX_FRAMES_IN_FLIGHT); + render_finished_semaphores_.resize(MAX_FRAMES_IN_FLIGHT); + in_flight_fences_.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphore_info{}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fence_info{}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(vk_device_->GetDevice(), &semaphore_info, nullptr, + &image_available_semaphores_[i]) != VK_SUCCESS || + vkCreateSemaphore(vk_device_->GetDevice(), &semaphore_info, nullptr, + &render_finished_semaphores_[i]) != VK_SUCCESS || + vkCreateFence(vk_device_->GetDevice(), &fence_info, nullptr, + &in_flight_fences_[i]) != VK_SUCCESS) { + LOGE("Failed to create synchronization objects"); + return false; + } + } + + return true; +} + +void GPUWindowSurfaceVk::CleanupSwapchain() { + for (auto framebuffer : swapchain_framebuffers_) { + vkDestroyFramebuffer(vk_device_->GetDevice(), framebuffer, nullptr); + } + + if (render_pass_ != VK_NULL_HANDLE) { + vkDestroyRenderPass(vk_device_->GetDevice(), render_pass_, nullptr); + } + + for (auto image_view : swapchain_image_views_) { + vkDestroyImageView(vk_device_->GetDevice(), image_view, nullptr); + } + + if (swapchain_ != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(vk_device_->GetDevice(), swapchain_, nullptr); + } +} + +bool GPUWindowSurfaceVk::AcquireNextImage() { + // Check fence status first before waiting + VkResult fence_status = vkGetFenceStatus(vk_device_->GetDevice(), + in_flight_fences_[current_frame_]); + + VkResult fence_result = vkWaitForFences( + vk_device_->GetDevice(), 1, &in_flight_fences_[current_frame_], VK_TRUE, + 100000000ULL); // 0.1 second timeout - shorter to avoid blocking + if (fence_result == VK_TIMEOUT) { + LOGE("Fence wait timed out after 0.1 seconds - frame may be stuck"); + return false; + } else if (fence_result != VK_SUCCESS) { + LOGE("Fence wait failed: %d", fence_result); + return false; + } + LOGI("AcquireNextImage: Fence wait completed, acquiring image"); + + VkResult result = vkAcquireNextImageKHR( + vk_device_->GetDevice(), swapchain_, 100000000ULL, // 0.1 second timeout + image_available_semaphores_[current_frame_], VK_NULL_HANDLE, + ¤t_image_index_); + LOGI("AcquireNextImage: vkAcquireNextImageKHR result: %d, image_index: %u", + result, current_image_index_); + + if (result == VK_TIMEOUT) { + LOGE("Swapchain image acquisition timed out"); + return false; + } else if (result == VK_ERROR_OUT_OF_DATE_KHR) { + LOGE("Swapchain out of date - recreation needed"); + return false; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + LOGE("Failed to acquire swap chain image: %d", result); + return false; + } + + // Only reset fence after successful acquisition + vkResetFences(vk_device_->GetDevice(), 1, &in_flight_fences_[current_frame_]); + LOGI("AcquireNextImage: Success, image_index: %u", current_image_index_); + + return true; +} + +bool GPUWindowSurfaceVk::PresentImage() { + VkPresentInfoKHR present_info{}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + VkSemaphore wait_semaphores[] = {render_finished_semaphores_[current_frame_]}; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = wait_semaphores; + + VkSwapchainKHR swap_chains[] = {swapchain_}; + present_info.swapchainCount = 1; + present_info.pSwapchains = swap_chains; + present_info.pImageIndices = ¤t_image_index_; + + VkResult result = + vkQueuePresentKHR(vk_device_->GetPresentQueue(), &present_info); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + // TODO: Handle swapchain recreation + return false; + } else if (result != VK_SUCCESS) { + LOGE("Failed to present swap chain image"); + return false; + } + + current_frame_ = (current_frame_ + 1) % MAX_FRAMES_IN_FLIGHT; + + return true; +} + +GPUWindowSurfaceVk::SwapChainSupportDetails +GPUWindowSurfaceVk::QuerySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, + &details.capabilities); + + uint32_t format_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &format_count, + nullptr); + + if (format_count != 0) { + details.formats.resize(format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &format_count, + details.formats.data()); + } + + uint32_t present_mode_count; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, + &present_mode_count, nullptr); + + if (present_mode_count != 0) { + details.present_modes.resize(present_mode_count); + vkGetPhysicalDeviceSurfacePresentModesKHR( + device, surface_, &present_mode_count, details.present_modes.data()); + } + + return details; +} + +VkSurfaceFormatKHR GPUWindowSurfaceVk::ChooseSwapSurfaceFormat( + const std::vector& available_formats) { + // Prefer linear (UNORM) format to match GL behavior and avoid sRGB conversion + // issues + for (const auto& available_format : available_formats) { + if (available_format.format == VK_FORMAT_B8G8R8A8_UNORM && + available_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return available_format; + } + } + + // Fall back to RGBA UNORM if BGRA UNORM not available + for (const auto& available_format : available_formats) { + if (available_format.format == VK_FORMAT_R8G8B8A8_UNORM && + available_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return available_format; + } + } + + // Last resort: try sRGB format (will appear lighter due to gamma correction) + for (const auto& available_format : available_formats) { + if (available_format.format == VK_FORMAT_B8G8R8A8_SRGB && + available_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return available_format; + } + } + + return available_formats[0]; +} + +VkPresentModeKHR GPUWindowSurfaceVk::ChooseSwapPresentMode( + const std::vector& available_present_modes) { + for (const auto& available_present_mode : available_present_modes) { + if (available_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) { + return available_present_mode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D GPUWindowSurfaceVk::ChooseSwapExtent( + const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != + std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + VkExtent2D actual_extent = {GetWidth(), GetHeight()}; + + actual_extent.width = std::max( + capabilities.minImageExtent.width, + std::min(capabilities.maxImageExtent.width, actual_extent.width)); + actual_extent.height = std::max( + capabilities.minImageExtent.height, + std::min(capabilities.maxImageExtent.height, actual_extent.height)); + + return actual_extent; + } +} + +HWRootLayer* GPUWindowSurfaceVk::OnBeginNextFrame(bool clear) { + // Acquire next swapchain image + if (!AcquireNextImage()) { + LOGE("Failed to acquire next swapchain image"); + return nullptr; + } + + // Create a texture wrapper for the current swapchain image + GPUTextureDescriptor tex_desc; + tex_desc.format = + GPUTextureFormat::kRGBA8Unorm; // Convert from Vulkan format + tex_desc.width = swapchain_extent_.width; + tex_desc.height = swapchain_extent_.height; + tex_desc.usage = + static_cast(GPUTextureUsage::kRenderAttachment); + + // Create a texture wrapper around the current swapchain image + auto* device_vk = static_cast(GetGPUContext()->GetGPUDevice()); + VkImage current_swapchain_image = swapchain_images_[current_image_index_]; + VkFormat swapchain_format = swapchain_image_format_; + + auto swapchain_texture = GPUTextureVk::CreateFromVkImage( + device_vk, current_swapchain_image, swapchain_format, + swapchain_extent_.width, swapchain_extent_.height); + + if (!swapchain_texture) { + LOGE("Failed to create swapchain texture wrapper"); + return nullptr; + } + + // Create root layer for this frame + auto root_layer = GetArenaAllocator()->Make( + swapchain_texture, Rect::MakeWH(GetWidth(), GetHeight())); + + root_layer->SetClearSurface(clear); + root_layer->SetSampleCount(GetSampleCount()); + root_layer->SetArenaAllocator(GetArenaAllocator()); + + return root_layer; +} + +void GPUWindowSurfaceVk::OnFlush() { + // CRITICAL: Flush the canvas to execute all drawing commands + // The canvas rendering happens to the swapchain image via + // VkExternTextureLayer + FlushCanvas(); + + // Canvas rendering is complete. The canvas command submission already + // waited for GPU completion via vkQueueWaitIdle(), so the swapchain + // image now contains the rendered content. + + // Signal the fence for next frame synchronization + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &image_available_semaphores_[current_frame_]; + + VkPipelineStageFlags wait_stages[] = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submit_info.pWaitDstStageMask = wait_stages; + + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &render_finished_semaphores_[current_frame_]; + + // Submit empty command buffer for synchronization only + submit_info.commandBufferCount = 0; + submit_info.pCommandBuffers = nullptr; + + VkResult result = + vkQueueSubmit(vk_device_->GetGraphicsQueue(), 1, &submit_info, + in_flight_fences_[current_frame_]); + if (result != VK_SUCCESS) { + LOGE("Failed to submit presentation synchronization: %d", result); + } + + // Present the image + if (!PresentImage()) { + LOGE("Failed to present image"); + } +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/gpu_window_surface_vk.hpp b/src/gpu/vk/gpu_window_surface_vk.hpp new file mode 100644 index 00000000..9802d9f2 --- /dev/null +++ b/src/gpu/vk/gpu_window_surface_vk.hpp @@ -0,0 +1,107 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_GPU_WINDOW_SURFACE_VK_HPP +#define SRC_GPU_VK_GPU_WINDOW_SURFACE_VK_HPP + +#include + +#include +#include + +#include "src/gpu/vk/gpu_surface_vk.hpp" + +namespace skity { + +class GPUDeviceVk; +class GPUTextureVk; +class VkInterface; + +/** + * @brief Vulkan surface for on-screen rendering with swapchain support + */ +class GPUWindowSurfaceVk : public GPUSurfaceVk { + public: + GPUWindowSurfaceVk(GPUContextImpl* ctx, uint32_t width, uint32_t height, + uint32_t sample_count, float content_scale); + ~GPUWindowSurfaceVk() override; + + /** + * Initialize the surface with a pre-created VkSurfaceKHR + * The surface should be created using glfwCreateWindowSurface or equivalent + */ + bool InitWithSurface(VkSurfaceKHR surface, VkInterface* vk_interface); + + /** + * Check if the surface is valid and ready for rendering + */ + bool IsValid() const { + return surface_ != VK_NULL_HANDLE && swapchain_ != VK_NULL_HANDLE; + } + + protected: + HWRootLayer* OnBeginNextFrame(bool clear) override; + void OnFlush() override; + + private: + // Swapchain management + bool CreateSwapchain(); + bool CreateSwapchainImageViews(); + bool CreateRenderPass(); + bool CreateFramebuffers(); + bool CreateSyncObjects(); + void CleanupSwapchain(); + + // Frame management + bool AcquireNextImage(); + bool PresentImage(); + + // Query swapchain support details + struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector present_modes; + }; + SwapChainSupportDetails QuerySwapChainSupport(VkPhysicalDevice device); + + // Choose swapchain settings + VkSurfaceFormatKHR ChooseSwapSurfaceFormat( + const std::vector& available_formats); + VkPresentModeKHR ChooseSwapPresentMode( + const std::vector& available_present_modes); + VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities); + + private: + // Window surface + VkSurfaceKHR surface_ = VK_NULL_HANDLE; + + // Swapchain + VkSwapchainKHR swapchain_ = VK_NULL_HANDLE; + std::vector swapchain_images_; + std::vector swapchain_image_views_; + std::vector swapchain_framebuffers_; + VkFormat swapchain_image_format_; + VkExtent2D swapchain_extent_; + + // Render pass for swapchain + VkRenderPass render_pass_ = VK_NULL_HANDLE; + + // Synchronization + std::vector image_available_semaphores_; + std::vector render_finished_semaphores_; + std::vector in_flight_fences_; + size_t current_frame_ = 0; + uint32_t current_image_index_ = 0; + + // Maximum frames in flight + static constexpr size_t MAX_FRAMES_IN_FLIGHT = 2; + + // Device reference + GPUDeviceVk* vk_device_ = nullptr; + VkInstance instance_ = VK_NULL_HANDLE; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_GPU_WINDOW_SURFACE_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/spirv_compiler_vk.cc b/src/gpu/vk/spirv_compiler_vk.cc new file mode 100644 index 00000000..f80f8af5 --- /dev/null +++ b/src/gpu/vk/spirv_compiler_vk.cc @@ -0,0 +1,330 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/spirv_compiler_vk.hpp" + +#include +#include +#include + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/logging.hpp" + +// Configure SPIRV-Cross to work without exceptions +#define SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS + +// We'll use a simplified approach for now - in a real implementation +// you would integrate with glslang or use shaderc for GLSL→SPIRV compilation +#include "third_party/SPIRV-Cross/spirv_cross.hpp" +#include "third_party/glslang/SPIRV/GlslangToSpv.h" +#include "third_party/glslang/glslang/Public/ResourceLimits.h" +#include "third_party/glslang/glslang/Public/ShaderLang.h" + +namespace skity { + +bool SPIRVCompilerVk::glslang_initialized_ = false; + +SPIRVCompilerVk::SPIRVCompilerVk(GPUDeviceVk* device) : device_(device) { + if (!glslang_initialized_) { + InitializeGlslang(); + } +} + +SPIRVCompilerVk::~SPIRVCompilerVk() { + // Note: We don't finalize glslang here as it's global + // and other instances might still need it +} + +bool SPIRVCompilerVk::InitializeGlslang() { + if (!glslang_initialized_) { + glslang::InitializeProcess(); + glslang_initialized_ = true; + LOGI("glslang initialized for SPIRV compilation"); + } + return true; +} + +void SPIRVCompilerVk::FinalizeGlslang() { + if (glslang_initialized_) { + glslang::FinalizeProcess(); + glslang_initialized_ = false; + } +} + +SPIRVCompileResult SPIRVCompilerVk::CompileWGSLToSPIRV( + const std::string& wgsl_source, const std::string& entry_point, + GPUShaderStage stage, const SPIRVCompileOptions& options) { + // Generate cache key and check cache first + std::string cache_key = + GenerateCacheKey(wgsl_source, entry_point, stage, options); + auto cached = GetCachedShader(cache_key); + if (cached) { + return *cached; + } + + SPIRVCompileResult result; + + // Step 1: Convert WGSL to GLSL using existing wgx pipeline + auto glsl_source = ConvertWGSLToGLSL(wgsl_source, entry_point, stage); + if (!glsl_source) { + result.error_message = "Failed to convert WGSL to GLSL"; + return result; + } + + // Step 2: Compile GLSL to SPIRV + result = CompileGLSLToSPIRV(*glsl_source, stage, options); + + if (result.success) { + CacheShader(cache_key, result); + } + + return result; +} + +SPIRVCompileResult SPIRVCompilerVk::CompileGLSLToSPIRV( + const std::string& glsl_source, GPUShaderStage stage, + const SPIRVCompileOptions& options) { + SPIRVCompileResult result; + + // Real implementation using glslang + EShLanguage glsl_stage = static_cast(GetGlslangStage(stage)); + + glslang::TShader shader(glsl_stage); + const char* source_cstr = glsl_source.c_str(); + shader.setStrings(&source_cstr, 1); + + // Set environment + shader.setEnvInput(glslang::EShSourceGlsl, glsl_stage, + glslang::EShClientVulkan, 100); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); + + // Configure default built-in resources + const TBuiltInResource* resources = GetDefaultResources(); + + // Parse shader + if (!shader.parse(resources, 100, false, EShMsgDefault)) { + result.error_message = + "GLSL parse error: " + std::string(shader.getInfoLog()); + return result; + } + + // Link program + glslang::TProgram program; + program.addShader(&shader); + + if (!program.link(EShMsgDefault)) { + result.error_message = + "GLSL link error: " + std::string(program.getInfoLog()); + return result; + } + + // Generate SPIRV + std::vector spirv; + spv::SpvBuildLogger logger; + glslang::SpvOptions spv_options; + spv_options.generateDebugInfo = options.debug_info; + spv_options.disableOptimizer = !options.optimize; + spv_options.optimizeSize = false; + + glslang::GlslangToSpv(*program.getIntermediate(glsl_stage), spirv, &logger, + &spv_options); + + if (!logger.getAllMessages().empty()) { + LOGW("SPIRV generation warnings: %s", logger.getAllMessages().c_str()); + } + + // Convert to uint32_t vector + result.spirv_code.assign(spirv.begin(), spirv.end()); + + // Extract reflection information + auto reflection = ReflectSPIRV(result.spirv_code, stage); + if (reflection) { + result.reflection = *reflection; + } + + result.success = true; + + // Validate SPIRV if requested + if (options.validate && !ValidateSPIRV(result.spirv_code)) { + result.error_message = "Generated SPIRV failed validation"; + result.success = false; + } + + return result; +} + +std::optional SPIRVCompilerVk::ReflectSPIRV( + const std::vector& spirv_code, GPUShaderStage stage) { + spirv_cross::Compiler compiler(spirv_code); + spirv_cross::ShaderResources resources = compiler.get_shader_resources(); + + SPIRVReflectionInfo reflection; + reflection.stage = stage; + + // Extract uniform buffers + for (const auto& ubo : resources.uniform_buffers) { + SPIRVReflectionInfo::UniformBinding binding; + binding.set = compiler.get_decoration(ubo.id, spv::DecorationDescriptorSet); + binding.binding = compiler.get_decoration(ubo.id, spv::DecorationBinding); + binding.name = ubo.name; + binding.stage = stage; + + // Get size information + const auto& type = compiler.get_type(ubo.base_type_id); + binding.size = compiler.get_declared_struct_size(type); + + reflection.uniform_bindings.push_back(binding); + } + + // Extract texture samplers + for (const auto& image : resources.sampled_images) { + SPIRVReflectionInfo::TextureBinding binding; + binding.set = + compiler.get_decoration(image.id, spv::DecorationDescriptorSet); + binding.binding = compiler.get_decoration(image.id, spv::DecorationBinding); + binding.name = image.name; + binding.stage = stage; + + reflection.texture_bindings.push_back(binding); + } + + // Extract separate samplers + for (const auto& sampler : resources.separate_samplers) { + SPIRVReflectionInfo::SamplerBinding binding; + binding.set = + compiler.get_decoration(sampler.id, spv::DecorationDescriptorSet); + binding.binding = + compiler.get_decoration(sampler.id, spv::DecorationBinding); + binding.name = sampler.name; + binding.stage = stage; + + reflection.sampler_bindings.push_back(binding); + } + + return reflection; +} + +std::optional SPIRVCompilerVk::ConvertWGSLToGLSL( + const std::string& wgsl_source, const std::string& entry_point, + GPUShaderStage stage) { + // Use existing wgx pipeline to convert WGSL to GLSL + auto program = wgx::Program::Parse(wgsl_source); + if (!program) { + LOGE("Failed to parse WGSL source"); + return std::nullopt; + } + + // Configure GLSL options + wgx::GlslOptions glsl_options; + glsl_options.standard = wgx::GlslOptions::Standard::kDesktop; + glsl_options.major_version = 4; + glsl_options.minor_version = 5; + + auto result = program->WriteToGlsl(entry_point.c_str(), glsl_options); + if (!result) { + auto diagnosis = program->GetDiagnosis(); + if (diagnosis) { + LOGE("WGSL to GLSL conversion failed: %s (line %zu, column %zu)", + diagnosis->message.c_str(), diagnosis->line, diagnosis->column); + } else { + LOGE("WGSL to GLSL conversion failed"); + } + return std::nullopt; + } + + return result.content; +} + +void SPIRVCompilerVk::CacheShader(const std::string& key, + const SPIRVCompileResult& result) { + shader_cache_[key] = result; +} + +std::optional SPIRVCompilerVk::GetCachedShader( + const std::string& key) { + auto it = shader_cache_.find(key); + if (it != shader_cache_.end()) { + return it->second; + } + return std::nullopt; +} + +void SPIRVCompilerVk::ClearCache() { shader_cache_.clear(); } + +std::string SPIRVCompilerVk::GenerateCacheKey( + const std::string& source, const std::string& entry_point, + GPUShaderStage stage, const SPIRVCompileOptions& options) { + std::ostringstream oss; + oss << SPIRVUtils::HashShaderSource(source) << "_" << entry_point << "_" + << static_cast(stage) << "_" << (options.optimize ? "opt" : "noopt") + << "_" << (options.debug_info ? "debug" : "nodebug") << "_" + << (options.validate ? "val" : "noval"); + + return oss.str(); +} + +uint32_t SPIRVCompilerVk::GetGlslangStage(GPUShaderStage stage) { + switch (stage) { + case GPUShaderStage::kVertex: + return EShLangVertex; + case GPUShaderStage::kFragment: + return EShLangFragment; + default: + return EShLangVertex; + } +} + +bool SPIRVCompilerVk::ValidateSPIRV(const std::vector& spirv_code) { + // Basic validation - check magic number and minimum size + if (spirv_code.size() < 5) { + return false; + } + + if (spirv_code[0] != 0x07230203) { // SPIRV magic number + return false; + } + + // For more thorough validation, you would use spirv-val + // from SPIRV-Tools, but this basic check is sufficient for now + return true; +} + +// SPIRVUtils namespace implementation +namespace SPIRVUtils { + +std::string HashShaderSource(const std::string& source) { + // Simple hash implementation + std::hash hasher; + size_t hash = hasher(source); + + std::ostringstream oss; + oss << std::hex << hash; + return oss.str(); +} + +const char* ShaderStageToString(GPUShaderStage stage) { + switch (stage) { + case GPUShaderStage::kVertex: + return "vertex"; + case GPUShaderStage::kFragment: + return "fragment"; + default: + return "unknown"; + } +} + +SPIRVCompileOptions GetDefaultOptions(GPUShaderStage stage) { + SPIRVCompileOptions options; + options.optimize = true; + options.debug_info = false; + options.validate = true; + options.target_env_version = 0; // Automatic + + return options; +} + +} // namespace SPIRVUtils + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/spirv_compiler_vk.hpp b/src/gpu/vk/spirv_compiler_vk.hpp new file mode 100644 index 00000000..35dad3e3 --- /dev/null +++ b/src/gpu/vk/spirv_compiler_vk.hpp @@ -0,0 +1,233 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_SPIRV_COMPILER_VK_HPP +#define SRC_GPU_VK_SPIRV_COMPILER_VK_HPP + +#include +#include +#include +#include +#include + +#include "src/gpu/gpu_shader_function.hpp" +#include "wgsl_cross.h" + +namespace skity { + +class GPUDeviceVk; + +struct SPIRVCompileOptions { + bool optimize = true; + bool debug_info = false; + bool validate = true; + uint32_t target_env_version = 0; // 0 = automatic detection +}; + +struct SPIRVReflectionInfo { + struct UniformBinding { + uint32_t set; + uint32_t binding; + std::string name; + size_t size; + GPUShaderStage stage; + }; + + struct TextureBinding { + uint32_t set; + uint32_t binding; + std::string name; + GPUShaderStage stage; + }; + + struct SamplerBinding { + uint32_t set; + uint32_t binding; + std::string name; + GPUShaderStage stage; + }; + + std::vector uniform_bindings; + std::vector texture_bindings; + std::vector sampler_bindings; + + // Entry point information + std::string entry_point; + GPUShaderStage stage; + + // Vertex input attributes (for vertex shaders) + struct VertexAttribute { + uint32_t location; + std::string name; + uint32_t format; // VkFormat + }; + std::vector vertex_attributes; + + // Push constant information + struct PushConstant { + uint32_t offset; + uint32_t size; + GPUShaderStage stage; + }; + std::optional push_constant; +}; + +struct SPIRVCompileResult { + std::vector spirv_code; + SPIRVReflectionInfo reflection; + bool success = false; + std::string error_message; + + operator bool() const { return success; } +}; + +/** + * SPIRV Compiler for Vulkan backend + * + * This class integrates with the existing WGSL→GLSL pipeline and adds + * GLSL→SPIRV compilation capabilities for Vulkan shaders. + * + * Pipeline: WGSL → GLSL → SPIRV (via glslang) + */ +class SPIRVCompilerVk { + public: + explicit SPIRVCompilerVk(GPUDeviceVk* device); + ~SPIRVCompilerVk(); + + /** + * Compile WGSL source to SPIRV bytecode + * + * @param wgsl_source The WGSL shader source code + * @param entry_point The shader entry point function name + * @param stage The shader stage (vertex, fragment, etc.) + * @param options Compilation options + * @return Compilation result with SPIRV bytecode and reflection info + */ + SPIRVCompileResult CompileWGSLToSPIRV( + const std::string& wgsl_source, const std::string& entry_point, + GPUShaderStage stage, const SPIRVCompileOptions& options = {}); + + /** + * Compile GLSL source to SPIRV bytecode (direct compilation) + * + * @param glsl_source The GLSL shader source code + * @param stage The shader stage + * @param options Compilation options + * @return Compilation result with SPIRV bytecode and reflection info + */ + SPIRVCompileResult CompileGLSLToSPIRV( + const std::string& glsl_source, GPUShaderStage stage, + const SPIRVCompileOptions& options = {}); + + /** + * Get reflection information from compiled SPIRV bytecode + * + * @param spirv_code The compiled SPIRV bytecode + * @param stage The shader stage + * @return Reflection information for descriptor set binding + */ + std::optional ReflectSPIRV( + const std::vector& spirv_code, GPUShaderStage stage); + + /** + * Cache compiled shaders for reuse + * + * @param key Unique identifier for the shader + * @param result Compilation result to cache + */ + void CacheShader(const std::string& key, const SPIRVCompileResult& result); + + /** + * Retrieve cached shader + * + * @param key Unique identifier for the shader + * @return Cached compilation result if found + */ + std::optional GetCachedShader(const std::string& key); + + /** + * Clear shader cache + */ + void ClearCache(); + + /** + * Initialize glslang (called automatically) + */ + static bool InitializeGlslang(); + + /** + * Finalize glslang (called automatically) + */ + static void FinalizeGlslang(); + + private: + GPUDeviceVk* device_; + + // Shader cache for compiled SPIRV + std::unordered_map shader_cache_; + + /** + * Convert WGSL to GLSL using existing wgx pipeline + */ + std::optional ConvertWGSLToGLSL(const std::string& wgsl_source, + const std::string& entry_point, + GPUShaderStage stage); + + /** + * Get GLSL version string for shader stage + */ + std::string GetGLSLVersionString(GPUShaderStage stage); + + /** + * Generate shader cache key + */ + std::string GenerateCacheKey(const std::string& source, + const std::string& entry_point, + GPUShaderStage stage, + const SPIRVCompileOptions& options); + + /** + * Extract binding information from wgx result + */ + void ExtractBindingInfo(const wgx::Result& wgx_result, + SPIRVReflectionInfo& reflection); + + /** + * Convert GPU shader stage to glslang stage + */ + uint32_t GetGlslangStage(GPUShaderStage stage); + + /** + * Validate SPIRV bytecode + */ + bool ValidateSPIRV(const std::vector& spirv_code); + + static bool glslang_initialized_; +}; + +/** + * Helper functions for SPIRV processing + */ +namespace SPIRVUtils { + +/** + * Generate a unique hash for shader source + */ +std::string HashShaderSource(const std::string& source); + +/** + * Convert GPU shader stage to string + */ +const char* ShaderStageToString(GPUShaderStage stage); + +/** + * Get default SPIRV compile options for a stage + */ +SPIRVCompileOptions GetDefaultOptions(GPUShaderStage stage); + +} // namespace SPIRVUtils + +} // namespace skity + +#endif // SRC_GPU_VK_SPIRV_COMPILER_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/sync_objects_vk.cc b/src/gpu/vk/sync_objects_vk.cc new file mode 100644 index 00000000..c94760c7 --- /dev/null +++ b/src/gpu/vk/sync_objects_vk.cc @@ -0,0 +1,370 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/sync_objects_vk.hpp" + +#include "src/gpu/vk/gpu_device_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +// VkSemaphore implementation +VkSemaphore::VkSemaphore(GPUDeviceVk* device) : device_(device) { + if (!device_) { + LOGE("Invalid device for semaphore creation"); + return; + } + + VkSemaphoreCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkResult result = vkCreateSemaphore(device_->GetDevice(), &create_info, + nullptr, &semaphore_); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan semaphore: %d", result); + semaphore_ = VK_NULL_HANDLE; + } +} + +VkSemaphore::~VkSemaphore() { Destroy(); } + +VkSemaphore::VkSemaphore(VkSemaphore&& other) noexcept + : device_(other.device_), semaphore_(other.semaphore_) { + other.device_ = nullptr; + other.semaphore_ = VK_NULL_HANDLE; +} + +VkSemaphore& VkSemaphore::operator=(VkSemaphore&& other) noexcept { + if (this != &other) { + Destroy(); + device_ = other.device_; + semaphore_ = other.semaphore_; + other.device_ = nullptr; + other.semaphore_ = VK_NULL_HANDLE; + } + return *this; +} + +void VkSemaphore::Destroy() { + if (semaphore_ != VK_NULL_HANDLE && device_) { + vkDestroySemaphore(device_->GetDevice(), semaphore_, nullptr); + semaphore_ = VK_NULL_HANDLE; + } +} + +// VkFence implementation +VkFence::VkFence(GPUDeviceVk* device, bool signaled) : device_(device) { + if (!device_) { + LOGE("Invalid device for fence creation"); + return; + } + + VkFenceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + if (signaled) { + create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + } + + VkResult result = + vkCreateFence(device_->GetDevice(), &create_info, nullptr, &fence_); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan fence: %d", result); + fence_ = VK_NULL_HANDLE; + } +} + +VkFence::~VkFence() { Destroy(); } + +VkFence::VkFence(VkFence&& other) noexcept + : device_(other.device_), fence_(other.fence_) { + other.device_ = nullptr; + other.fence_ = VK_NULL_HANDLE; +} + +VkFence& VkFence::operator=(VkFence&& other) noexcept { + if (this != &other) { + Destroy(); + device_ = other.device_; + fence_ = other.fence_; + other.device_ = nullptr; + other.fence_ = VK_NULL_HANDLE; + } + return *this; +} + +void VkFence::Destroy() { + if (fence_ != VK_NULL_HANDLE && device_) { + vkDestroyFence(device_->GetDevice(), fence_, nullptr); + fence_ = VK_NULL_HANDLE; + } +} + +bool VkFence::Wait(uint64_t timeout_ns) const { + if (!IsValid()) { + return false; + } + + VkResult result = + vkWaitForFences(device_->GetDevice(), 1, &fence_, VK_TRUE, timeout_ns); + if (result == VK_SUCCESS) { + return true; + } else if (result == VK_TIMEOUT) { + return false; + } else { + LOGE("Failed to wait for fence: %d", result); + return false; + } +} + +bool VkFence::IsSignaled() const { + if (!IsValid()) { + return false; + } + + VkResult result = vkGetFenceStatus(device_->GetDevice(), fence_); + return result == VK_SUCCESS; +} + +void VkFence::Reset() { + if (!IsValid()) { + return; + } + + VkResult result = vkResetFences(device_->GetDevice(), 1, &fence_); + if (result != VK_SUCCESS) { + LOGE("Failed to reset fence: %d", result); + } +} + +// VkSyncManager implementation +VkSyncManager::VkSyncManager(GPUDeviceVk* device) : device_(device) {} + +void VkSyncManager::AddMemoryBarrier(const VkMemoryBarrier& barrier) { + VkMemoryBarrier2KHR vk_barrier{}; + vk_barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR; + vk_barrier.srcStageMask = barrier.src_stage_mask; + vk_barrier.srcAccessMask = barrier.src_access_mask; + vk_barrier.dstStageMask = barrier.dst_stage_mask; + vk_barrier.dstAccessMask = barrier.dst_access_mask; + + memory_barriers_.push_back(vk_barrier); + src_stage_mask_ |= barrier.src_stage_mask; + dst_stage_mask_ |= barrier.dst_stage_mask; +} + +void VkSyncManager::AddImageBarrier(const VkImageBarrier& barrier) { + VkImageMemoryBarrier2KHR vk_barrier{}; + vk_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2_KHR; + vk_barrier.srcStageMask = barrier.src_stage_mask; + vk_barrier.srcAccessMask = barrier.src_access_mask; + vk_barrier.dstStageMask = barrier.dst_stage_mask; + vk_barrier.dstAccessMask = barrier.dst_access_mask; + vk_barrier.oldLayout = barrier.old_layout; + vk_barrier.newLayout = barrier.new_layout; + vk_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + vk_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + vk_barrier.image = barrier.image; + vk_barrier.subresourceRange = barrier.subresource_range; + + image_barriers_.push_back(vk_barrier); + src_stage_mask_ |= barrier.src_stage_mask; + dst_stage_mask_ |= barrier.dst_stage_mask; +} + +void VkSyncManager::AddBufferBarrier(const VkBufferBarrier& barrier) { + VkBufferMemoryBarrier2KHR vk_barrier{}; + vk_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + vk_barrier.srcStageMask = barrier.src_stage_mask; + vk_barrier.srcAccessMask = barrier.src_access_mask; + vk_barrier.dstStageMask = barrier.dst_stage_mask; + vk_barrier.dstAccessMask = barrier.dst_access_mask; + vk_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + vk_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + vk_barrier.buffer = barrier.buffer; + vk_barrier.offset = barrier.offset; + vk_barrier.size = barrier.size; + + buffer_barriers_.push_back(vk_barrier); + src_stage_mask_ |= barrier.src_stage_mask; + dst_stage_mask_ |= barrier.dst_stage_mask; +} + +void VkSyncManager::ExecuteBarriers(VkCommandBuffer cmd_buffer) { + if (memory_barriers_.empty() && image_barriers_.empty() && + buffer_barriers_.empty()) { + return; + } + + // Use VK_KHR_synchronization2 if available, fallback to legacy barriers + VkDependencyInfoKHR dependency_info{}; + dependency_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR; + dependency_info.memoryBarrierCount = + static_cast(memory_barriers_.size()); + dependency_info.pMemoryBarriers = memory_barriers_.data(); + dependency_info.imageMemoryBarrierCount = + static_cast(image_barriers_.size()); + dependency_info.pImageMemoryBarriers = image_barriers_.data(); + dependency_info.bufferMemoryBarrierCount = + static_cast(buffer_barriers_.size()); + dependency_info.pBufferMemoryBarriers = buffer_barriers_.data(); + + // Try to use synchronization2 extension, fallback to legacy if not available + if (device_->HasSynchronization2Support() && + vkCmdPipelineBarrier2KHR != nullptr) { + vkCmdPipelineBarrier2KHR(cmd_buffer, &dependency_info); + } else { + // Fall back to legacy barriers (either not supported or function not + // loaded) Convert to legacy barriers for compatibility + std::vector<::VkMemoryBarrier> legacy_memory_barriers; + std::vector<::VkImageMemoryBarrier> legacy_image_barriers; + std::vector<::VkBufferMemoryBarrier> legacy_buffer_barriers; + + for (const auto& barrier : memory_barriers_) { + ::VkMemoryBarrier legacy_barrier{}; + legacy_barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + legacy_barrier.srcAccessMask = barrier.srcAccessMask; + legacy_barrier.dstAccessMask = barrier.dstAccessMask; + legacy_memory_barriers.push_back(legacy_barrier); + } + + for (const auto& barrier : image_barriers_) { + ::VkImageMemoryBarrier legacy_barrier{}; + legacy_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + legacy_barrier.srcAccessMask = barrier.srcAccessMask; + legacy_barrier.dstAccessMask = barrier.dstAccessMask; + legacy_barrier.oldLayout = barrier.oldLayout; + legacy_barrier.newLayout = barrier.newLayout; + legacy_barrier.srcQueueFamilyIndex = barrier.srcQueueFamilyIndex; + legacy_barrier.dstQueueFamilyIndex = barrier.dstQueueFamilyIndex; + legacy_barrier.image = barrier.image; + legacy_barrier.subresourceRange = barrier.subresourceRange; + legacy_image_barriers.push_back(legacy_barrier); + } + + for (const auto& barrier : buffer_barriers_) { + ::VkBufferMemoryBarrier legacy_barrier{}; + legacy_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + legacy_barrier.srcAccessMask = barrier.srcAccessMask; + legacy_barrier.dstAccessMask = barrier.dstAccessMask; + legacy_barrier.srcQueueFamilyIndex = barrier.srcQueueFamilyIndex; + legacy_barrier.dstQueueFamilyIndex = barrier.dstQueueFamilyIndex; + legacy_barrier.buffer = barrier.buffer; + legacy_barrier.offset = barrier.offset; + legacy_barrier.size = barrier.size; + legacy_buffer_barriers.push_back(legacy_barrier); + } + + vkCmdPipelineBarrier(cmd_buffer, src_stage_mask_, dst_stage_mask_, 0, + static_cast(legacy_memory_barriers.size()), + legacy_memory_barriers.data(), + static_cast(legacy_buffer_barriers.size()), + legacy_buffer_barriers.data(), + static_cast(legacy_image_barriers.size()), + legacy_image_barriers.data()); + } +} + +void VkSyncManager::Reset() { + memory_barriers_.clear(); + image_barriers_.clear(); + buffer_barriers_.clear(); + src_stage_mask_ = 0; + dst_stage_mask_ = 0; +} + +VkImageBarrier VkSyncManager::CreateImageTransitionBarrier( + VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, + VkImageAspectFlags aspect_mask) { + VkImageBarrier barrier{}; + barrier.old_layout = old_layout; + barrier.new_layout = new_layout; + barrier.image = image; + barrier.subresource_range.aspectMask = aspect_mask; + barrier.subresource_range.baseMipLevel = 0; + barrier.subresource_range.levelCount = 1; + barrier.subresource_range.baseArrayLayer = 0; + barrier.subresource_range.layerCount = 1; + + // Set appropriate access masks and pipeline stages based on layouts + if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && + new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.src_access_mask = 0; + barrier.dst_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT; + barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT; + barrier.dst_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && + new_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier.src_access_mask = 0; + barrier.dst_access_mask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + barrier.src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + barrier.dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } else { + // General case - may need optimization for specific transitions + barrier.src_access_mask = + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + barrier.dst_access_mask = + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + barrier.src_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + barrier.dst_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + } + + return barrier; +} + +VkBufferBarrier VkSyncManager::CreateBufferBarrier(VkBuffer buffer, + VkAccessFlags src_access, + VkAccessFlags dst_access, + VkDeviceSize offset, + VkDeviceSize size) { + VkBufferBarrier barrier{}; + barrier.buffer = buffer; + barrier.src_access_mask = src_access; + barrier.dst_access_mask = dst_access; + barrier.offset = offset; + barrier.size = size; + + // Set appropriate pipeline stages based on access patterns + if (src_access & + (VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT)) { + barrier.src_stage_mask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; + } + if (src_access & VK_ACCESS_UNIFORM_READ_BIT) { + barrier.src_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + if (src_access & VK_ACCESS_TRANSFER_WRITE_BIT) { + barrier.src_stage_mask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + } + + if (dst_access & + (VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT)) { + barrier.dst_stage_mask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; + } + if (dst_access & VK_ACCESS_UNIFORM_READ_BIT) { + barrier.dst_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + if (dst_access & VK_ACCESS_TRANSFER_READ_BIT) { + barrier.dst_stage_mask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + } + + // Default stages if none set + if (barrier.src_stage_mask == 0) { + barrier.src_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + } + if (barrier.dst_stage_mask == 0) { + barrier.dst_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + } + + return barrier; +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/sync_objects_vk.hpp b/src/gpu/vk/sync_objects_vk.hpp new file mode 100644 index 00000000..55d10b2c --- /dev/null +++ b/src/gpu/vk/sync_objects_vk.hpp @@ -0,0 +1,138 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_SYNC_OBJECTS_VK_HPP +#define SRC_GPU_VK_SYNC_OBJECTS_VK_HPP + +#include + +#include +#include + +namespace skity { + +class GPUDeviceVk; + +// Vulkan semaphore wrapper for GPU-GPU synchronization +class VkSemaphore { + public: + explicit VkSemaphore(GPUDeviceVk* device); + ~VkSemaphore(); + + VkSemaphore(const VkSemaphore&) = delete; + VkSemaphore& operator=(const VkSemaphore&) = delete; + + VkSemaphore(VkSemaphore&& other) noexcept; + VkSemaphore& operator=(VkSemaphore&& other) noexcept; + + VkSemaphore_T* GetHandle() const { return semaphore_; } + bool IsValid() const { return semaphore_ != VK_NULL_HANDLE; } + + private: + void Destroy(); + + GPUDeviceVk* device_ = nullptr; + VkSemaphore_T* semaphore_ = VK_NULL_HANDLE; +}; + +// Vulkan fence wrapper for CPU-GPU synchronization +class VkFence { + public: + explicit VkFence(GPUDeviceVk* device, bool signaled = false); + ~VkFence(); + + VkFence(const VkFence&) = delete; + VkFence& operator=(const VkFence&) = delete; + + VkFence(VkFence&& other) noexcept; + VkFence& operator=(VkFence&& other) noexcept; + + VkFence_T* GetHandle() const { return fence_; } + bool IsValid() const { return fence_ != VK_NULL_HANDLE; } + + // Fence operations + bool Wait(uint64_t timeout_ns = UINT64_MAX) const; + bool IsSignaled() const; + void Reset(); + + private: + void Destroy(); + + GPUDeviceVk* device_ = nullptr; + VkFence_T* fence_ = VK_NULL_HANDLE; +}; + +// Memory barrier wrapper for resource transitions +struct VkMemoryBarrier { + VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkAccessFlags src_access_mask = 0; + VkAccessFlags dst_access_mask = 0; +}; + +// Image layout transition helper +struct VkImageBarrier { + VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkAccessFlags src_access_mask = 0; + VkAccessFlags dst_access_mask = 0; + VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout new_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImage image = VK_NULL_HANDLE; + VkImageSubresourceRange subresource_range{}; +}; + +// Buffer barrier for buffer access synchronization +struct VkBufferBarrier { + VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkAccessFlags src_access_mask = 0; + VkAccessFlags dst_access_mask = 0; + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceSize offset = 0; + VkDeviceSize size = VK_WHOLE_SIZE; +}; + +// Synchronization manager for efficient barrier management +class VkSyncManager { + public: + explicit VkSyncManager(GPUDeviceVk* device); + ~VkSyncManager() = default; + + // Barrier recording + void AddMemoryBarrier(const VkMemoryBarrier& barrier); + void AddImageBarrier(const VkImageBarrier& barrier); + void AddBufferBarrier(const VkBufferBarrier& barrier); + + // Execute all recorded barriers + void ExecuteBarriers(VkCommandBuffer cmd_buffer); + + // Clear recorded barriers + void Reset(); + + // Utility functions for common transitions + static VkImageBarrier CreateImageTransitionBarrier( + VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, + VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT); + + static VkBufferBarrier CreateBufferBarrier(VkBuffer buffer, + VkAccessFlags src_access, + VkAccessFlags dst_access, + VkDeviceSize offset = 0, + VkDeviceSize size = VK_WHOLE_SIZE); + + private: + GPUDeviceVk* device_ = nullptr; + + std::vector memory_barriers_; + std::vector image_barriers_; + std::vector buffer_barriers_; + + VkPipelineStageFlags src_stage_mask_ = 0; + VkPipelineStageFlags dst_stage_mask_ = 0; +}; + +} // namespace skity + +#endif // SRC_GPU_VK_SYNC_OBJECTS_VK_HPP \ No newline at end of file diff --git a/src/gpu/vk/vk_interface.cc b/src/gpu/vk/vk_interface.cc new file mode 100644 index 00000000..d1178f6b --- /dev/null +++ b/src/gpu/vk/vk_interface.cc @@ -0,0 +1,229 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/gpu/vk/vk_interface.hpp" + +#include +#include + +#include "src/logging.hpp" + +namespace skity { + +const std::vector VkInterface::kValidationLayers = { + "VK_LAYER_KHRONOS_validation"}; + +VkInterface::VkInterface() = default; + +VkInterface::~VkInterface() { + if (instance_ != VK_NULL_HANDLE) { + vkDestroyInstance(instance_, nullptr); + } +} + +bool VkInterface::Init() { + // Initialize Volk + VkResult result = volkInitialize(); + if (result != VK_SUCCESS) { + LOGE("Failed to initialize Volk: {}", result); + return false; + } + + if (!CreateInstance()) { + return false; + } + + // Load instance functions + volkLoadInstance(instance_); + + EnumeratePhysicalDevices(); + + return true; +} + +bool VkInterface::CreateInstance() { + VkApplicationInfo app_info{}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = "Skity Application"; + app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + app_info.pEngineName = "Skity"; + app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); + app_info.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + create_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + + auto extensions = GetRequiredInstanceExtensions(); + create_info.enabledExtensionCount = static_cast(extensions.size()); + create_info.ppEnabledExtensionNames = extensions.data(); + +#ifdef SKITY_DEBUG + validation_layers_enabled_ = CheckValidationLayerSupport(); + if (validation_layers_enabled_) { + create_info.enabledLayerCount = + static_cast(kValidationLayers.size()); + create_info.ppEnabledLayerNames = kValidationLayers.data(); + LOGI("Vulkan validation layers enabled"); + } +#endif + + VkResult result = vkCreateInstance(&create_info, nullptr, &instance_); + if (result != VK_SUCCESS) { + LOGE("Failed to create Vulkan instance: {}", result); + return false; + } + + LOGI("Vulkan instance created successfully"); + return true; +} + +void VkInterface::EnumeratePhysicalDevices() { + uint32_t device_count = 0; + vkEnumeratePhysicalDevices(instance_, &device_count, nullptr); + + if (device_count == 0) { + LOGE("Failed to find GPUs with Vulkan support"); + return; + } + + physical_devices_.resize(device_count); + vkEnumeratePhysicalDevices(instance_, &device_count, + physical_devices_.data()); + + LOGI("Found {} Vulkan physical devices", device_count); +} + +VkPhysicalDevice VkInterface::SelectBestPhysicalDevice() const { + if (physical_devices_.empty()) { + return VK_NULL_HANDLE; + } + + // For now, just return the first discrete GPU, or the first device if none + // found + for (auto device : physical_devices_) { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + LOGI("Selected discrete GPU: {}", properties.deviceName); + return device; + } + } + + // Fall back to first available device + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(physical_devices_[0], &properties); + LOGI("Selected device: {}", properties.deviceName); + return physical_devices_[0]; +} + +bool VkInterface::CheckValidationLayerSupport() const { + uint32_t layer_count; + vkEnumerateInstanceLayerProperties(&layer_count, nullptr); + + std::vector available_layers(layer_count); + vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data()); + + for (const char* layer_name : kValidationLayers) { + bool layer_found = false; + + for (const auto& layer_properties : available_layers) { + if (strcmp(layer_name, layer_properties.layerName) == 0) { + layer_found = true; + break; + } + } + + if (!layer_found) { + return false; + } + } + + return true; +} + +std::vector VkInterface::GetRequiredInstanceExtensions() const { + std::vector extensions; + + // Add platform-specific extensions +#ifdef VK_USE_PLATFORM_WIN32_KHR + extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#endif +#ifdef VK_USE_PLATFORM_ANDROID_KHR + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); +#endif + + // Common extensions + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + + // Add macOS surface extension for GLFW window surfaces +#ifdef __APPLE__ + // Required for MoltenVK portability + extensions.push_back("VK_EXT_metal_surface"); + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#endif + +#ifdef SKITY_DEBUG + if (IsValidationLayersAvailable()) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } +#endif + + return extensions; +} + +std::vector VkInterface::GetRequiredDeviceExtensions() const { + return {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; +} + +std::vector VkInterface::GetRequiredDeviceExtensions( + VkPhysicalDevice device) const { + std::vector extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + // Check if VK_KHR_portability_subset is available and required + uint32_t extension_count; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, + nullptr); + + std::vector available_extensions(extension_count); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, + available_extensions.data()); + + // Check for portability subset extension (required on MoltenVK) + const char* portability_extension = "VK_KHR_portability_subset"; + for (const auto& extension : available_extensions) { + if (strcmp(extension.extensionName, portability_extension) == 0) { + extensions.push_back(portability_extension); + LOGI( + "Added VK_KHR_portability_subset extension for MoltenVK " + "compatibility"); + break; + } + } + + return extensions; +} + +bool VkInterface::IsValidationLayersAvailable() const { + return validation_layers_enabled_; +} + +// Global instance +static std::unique_ptr g_vk_interface; + +VkInterface* GetVkInterface() { + if (!g_vk_interface) { + g_vk_interface = std::make_unique(); + if (!g_vk_interface->Init()) { + g_vk_interface.reset(); + return nullptr; + } + } + return g_vk_interface.get(); +} + +} // namespace skity \ No newline at end of file diff --git a/src/gpu/vk/vk_interface.hpp b/src/gpu/vk/vk_interface.hpp new file mode 100644 index 00000000..b7b79baf --- /dev/null +++ b/src/gpu/vk/vk_interface.hpp @@ -0,0 +1,86 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GPU_VK_VK_INTERFACE_HPP +#define SRC_GPU_VK_VK_INTERFACE_HPP + +#include + +#include +#include + +namespace skity { + +/** + * Vulkan interface wrapper that manages instance and function loading + */ +class VkInterface { + public: + VkInterface(); + ~VkInterface(); + + /** + * Initialize Vulkan loader and create instance + */ + bool Init(); + + /** + * Get the Vulkan instance + */ + VkInstance GetInstance() const { return instance_; } + + /** + * Get available physical devices + */ + const std::vector& GetPhysicalDevices() const { + return physical_devices_; + } + + /** + * Select the best physical device for rendering + */ + VkPhysicalDevice SelectBestPhysicalDevice() const; + + /** + * Check if validation layers are available + */ + bool IsValidationLayersAvailable() const; + + /** + * Get required instance extensions + */ + std::vector GetRequiredInstanceExtensions() const; + + /** + * Get required device extensions + */ + std::vector GetRequiredDeviceExtensions() const; + + /** + * Get required device extensions for a specific physical device + */ + std::vector GetRequiredDeviceExtensions( + VkPhysicalDevice device) const; + + private: + bool CreateInstance(); + void EnumeratePhysicalDevices(); + bool CheckValidationLayerSupport() const; + + private: + VkInstance instance_ = VK_NULL_HANDLE; + std::vector physical_devices_; + bool validation_layers_enabled_ = false; + + static const std::vector kValidationLayers; +}; + +/** + * Get global Vulkan interface instance + */ +VkInterface* GetVkInterface(); + +} // namespace skity + +#endif // SRC_GPU_VK_VK_INTERFACE_HPP \ No newline at end of file diff --git a/src/gpu/vk/vma_impl.cc b/src/gpu/vk/vma_impl.cc new file mode 100644 index 00000000..8f3d11a2 --- /dev/null +++ b/src/gpu/vk/vma_impl.cc @@ -0,0 +1,7 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +// VMA implementation file +#define VMA_IMPLEMENTATION +#include \ No newline at end of file diff --git a/src/render/hw/hw_layer.cc b/src/render/hw/hw_layer.cc index 4626e1ab..2f4807c1 100644 --- a/src/render/hw/hw_layer.cc +++ b/src/render/hw/hw_layer.cc @@ -9,6 +9,7 @@ #include "src/geometry/glm_helper.hpp" #include "src/gpu/gpu_context_impl.hpp" +#include "src/logging.hpp" #include "src/tracing.hpp" namespace skity { @@ -30,8 +31,16 @@ HWLayer::HWLayer(Matrix matrix, int32_t depth, Rect bounds, uint32_t width, void HWLayer::Draw(GPURenderPass* render_pass) { SKITY_TRACE_EVENT(HWLayer_Draw); auto cmd = CreateCommandBuffer(); + if (!cmd) { + LOGE("Failed to create command buffer"); + return; + } auto self_pass = OnBeginRenderPass(cmd.get()); + if (!self_pass) { + LOGE("OnBeginRenderPass() returned null"); + return; + } self_pass->SetArenaAllocator(arena_allocator_); for (auto draw : draw_ops_) { @@ -144,8 +153,16 @@ HWDrawState HWLayer::OnPrepare(HWDrawContext* context) { sub_context.stageBuffer = context->stageBuffer; sub_context.pipelineLib = context->pipelineLib; sub_context.gpuContext = context->gpuContext; - sub_context.mvp = FromGLM(glm::ortho(bounds_.Left(), bounds_.Right(), - bounds_.Bottom(), bounds_.Top())); + // For Vulkan backend, adjust coordinate system to account for Y-axis + // differences + if (context->gpuContext && + context->gpuContext->GetBackendType() == GPUBackendType::kVulkan) { + sub_context.mvp = FromGLM(glm::ortho(bounds_.Left(), bounds_.Right(), + bounds_.Top(), bounds_.Bottom())); + } else { + sub_context.mvp = FromGLM(glm::ortho(bounds_.Left(), bounds_.Right(), + bounds_.Bottom(), bounds_.Top())); + } sub_context.pool = &pool; sub_context.vertex_vector_cache = context->vertex_vector_cache; sub_context.index_vector_cache = context->index_vector_cache; @@ -170,8 +187,18 @@ void HWLayer::OnGenerateCommand(HWDrawContext* context, HWDrawState state) { sub_context.stageBuffer = context->stageBuffer; sub_context.pipelineLib = context->pipelineLib; sub_context.gpuContext = context->gpuContext; - sub_context.mvp = FromGLM(glm::ortho(bounds_.Left(), bounds_.Right(), - bounds_.Bottom(), bounds_.Top())); + + // Vulkan has Y-axis pointing down, while OpenGL has Y-axis pointing up + // Use flipped parameters for Vulkan + if (context->gpuContext && + context->gpuContext->GetBackendType() == GPUBackendType::kVulkan) { + sub_context.mvp = FromGLM(glm::ortho(bounds_.Left(), bounds_.Right(), + bounds_.Top(), bounds_.Bottom())); + } else { + sub_context.mvp = FromGLM(glm::ortho(bounds_.Left(), bounds_.Right(), + bounds_.Bottom(), bounds_.Top())); + } + sub_context.pool = &pool; sub_context.vertex_vector_cache = context->vertex_vector_cache; sub_context.index_vector_cache = context->index_vector_cache; diff --git a/src/render/hw/vk/vk_root_layer.cc b/src/render/hw/vk/vk_root_layer.cc new file mode 100644 index 00000000..f910d724 --- /dev/null +++ b/src/render/hw/vk/vk_root_layer.cc @@ -0,0 +1,118 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "src/render/hw/vk/vk_root_layer.hpp" + +#include "src/gpu/gpu_context_impl.hpp" +#include "src/gpu/vk/gpu_texture_vk.hpp" +#include "src/logging.hpp" + +namespace skity { + +VkRootLayer::VkRootLayer(uint32_t width, uint32_t height, const Rect &bounds) + : HWRootLayer(width, height, bounds, GPUTextureFormat::kRGBA8Unorm) {} + +void VkRootLayer::Draw(GPURenderPass *render_pass) { + LOGI("VkRootLayer::Draw() called - starting Vulkan rendering"); + // Call the base class implementation which contains the actual drawing logic + HWRootLayer::Draw(render_pass); + LOGI("VkRootLayer::Draw() completed"); +} + +void VkRootLayer::OnPostDraw(GPURenderPass *render_pass, + GPUCommandBuffer *cmd) { + // Post-draw cleanup - for Vulkan this could include command buffer submission + // For now, we rely on the command buffer's automatic submission +} + +VkExternTextureLayer::VkExternTextureLayer(std::shared_ptr texture, + const Rect &bounds) + : VkRootLayer(texture->GetDescriptor().width, + texture->GetDescriptor().height, bounds), + ext_texture_(std::move(texture)) {} + +HWDrawState VkExternTextureLayer::OnPrepare(HWDrawContext *context) { + auto ret = VkRootLayer::OnPrepare(context); + + // Set up render pass descriptor for the external texture + render_pass_desc_.color_attachment.texture = ext_texture_; + render_pass_desc_.color_attachment.load_op = GPULoadOp::kDontCare; + render_pass_desc_.color_attachment.store_op = GPUStoreOp::kStore; + render_pass_desc_.color_attachment.clear_value = { + 1.0f, 1.0f, 1.0f, 1.0f}; // White background to match GL behavior + + // Create proper depth/stencil textures like GL backend + CreateDepthStencilTextures(context->gpuContext->GetGPUDevice()); + + // Set up stencil and depth attachments like GL backend for path rendering + if (depth_stencil_texture_) { + render_pass_desc_.stencil_attachment.texture = depth_stencil_texture_; + render_pass_desc_.stencil_attachment.load_op = GPULoadOp::kClear; + render_pass_desc_.stencil_attachment.store_op = GPUStoreOp::kDiscard; + render_pass_desc_.stencil_attachment.clear_value = 0; + render_pass_desc_.depth_attachment.texture = depth_stencil_texture_; + render_pass_desc_.depth_attachment.load_op = GPULoadOp::kClear; + render_pass_desc_.depth_attachment.store_op = GPUStoreOp::kDiscard; + render_pass_desc_.depth_attachment.clear_value = 0.0f; + } + + return ret; +} + +std::shared_ptr VkExternTextureLayer::OnBeginRenderPass( + GPUCommandBuffer *cmd) { + LOGI("VkExternTextureLayer::OnBeginRenderPass called"); + if (!cmd || !ext_texture_) { + LOGE( + "Invalid command buffer or texture for Vulkan render pass - cmd: %p, " + "texture: %p", + cmd, ext_texture_.get()); + return nullptr; + } + + LOGI("Starting Vulkan render pass with texture %dx%d", + ext_texture_->GetDescriptor().width, + ext_texture_->GetDescriptor().height); + + auto render_pass = cmd->BeginRenderPass(render_pass_desc_); + if (!render_pass) { + LOGE("Failed to begin Vulkan render pass"); + return nullptr; + } + + LOGI("Vulkan render pass created successfully"); + return render_pass; +} + +void VkExternTextureLayer::CreateDepthStencilTextures(GPUDevice *device) { + if (!ext_texture_ || !device) { + return; + } + + // Create depth/stencil texture with same dimensions as color texture + const auto &color_desc = ext_texture_->GetDescriptor(); + + GPUTextureDescriptor depth_stencil_desc = {}; + depth_stencil_desc.width = color_desc.width; + depth_stencil_desc.height = color_desc.height; + depth_stencil_desc.format = + GPUTextureFormat::kDepth24Stencil8; // Combined depth-stencil format + depth_stencil_desc.usage = + static_cast(GPUTextureUsage::kRenderAttachment); + depth_stencil_desc.storage_mode = + GPUTextureStorageMode::kPrivate; // GPU-only + depth_stencil_desc.mip_level_count = 1; + depth_stencil_desc.sample_count = 1; + + // Create the depth/stencil texture using the GPU device + depth_stencil_texture_ = device->CreateTexture(depth_stencil_desc); + + if (depth_stencil_texture_) { + LOGI("Depth/stencil texture created successfully"); + } else { + LOGE("Failed to create depth/stencil texture"); + } +} + +} // namespace skity \ No newline at end of file diff --git a/src/render/hw/vk/vk_root_layer.hpp b/src/render/hw/vk/vk_root_layer.hpp new file mode 100644 index 00000000..b7378572 --- /dev/null +++ b/src/render/hw/vk/vk_root_layer.hpp @@ -0,0 +1,47 @@ +// Copyright 2021 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_RENDER_HW_VK_VK_ROOT_LAYER_HPP +#define SRC_RENDER_HW_VK_VK_ROOT_LAYER_HPP + +#include + +#include "src/gpu/gpu_render_pass.hpp" +#include "src/render/hw/layer/hw_root_layer.hpp" + +namespace skity { + +class GPUTextureVk; + +class VkRootLayer : public HWRootLayer { + public: + VkRootLayer(uint32_t width, uint32_t height, const Rect &bounds); + ~VkRootLayer() override = default; + + protected: + void Draw(GPURenderPass *render_pass) override; + void OnPostDraw(GPURenderPass *render_pass, GPUCommandBuffer *cmd) override; +}; + +class VkExternTextureLayer : public VkRootLayer { + public: + VkExternTextureLayer(std::shared_ptr texture, const Rect &bounds); + ~VkExternTextureLayer() override = default; + + protected: + HWDrawState OnPrepare(HWDrawContext *context) override; + std::shared_ptr OnBeginRenderPass( + GPUCommandBuffer *cmd) override; + + private: + std::shared_ptr ext_texture_; + std::shared_ptr depth_stencil_texture_; + GPURenderPassDescriptor render_pass_desc_ = {}; + + void CreateDepthStencilTextures(GPUDevice *device); +}; + +} // namespace skity + +#endif // SRC_RENDER_HW_VK_VK_ROOT_LAYER_HPP \ No newline at end of file