diff --git a/CMakeLists.txt b/CMakeLists.txt index 86557564..64f2baa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,6 +149,7 @@ set(SOURCE_FILES MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp MobileGL/MG_Impl/EGLImpl/EGLWrapper/EGLWrapper.cpp + MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp # @INSERTION_POINT:SOURCE_FILE_GLIMPL@ # MobileGL/MG_Impl/GLImpl/Sampler/Validators.cpp @@ -178,6 +179,13 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectGLES/Utils.cpp MobileGL/MG_Backend/DirectGLES/Managers.cpp + MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp + MobileGL/MG_State/GLState/Core.cpp MobileGL/MG_State/GLState/ErrorState/Error.cpp MobileGL/MG_State/GLState/BufferState/BufferState.cpp @@ -295,10 +303,12 @@ if (ANDROID) ) endif() -if (MOBILEGL_BUILD_TEST) - add_subdirectory(MobileGL/MG_Test) -endif() +if (NOT ANDROID) + if (MOBILEGL_BUILD_TEST) + add_subdirectory(MobileGL/MG_Test) + endif() -if (MOBILEGL_BUILD_BENCHMARK) - add_subdirectory(MobileGL/MG_Benchmark) -endif() + if (MOBILEGL_BUILD_BENCHMARK) + add_subdirectory(MobileGL/MG_Benchmark) + endif() +endif() \ No newline at end of file diff --git a/MobileGL/Includes.h b/MobileGL/Includes.h index d717ebe9..50a3d3fb 100644 --- a/MobileGL/Includes.h +++ b/MobileGL/Includes.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp new file mode 100644 index 00000000..a65552f5 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -0,0 +1,11 @@ +#include "DirectVulkan.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + UniquePtr pVulkanRenderer = nullptr; + + void Clear(GLbitfield mask) {} + + void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { + pVulkanRenderer->RenderFrame(); + } +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h new file mode 100644 index 00000000..a2be5ddb --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include "Renderer/VulkanRenderer.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + extern UniquePtr pVulkanRenderer; + + void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); + void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value); + void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value); + void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value); + void Clear(GLbitfield mask); + void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); + void DrawArrays(GLenum mode, GLint first, GLsizei count); + void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex); + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount); + void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount, const GLint* basevertex); + void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); + void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride); + void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, + const void* indices, GLint basevertex); + void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices); + void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLint basevertex, GLuint baseinstance); + void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLint basevertex); + void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLuint baseinstance); + void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount); + void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect); + void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, + GLuint baseinstance); + void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); + void DrawArraysIndirect(GLenum mode, const void* indirect); + void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, + GLint dstY1, GLbitfield mask, GLenum filter); + void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, + GLsizei height, GLint border); + void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, + GLsizei height); + void GenerateMipmap(GLenum target); + const GLubyte* GetString(GLenum name); +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp new file mode 100644 index 00000000..de2c8465 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -0,0 +1,44 @@ +#include "FrameContext.h" +#include "VulkanContext.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + FrameContext::~FrameContext() = default; + + void FrameContext::Initialize(VulkanContext& ctx, VkCommandPool pool) { + // allocate cmd + VkCommandBufferAllocateInfo abci{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + abci.commandPool = pool; + abci.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + abci.commandBufferCount = 1; + ThrowIfFailed(vkAllocateCommandBuffers(ctx.GetDevice(), &abci, &CommandBuffer), "vkAllocateCommandBuffers"); + + // semaphores + VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + ThrowIfFailed(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &ImageAvailable), + "vkCreateSemaphore ImageAvailable"); + ThrowIfFailed(vkCreateSemaphore(ctx.GetDevice(), &sci, nullptr, &RenderFinished), + "vkCreateSemaphore RenderFinished"); + + // fence (start signaled) + VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + fci.flags = VK_FENCE_CREATE_SIGNALED_BIT; + ThrowIfFailed(vkCreateFence(ctx.GetDevice(), &fci, nullptr, &InFlightFence), "vkCreateFence"); + } + + void FrameContext::Cleanup(VulkanContext& ctx) { + if (ImageAvailable != VK_NULL_HANDLE) { + vkDestroySemaphore(ctx.GetDevice(), ImageAvailable, nullptr); + ImageAvailable = VK_NULL_HANDLE; + } + if (RenderFinished != VK_NULL_HANDLE) { + vkDestroySemaphore(ctx.GetDevice(), RenderFinished, nullptr); + RenderFinished = VK_NULL_HANDLE; + } + if (InFlightFence != VK_NULL_HANDLE) { + vkDestroyFence(ctx.GetDevice(), InFlightFence, nullptr); + InFlightFence = VK_NULL_HANDLE; + } + + CommandBuffer = VK_NULL_HANDLE; + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h new file mode 100644 index 00000000..f87f6e54 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + class VulkanContext; + + struct FrameContext { + FrameContext() = default; + ~FrameContext(); + + void Initialize(VulkanContext& ctx, VkCommandPool pool); + void Cleanup(VulkanContext& ctx); + + VkCommandBuffer CommandBuffer = VK_NULL_HANDLE; + VkSemaphore ImageAvailable = VK_NULL_HANDLE; + VkSemaphore RenderFinished = VK_NULL_HANDLE; + VkFence InFlightFence = VK_NULL_HANDLE; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp new file mode 100644 index 00000000..89e42358 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.cpp @@ -0,0 +1,134 @@ +#include "PipelineManager.h" +#include "VulkanContext.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + PipelineManager::PipelineManager(VulkanContext& ctx) : Ctx(ctx) {} + PipelineManager::~PipelineManager() { + Cleanup(); + } + + void PipelineManager::EnsurePipelineLayout() { + if (PipelineLayout != VK_NULL_HANDLE) return; + VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + ThrowIfFailed(vkCreatePipelineLayout(Ctx.GetDevice(), &plci, nullptr, &PipelineLayout), + "vkCreatePipelineLayout"); + } + + VkPipeline PipelineManager::CreateGraphicsPipelineFromSpv(const std::string& key, + const std::vector& vsSpv, + const std::vector& fsSpv, + VkRenderPass renderPass, VkExtent2D extent) { + if (Pipelines.find(key) != Pipelines.end()) { + MGLOG_W("Pipeline key '%s' already exists - returning existing", key.c_str()); + return Pipelines[key]; + } + EnsurePipelineLayout(); + + VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + smci.codeSize = vsSpv.size() * sizeof(uint32_t); + smci.pCode = vsSpv.data(); + VkShaderModule vs; + ThrowIfFailed(vkCreateShaderModule(Ctx.GetDevice(), &smci, nullptr, &vs), "vkCreateShaderModule VS"); + + smci.codeSize = fsSpv.size() * sizeof(uint32_t); + smci.pCode = fsSpv.data(); + VkShaderModule fs; + ThrowIfFailed(vkCreateShaderModule(Ctx.GetDevice(), &smci, nullptr, &fs), "vkCreateShaderModule FS"); + + VkPipelineShaderStageCreateInfo stages[2]{}; + stages[0] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[0].module = vs; + stages[0].pName = "main"; + stages[1] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[1].module = fs; + stages[1].pName = "main"; + + VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + vertexInput.vertexBindingDescriptionCount = 0; + vertexInput.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkViewport vp{}; + vp.x = 0; + vp.y = 0; + vp.width = (float)extent.width; + vp.height = (float)extent.height; + vp.minDepth = 0; + vp.maxDepth = 1; + VkRect2D scissor{{0, 0}, extent}; + VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + vpci.viewportCount = 1; + vpci.pViewports = &vp; + vpci.scissorCount = 1; + vpci.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + raster.polygonMode = VK_POLYGON_MODE_FILL; + raster.cullMode = VK_CULL_MODE_NONE; + raster.frontFace = VK_FRONT_FACE_CLOCKWISE; + raster.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorAttach{}; + colorAttach.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorAttach.blendEnable = VK_FALSE; + VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + blend.attachmentCount = 1; + blend.pAttachments = &colorAttach; + + VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + gpi.stageCount = 2; + gpi.pStages = stages; + gpi.pVertexInputState = &vertexInput; + gpi.pInputAssemblyState = &ia; + gpi.pViewportState = &vpci; + gpi.pRasterizationState = &raster; + gpi.pMultisampleState = &ms; + gpi.pColorBlendState = &blend; + gpi.layout = PipelineLayout; + gpi.renderPass = renderPass; + gpi.subpass = 0; + + VkPipeline pipeline; + ThrowIfFailed(vkCreateGraphicsPipelines(Ctx.GetDevice(), VK_NULL_HANDLE, 1, &gpi, nullptr, &pipeline), + "vkCreateGraphicsPipelines"); + + vkDestroyShaderModule(Ctx.GetDevice(), vs, nullptr); + vkDestroyShaderModule(Ctx.GetDevice(), fs, nullptr); + + Pipelines.emplace(key, pipeline); + MGLOG_D("Pipeline '%s' created", key.c_str()); + return pipeline; + } + + VkPipeline PipelineManager::GetPipeline(const std::string& key) const { + auto it = Pipelines.find(key); + if (it == Pipelines.end()) return VK_NULL_HANDLE; + return it->second; + } + + void PipelineManager::DestroyPipeline(const std::string& key) { + auto it = Pipelines.find(key); + if (it != Pipelines.end()) { + vkDestroyPipeline(Ctx.GetDevice(), it->second, nullptr); + Pipelines.erase(it); + } + } + + void PipelineManager::Cleanup() { + for (auto& kv : Pipelines) + vkDestroyPipeline(Ctx.GetDevice(), kv.second, nullptr); + Pipelines.clear(); + if (PipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(Ctx.GetDevice(), PipelineLayout, nullptr); + PipelineLayout = VK_NULL_HANDLE; + } + } +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h new file mode 100644 index 00000000..b3c57f0b --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineManager.h @@ -0,0 +1,27 @@ +#pragma once +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + class VulkanContext; + + class PipelineManager { + public: + PipelineManager(VulkanContext& ctx); + ~PipelineManager(); + + VkPipeline CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector& vsSpv, + const std::vector& fsSpv, VkRenderPass renderPass, + VkExtent2D extent); + + VkPipeline GetPipeline(const std::string& key) const; + void DestroyPipeline(const std::string& key); + void Cleanup(); + + private: + VulkanContext& Ctx; + VkPipelineLayout PipelineLayout = VK_NULL_HANDLE; // TODO: per-pipeline layouts? + std::unordered_map Pipelines; + + void EnsurePipelineLayout(); + }; +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp new file mode 100644 index 00000000..9fe3bf84 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.cpp @@ -0,0 +1,99 @@ +#include "SwapchainManager.h" +#include "VulkanContext.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + SwapchainManager::SwapchainManager(VulkanContext& ctx) : Ctx(ctx) {} + SwapchainManager::~SwapchainManager() { + Cleanup(); + } + + void SwapchainManager::Initialize() { + CreateSwapchainInternal(); + CreateImageViews(); + } + + void SwapchainManager::Recreate() { + DestroyImageViews(); + if (Swapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(Ctx.GetDevice(), Swapchain, nullptr); + Swapchain = VK_NULL_HANDLE; + } + CreateSwapchainInternal(); + CreateImageViews(); + Framebuffers.clear(); + MGLOG_D("Swapchain recreated"); + } + + void SwapchainManager::Cleanup() { + for (auto fb : Framebuffers) + if (fb != VK_NULL_HANDLE) vkDestroyFramebuffer(Ctx.GetDevice(), fb, nullptr); + Framebuffers.clear(); + DestroyImageViews(); + if (Swapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(Ctx.GetDevice(), Swapchain, nullptr); + Swapchain = VK_NULL_HANDLE; + } + } + + void SwapchainManager::SetFramebuffers(std::vector&& fbs) { + Framebuffers = std::move(fbs); + } + + void SwapchainManager::CreateSwapchainInternal() { + VkSurfaceCapabilitiesKHR caps; + ThrowIfFailed(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(Ctx.GetPhysicalDevice(), Ctx.GetSurface(), &caps), + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + + Extent = caps.currentExtent; + ImageFormat = VK_FORMAT_R8G8B8A8_UNORM; + + VkSwapchainCreateInfoKHR sci{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; + sci.surface = Ctx.GetSurface(); + sci.minImageCount = std::max(2, caps.minImageCount); + sci.imageFormat = ImageFormat; + sci.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + sci.imageExtent = Extent; + sci.imageArrayLayers = 1; + sci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + sci.preTransform = caps.currentTransform; + sci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + sci.presentMode = VK_PRESENT_MODE_FIFO_KHR; + + ThrowIfFailed(vkCreateSwapchainKHR(Ctx.GetDevice(), &sci, nullptr, &Swapchain), "vkCreateSwapchainKHR"); + + uint32_t count = 0; + ThrowIfFailed(vkGetSwapchainImagesKHR(Ctx.GetDevice(), Swapchain, &count, nullptr), + "vkGetSwapchainImagesKHR count"); + Images.resize(count); + ThrowIfFailed(vkGetSwapchainImagesKHR(Ctx.GetDevice(), Swapchain, &count, Images.data()), + "vkGetSwapchainImagesKHR images"); + MGLOG_D("Swapchain created (%u images)", count); + } + + void SwapchainManager::CreateImageViews() { + DestroyImageViews(); + ImageViews.resize(Images.size()); + for (size_t i = 0; i < Images.size(); ++i) { + VkImageViewCreateInfo ivci{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + ivci.image = Images[i]; + ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; + ivci.format = ImageFormat; + ivci.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A}; + ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + ivci.subresourceRange.baseMipLevel = 0; + ivci.subresourceRange.levelCount = 1; + ivci.subresourceRange.baseArrayLayer = 0; + ivci.subresourceRange.layerCount = 1; + ThrowIfFailed(vkCreateImageView(Ctx.GetDevice(), &ivci, nullptr, &ImageViews[i]), "vkCreateImageView"); + } + MGLOG_D("ImageViews created"); + } + + void SwapchainManager::DestroyImageViews() { + for (auto iv : ImageViews) + if (iv != VK_NULL_HANDLE) vkDestroyImageView(Ctx.GetDevice(), iv, nullptr); + ImageViews.clear(); + } +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h new file mode 100644 index 00000000..ff567cd1 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainManager.h @@ -0,0 +1,37 @@ +#pragma once +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + class VulkanContext; + + class SwapchainManager { + public: + SwapchainManager(VulkanContext& ctx); + ~SwapchainManager(); + + void Initialize(); + void Recreate(); + void Cleanup(); + + VkSwapchainKHR GetSwapchain() const { return Swapchain; } + VkFormat GetFormat() const { return ImageFormat; } + VkExtent2D GetExtent() const { return Extent; } + const std::vector& GetImageViews() const { return ImageViews; } + const std::vector& GetFramebuffers() const { return Framebuffers; } + + void SetFramebuffers(std::vector&& fbs); + + private: + VulkanContext& Ctx; + VkSwapchainKHR Swapchain = VK_NULL_HANDLE; + VkFormat ImageFormat = VK_FORMAT_UNDEFINED; + VkExtent2D Extent{0, 0}; + std::vector Images; + std::vector ImageViews; + std::vector Framebuffers; + + void CreateSwapchainInternal(); + void CreateImageViews(); + void DestroyImageViews(); + }; +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp new file mode 100644 index 00000000..977fb990 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.cpp @@ -0,0 +1,105 @@ +#include "VulkanContext.h" +#include "MG_Util/Types.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + VulkanContext::~VulkanContext() { + Shutdown(); + } + + void VulkanContext::Initialize(ANativeWindow* window, const std::string& appName) { + if (Initialized) return; + CreateInstance(appName); + CreateSurface(window); + PickPhysicalDevice(); + CreateLogicalDevice(); + Initialized = true; + MGLOG_D("VulkanContext initialized"); + } + + void VulkanContext::Shutdown() { + if (!Initialized && Instance == VK_NULL_HANDLE) return; + if (Device != VK_NULL_HANDLE) { + vkDeviceWaitIdle(Device); + vkDestroyDevice(Device, nullptr); + Device = VK_NULL_HANDLE; + } + if (Surface != VK_NULL_HANDLE) { + vkDestroySurfaceKHR(Instance, Surface, nullptr); + Surface = VK_NULL_HANDLE; + } + if (Instance != VK_NULL_HANDLE) { + vkDestroyInstance(Instance, nullptr); + Instance = VK_NULL_HANDLE; + } + Initialized = false; + MGLOG_D("VulkanContext shutdown"); + } + + void VulkanContext::CreateInstance(const std::string& appName) { + VkApplicationInfo appInfo{VK_STRUCTURE_TYPE_APPLICATION_INFO}; + appInfo.pApplicationName = appName.c_str(); + appInfo.apiVersion = VK_API_VERSION_1_0; + + const char* exts[] = {VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME}; + + VkInstanceCreateInfo ci{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; + ci.pApplicationInfo = &appInfo; + ci.enabledExtensionCount = 2; + ci.ppEnabledExtensionNames = exts; + + ThrowIfFailed(vkCreateInstance(&ci, nullptr, &Instance), "vkCreateInstance failed"); + } + + void VulkanContext::CreateSurface(ANativeWindow* window) { + if (!Instance) throw MobileGL::RuntimeError("Instance not created"); + VkAndroidSurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR}; + sci.window = window; + ThrowIfFailed(vkCreateAndroidSurfaceKHR(Instance, &sci, nullptr, &Surface), "vkCreateAndroidSurfaceKHR failed"); + } + + void VulkanContext::PickPhysicalDevice() { + uint32_t count = 0; + ThrowIfFailed(vkEnumeratePhysicalDevices(Instance, &count, nullptr), "vkEnumeratePhysicalDevices count"); + if (count == 0) throw MobileGL::RuntimeError("No physical devices"); + std::vector devs(count); + ThrowIfFailed(vkEnumeratePhysicalDevices(Instance, &count, devs.data()), "vkEnumeratePhysicalDevices"); + + for (auto d : devs) { + uint32_t qcount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(d, &qcount, nullptr); + std::vector qprops(qcount); + vkGetPhysicalDeviceQueueFamilyProperties(d, &qcount, qprops.data()); + for (uint32_t i = 0; i < qcount; ++i) { + VkBool32 present = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(d, i, Surface, &present); + if ((qprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) { + PhysicalDevice = d; + GraphicsQueueFamily = i; + MGLOG_D("Picked physical device, queue family %u", i); + return; + } + } + } + throw MobileGL::RuntimeError("No suitable physical device"); + } + + void VulkanContext::CreateLogicalDevice() { + float prio = 1.0f; + VkDeviceQueueCreateInfo dqci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + dqci.queueFamilyIndex = GraphicsQueueFamily; + dqci.queueCount = 1; + dqci.pQueuePriorities = &prio; + + const char* devExts[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + VkDeviceCreateInfo dci{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; + dci.queueCreateInfoCount = 1; + dci.pQueueCreateInfos = &dqci; + dci.enabledExtensionCount = 1; + dci.ppEnabledExtensionNames = devExts; + + ThrowIfFailed(vkCreateDevice(PhysicalDevice, &dci, nullptr, &Device), "vkCreateDevice failed"); + vkGetDeviceQueue(Device, GraphicsQueueFamily, 0, &GraphicsQueue); + MGLOG_D("Logical device created"); + } +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h new file mode 100644 index 00000000..4615f268 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanContext.h @@ -0,0 +1,41 @@ +#pragma once +#include + +static inline void ThrowIfFailed(VkResult r, const char* msg) { + if (r != VK_SUCCESS) { + MGLOG_E("%s (VkResult=%d)", msg, int(r)); + throw MobileGL::RuntimeError(msg); + } +} + +namespace MobileGL::MG_Backend::DirectVulkan { + class VulkanContext { + public: + VulkanContext() = default; + ~VulkanContext(); + + void Initialize(ANativeWindow* window, const std::string& appName = "VulkanEngineApp"); + void Shutdown(); + + VkInstance GetInstance() const { return Instance; } + VkPhysicalDevice GetPhysicalDevice() const { return PhysicalDevice; } + VkDevice GetDevice() const { return Device; } + VkQueue GetGraphicsQueue() const { return GraphicsQueue; } + uint32_t GetGraphicsQueueFamily() const { return GraphicsQueueFamily; } + VkSurfaceKHR GetSurface() const { return Surface; } + + private: + void CreateInstance(const std::string& appName); + void CreateSurface(ANativeWindow* window); + void PickPhysicalDevice(); + void CreateLogicalDevice(); + + VkInstance Instance = VK_NULL_HANDLE; + VkPhysicalDevice PhysicalDevice = VK_NULL_HANDLE; + VkDevice Device = VK_NULL_HANDLE; + VkQueue GraphicsQueue = VK_NULL_HANDLE; + uint32_t GraphicsQueueFamily = UINT32_MAX; + VkSurfaceKHR Surface = VK_NULL_HANDLE; + bool Initialized = false; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp new file mode 100644 index 00000000..94cf2b40 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -0,0 +1,266 @@ +#include "VulkanRenderer.h" +#include "VulkanContext.h" +#include "SwapchainManager.h" +#include "PipelineManager.h" +#include "FrameContext.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + VulkanRenderer::VulkanRenderer(ANativeWindow* window, const RendererConfig& cfg) : Window(window), Config(cfg) { + Ctx = std::make_unique(); + } + + VulkanRenderer::~VulkanRenderer() { + Shutdown(); + } + + void VulkanRenderer::Initialize() { + if (!Window) throw MobileGL::RuntimeError("ANativeWindow is null"); + Ctx->Initialize(Window, Config.AppName); + + Swapchain = std::make_unique(*Ctx); + Swapchain->Initialize(); + + CreateRenderPass(); + + PipelineMgr = std::make_unique(*Ctx); + + CreateCommandPool(); + CreateFrameResources(); + + MGLOG_D("VulkanRenderer initialized"); + } + + void VulkanRenderer::Shutdown() { + if (!Ctx) return; + + vkDeviceWaitIdle(Ctx->GetDevice()); + + DestroyFrameResources(); + DestroyCommandPool(); + + if (PipelineMgr) { + PipelineMgr->Cleanup(); + PipelineMgr.reset(); + } + DestroyRenderPass(); + if (Swapchain) { + Swapchain->Cleanup(); + Swapchain.reset(); + } + if (Ctx) { + Ctx->Shutdown(); + Ctx.reset(); + } + MGLOG_D("VulkanRenderer shutdown"); + } + + void VulkanRenderer::CreateRenderPass() { + VkAttachmentDescription color{}; + color.format = Swapchain->GetFormat(); + color.samples = VK_SAMPLE_COUNT_1_BIT; + color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sub{}; + sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sub.colorAttachmentCount = 1; + sub.pColorAttachments = &colorRef; + + VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rpci.attachmentCount = 1; + rpci.pAttachments = &color; + rpci.subpassCount = 1; + rpci.pSubpasses = ⊂ + + ThrowIfFailed(vkCreateRenderPass(Ctx->GetDevice(), &rpci, nullptr, &RenderPass), "vkCreateRenderPass"); + + // Create framebuffers now (use swapchain imageviews) + const auto& imageViews = Swapchain->GetImageViews(); + std::vector fbs; + fbs.reserve(imageViews.size()); + for (auto iv : imageViews) { + VkImageView attachments[] = {iv}; + VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = RenderPass; + fbci.attachmentCount = 1; + fbci.pAttachments = attachments; + fbci.width = Swapchain->GetExtent().width; + fbci.height = Swapchain->GetExtent().height; + fbci.layers = 1; + VkFramebuffer fb; + ThrowIfFailed(vkCreateFramebuffer(Ctx->GetDevice(), &fbci, nullptr, &fb), "vkCreateFramebuffer"); + fbs.push_back(fb); + } + Swapchain->SetFramebuffers(std::move(fbs)); + MGLOG_D("RenderPass created and framebuffers set"); + } + + void VulkanRenderer::DestroyRenderPass() { + if (RenderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(Ctx->GetDevice(), RenderPass, nullptr); + RenderPass = VK_NULL_HANDLE; + } + } + + void VulkanRenderer::CreateCommandPool() { + VkCommandPoolCreateInfo cpci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + cpci.queueFamilyIndex = Ctx->GetGraphicsQueueFamily(); + cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + ThrowIfFailed(vkCreateCommandPool(Ctx->GetDevice(), &cpci, nullptr, &CommandPool), "vkCreateCommandPool"); + } + + void VulkanRenderer::DestroyCommandPool() { + if (CommandPool != VK_NULL_HANDLE) { + vkDestroyCommandPool(Ctx->GetDevice(), CommandPool, nullptr); + CommandPool = VK_NULL_HANDLE; + } + } + + void VulkanRenderer::CreateFrameResources() { + uint32_t imageCount = static_cast(Swapchain->GetImageViews().size()); + if (imageCount == 0) throw MobileGL::RuntimeError("Swapchain has zero images"); + uint32_t frames = std::min(Config.MaxFramesInFlight, imageCount); + Frames.clear(); + for (uint32_t i = 0; i < frames; ++i) { + auto fr = std::make_unique(); + fr->Initialize(*Ctx, CommandPool); + Frames.push_back(std::move(fr)); + } + CurrentFrame = 0; + MGLOG_D("FrameResources created: %u", (uint32_t)Frames.size()); + } + + void VulkanRenderer::DestroyFrameResources() { + for (auto& f : Frames) { + if (f) f->Cleanup(*Ctx); + } + Frames.clear(); + } + + void VulkanRenderer::RecordFrameCommandBuffer(FrameContext& frame, uint32_t imageIndex) { + // Begin + VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + ThrowIfFailed(vkBeginCommandBuffer(frame.CommandBuffer, &bi), "vkBeginCommandBuffer"); + + VkClearValue clear{}; + clear.color = {{0.0f, 0.0f, 0.0f, 1.0f}}; + VkRenderPassBeginInfo rpbi{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + rpbi.renderPass = RenderPass; + rpbi.framebuffer = Swapchain->GetFramebuffers()[imageIndex]; + rpbi.renderArea.offset = {0, 0}; + rpbi.renderArea.extent = Swapchain->GetExtent(); + rpbi.clearValueCount = 1; + rpbi.pClearValues = &clear; + + vkCmdBeginRenderPass(frame.CommandBuffer, &rpbi, VK_SUBPASS_CONTENTS_INLINE); + + for (auto& kv : RenderCallbacks) { + if (kv.second) { + kv.second(frame.CommandBuffer, imageIndex, Swapchain->GetExtent()); + } + } + + vkCmdEndRenderPass(frame.CommandBuffer); + ThrowIfFailed(vkEndCommandBuffer(frame.CommandBuffer), "vkEndCommandBuffer"); + } + + void VulkanRenderer::RecreateSwapchainIfNeeded() { + vkDeviceWaitIdle(Ctx->GetDevice()); + DestroyFrameResources(); + DestroyRenderPass(); + Swapchain->Recreate(); + CreateRenderPass(); + CreateFrameResources(); + } + + // Wait fence & Acquire image & Record commands & Submit & Present + void VulkanRenderer::RenderFrame() { + if (!Ctx) throw MobileGL::RuntimeError("Renderer not initialized"); + FrameContext& frame = *Frames[CurrentFrame]; + + // Wait fence and reset + ThrowIfFailed(vkWaitForFences(Ctx->GetDevice(), 1, &frame.InFlightFence, VK_TRUE, UINT64_MAX), + "vkWaitForFences"); + ThrowIfFailed(vkResetFences(Ctx->GetDevice(), 1, &frame.InFlightFence), "vkResetFences"); + + // Acquire image + uint32_t imageIndex = 0; + VkResult res = vkAcquireNextImageKHR(Ctx->GetDevice(), Swapchain->GetSwapchain(), UINT64_MAX, + frame.ImageAvailable, VK_NULL_HANDLE, &imageIndex); + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + MGLOG_D("vkAcquireNextImageKHR: OUT_OF_DATE -> recreate"); + RecreateSwapchainIfNeeded(); + return; + } + ThrowIfFailed(res, "vkAcquireNextImageKHR"); + + // Record commands + ThrowIfFailed(vkResetCommandBuffer(frame.CommandBuffer, 0), "vkResetCommandBuffer"); + RecordFrameCommandBuffer(frame, imageIndex); + + // Submit + VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; + VkSemaphore waitSemaphores[] = {frame.ImageAvailable}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + si.waitSemaphoreCount = 1; + si.pWaitSemaphores = waitSemaphores; + si.pWaitDstStageMask = waitStages; + si.commandBufferCount = 1; + si.pCommandBuffers = &frame.CommandBuffer; + VkSemaphore signalSemaphores[] = {frame.RenderFinished}; + si.signalSemaphoreCount = 1; + si.pSignalSemaphores = signalSemaphores; + + ThrowIfFailed(vkQueueSubmit(Ctx->GetGraphicsQueue(), 1, &si, frame.InFlightFence), "vkQueueSubmit"); + + // Present + VkPresentInfoKHR pi{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; + pi.waitSemaphoreCount = 1; + pi.pWaitSemaphores = signalSemaphores; + VkSwapchainKHR scs[] = {Swapchain->GetSwapchain()}; + pi.swapchainCount = 1; + pi.pSwapchains = scs; + pi.pImageIndices = &imageIndex; + VkResult pres = vkQueuePresentKHR(Ctx->GetGraphicsQueue(), &pi); + if (pres == VK_ERROR_OUT_OF_DATE_KHR || pres == VK_SUBOPTIMAL_KHR) { + MGLOG_D("vkQueuePresentKHR: out_of_date/suboptimal -> recreate"); + RecreateSwapchainIfNeeded(); + } else { + ThrowIfFailed(pres, "vkQueuePresentKHR"); + } + + CurrentFrame = (CurrentFrame + 1) % Frames.size(); + } + + void VulkanRenderer::RegisterRenderCallback(const std::string& name, RenderCallback cb) { + auto it = std::find_if(RenderCallbacks.begin(), RenderCallbacks.end(), + [&](const auto& kv) { return kv.first == name; }); + if (it != RenderCallbacks.end()) { + MGLOG_W("Render callback '%s' already registered", name.c_str()); + return; + } + RenderCallbacks.emplace_back(name, std::move(cb)); + } + + void VulkanRenderer::UnregisterRenderCallback(const std::string& name) { + RenderCallbacks.erase(std::remove_if(RenderCallbacks.begin(), RenderCallbacks.end(), + [&](const auto& kv) { return kv.first == name; }), + RenderCallbacks.end()); + } + + VkPipeline VulkanRenderer::CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector& vsSpv, + const std::vector& fsSpv) { + return PipelineMgr->CreateGraphicsPipelineFromSpv(key, vsSpv, fsSpv, RenderPass, Swapchain->GetExtent()); + } + + VkExtent2D VulkanRenderer::GetExtent() const { + return Swapchain ? Swapchain->GetExtent() : VkExtent2D{0, 0}; + } + + void VulkanRenderer::WaitIdle() { + if (Ctx && Ctx->GetDevice() != VK_NULL_HANDLE) vkDeviceWaitIdle(Ctx->GetDevice()); + } +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h new file mode 100644 index 00000000..64d82fef --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -0,0 +1,64 @@ +#pragma once +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + class VulkanContext; + class SwapchainManager; + class PipelineManager; + struct FrameContext; + + using RenderCallback = std::function; + + struct RendererConfig { + Uint32 MaxFramesInFlight = 2; + String AppName = "MobileGL-VulkanRenderer"; + }; + + class VulkanRenderer { + public: + VulkanRenderer(ANativeWindow* window, const RendererConfig& cfg = {}); + ~VulkanRenderer(); + + void Initialize(); + void Shutdown(); + + void RenderFrame(); + + void RegisterRenderCallback(const std::string& name, RenderCallback cb); + void UnregisterRenderCallback(const std::string& name); + + VkPipeline CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector& vsSpv, + const std::vector& fsSpv); + + VkExtent2D GetExtent() const; + + void WaitIdle(); + + private: + ANativeWindow* Window = nullptr; + RendererConfig Config; + + std::unique_ptr Ctx; + std::unique_ptr Swapchain; + std::unique_ptr PipelineMgr; + + VkRenderPass RenderPass = VK_NULL_HANDLE; + VkCommandPool CommandPool = VK_NULL_HANDLE; + + std::vector> Frames; + uint32_t CurrentFrame = 0; + + // Render callbacks map + std::vector> RenderCallbacks; + + // Internals + void CreateRenderPass(); + void DestroyRenderPass(); + void CreateCommandPool(); + void DestroyCommandPool(); + void CreateFrameResources(); + void DestroyFrameResources(); + void RecordFrameCommandBuffer(FrameContext& frame, uint32_t imageIndex); + void RecreateSwapchainIfNeeded(); + }; +} // namespace MobileGL::MG_Backend::DirectVulkan \ No newline at end of file diff --git a/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp b/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp new file mode 100644 index 00000000..515af665 --- /dev/null +++ b/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp @@ -0,0 +1,320 @@ +// MobileGL - MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "EGLForVulkan.h" +#include "MG_Backend/DirectVulkan/DirectVulkan.h" +#include +#include + +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN +namespace MobileGL { + namespace MG_Impl::EGLImpl { + // TODO: complete EGL for Vulkan implementation + + const char* demoFS = R"(#version 460 + layout(location = 0) out vec4 outColor; + void main() { + outColor = vec4(0.0, 1.0, 0.0, 1.0); + } +)"; + const char* demoVS = R"(#version 460 + layout(location = 0) out vec3 fragColor; + vec2 positions[3] = vec2[](vec2(0.0, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5)); + vec3 colors[3] = vec3[](vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0)); + void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; + } +)"; + void PrepareDemoRes() { + MGLOG_D("EGLForVulkan::PrepareDemoRes called"); + + // Create shader&program object + auto programObject = MG_State::GLState::ProgramObject(0); + auto vsObject = MakeShared(ShaderStage::Vertex, 0); + vsObject->SetShaderSource(demoVS); + vsObject->Compile(); + if (!vsObject->GetCompileStatus()) { + MGLOG_E("Vertex shader compilation failed: %s", vsObject->GetInfoLog().c_str()); + return; + } + auto fsObject = MakeShared(ShaderStage::Fragment, 1); + fsObject->SetShaderSource(demoFS); + fsObject->Compile(); + if (!fsObject->GetCompileStatus()) { + MGLOG_E("Fragment shader compilation failed: %s", fsObject->GetInfoLog().c_str()); + return; + } + programObject.AttachShader(vsObject); + programObject.AttachShader(fsObject); + programObject.Link(); + if (!programObject.GetLinkStatus()) { + MGLOG_E("Program linking failed: %s", programObject.GetInfoLog().c_str()); + return; + } + + Vector vsSpv; + Vector fsSpv; + + auto& shaderSpirvs = programObject.GetGeneratedSpirv(); + auto& attachedShaders = programObject.GetAttachedShaders(); + for (int index = 0; index < attachedShaders.size(); ++index) { + auto& shader = attachedShaders[index]; + auto& spirvCode = shaderSpirvs[index]; + if (shader->GetShaderStage() == ShaderStage::Vertex) { + vsSpv = spirvCode; + } else if (shader->GetShaderStage() == ShaderStage::Fragment) { + fsSpv = spirvCode; + } + } + + // Create pipeline + VkPipeline trianglePipeline = MG_Backend::DirectVulkan::pVulkanRenderer->CreateGraphicsPipelineFromSpv( + "TrianglePipeline", vsSpv, fsSpv); + + // Register render callback + MG_Backend::DirectVulkan::pVulkanRenderer->RegisterRenderCallback( + "DrawTriangle", [trianglePipeline](VkCommandBuffer cmd, uint32_t imageIndex, VkExtent2D extent) { + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, trianglePipeline); + + vkCmdDraw(cmd, 3, 1, 0, 0); + }); + } + + void CreateWindowSurfaceForVulkan(ANativeWindow* window) { + MG_Backend::DirectVulkan::pVulkanRenderer = MakeUnique(window); + MG_Backend::DirectVulkan::pVulkanRenderer->Initialize(); + + PrepareDemoRes(); // for demo use + } + + EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window, + const EGLint* attrib_list) { + MGLOG_D("EGLForVulkan::CreateWindowSurface called with window=%p", window); + auto* nativeWindow = static_cast(window); + CreateWindowSurfaceForVulkan(nativeWindow); + return (EGLSurface)1; + } + + EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) { + return EGL_TRUE; + } + + EGLBoolean ChooseConfig(EGLDisplay dpy, const EGLint* attrib_list, EGLConfig* configs, EGLint config_size, + EGLint* num_config) { + *num_config = 1; + return EGL_TRUE; + } + + EGLContext CreateContext(EGLDisplay dpy, EGLConfig config, EGLContext shareCtx, const EGLint* attrib_list) { + return (EGLContext)1; + } + + EGLBoolean Initialize(EGLDisplay dpy, EGLint* major, EGLint* minor) { + if (major) *major = 1; + if (minor) *minor = 5; + return EGL_TRUE; + } + + EGLDisplay GetDisplay(NativeDisplayType display) { + return (EGLDisplay)1; + } + + EGLint GetError() { + return EGL_SUCCESS; + } + + EGLBoolean MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { + return EGL_TRUE; + } + + EGLBoolean DestroyContext(EGLDisplay dpy, EGLContext ctx) { + return EGL_TRUE; + } + + EGLBoolean DestroySurface(EGLDisplay dpy, EGLSurface surface) { + return EGL_TRUE; + } + + EGLBoolean Terminate(EGLDisplay dpy) { + return EGL_TRUE; + } + + EGLBoolean ReleaseThread(void) { + return EGL_TRUE; + } + + EGLContext GetCurrentContext(void) { + return (EGLContext)1; + } + + EGLBoolean GetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint* value) { + if (attribute == EGL_NATIVE_VISUAL_ID) { +#if defined(ANDROID) + *value = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + return EGL_TRUE; +#elif defined(__linux__) + *value = 0; + return EGL_TRUE; +#elif defined(_WIN32) + *value = 0; + return EGL_TRUE; +#else + *value = 0; + return EGL_FALSE; +#endif + } + return EGL_TRUE; + } + + EGLBoolean BindAPI(EGLenum api) { + return EGL_TRUE; + } + + EGLSurface GetCurrentSurface(EGLint readdraw) { + return (EGLSurface)1; + } + + EGLBoolean QuerySurface(EGLDisplay display, EGLSurface surface, EGLint attribute, EGLint* value) { + return EGL_TRUE; + } + + char const* QueryString(EGLDisplay display, EGLint name) { + return ""; + } + + EGLBoolean SwapInterval(EGLDisplay dpy, EGLint interval) { + return EGL_TRUE; + } + + EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) { + return (EGLSurface)1; + } + + EGLBoolean BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) { + return EGL_TRUE; + } + + EGLBoolean ReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) { + return EGL_TRUE; + } + + EGLBoolean CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target) { + return EGL_TRUE; + } + + EGLSurface CreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, + EGLConfig config, const EGLint* attrib_list) { + return (EGLSurface)1; + } + + EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, + const EGLint* attrib_list) { + return (EGLSurface)1; + } + + EGLBoolean GetConfigs(EGLDisplay dpy, EGLConfig* configs, EGLint config_size, EGLint* num_config) { + if (num_config) { + *num_config = 1; + } + if (configs && config_size > 0) { + configs[0] = (EGLConfig)1; + } + return EGL_TRUE; + } + + EGLDisplay GetCurrentDisplay(void) { + return (EGLDisplay)1; + } + + EGLenum QueryAPI(void) { + return EGL_OPENGL_API; + } + + EGLBoolean QueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint* value) { + if (value) { + *value = 0; + } + return EGL_TRUE; + } + + EGLBoolean SurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value) { + return EGL_TRUE; + } + + EGLBoolean WaitClient(void) { + return EGL_TRUE; + } + + EGLBoolean WaitGL(void) { + return EGL_TRUE; + } + + EGLBoolean WaitNative(EGLint engine) { + return EGL_TRUE; + } + + EGLSync CreateSync(void* dpy, unsigned int type, long const* attrib_list) { + return reinterpret_cast(0x1); + } + + EGLBoolean DestroySync(void* dpy, void* sync) { + return EGL_TRUE; + } + + EGLint ClientWaitSync(void* dpy, void* sync, int flags, unsigned long timeout) { + return EGL_CONDITION_SATISFIED; + } + + EGLBoolean GetSyncAttrib(void* dpy, void* sync, int attribute, long* value) { + if (value) { + *value = 0; + } + return EGL_TRUE; + } + + EGLImage CreateImage(void* dpy, void* ctx, unsigned int target, void* buffer, long const* attrib_list) { + return reinterpret_cast(0x1); + } + + EGLBoolean DestroyImage(void* dpy, void* image) { + return EGL_TRUE; + } + + EGLDisplay GetPlatformDisplay(unsigned int platform, void* native_display, long const* attrib_list) { + return reinterpret_cast(0x1); + } + + EGLSurface CreatePlatformWindowSurface(void* dpy, void* config, void* native_window, long const* attrib_list) { + return reinterpret_cast(0x1); + } + + EGLSurface CreatePlatformPixmapSurface(void* dpy, void* config, void* native_pixmap, long const* attrib_list) { + return reinterpret_cast(0x1); + } + + EGLBoolean WaitSync(void* dpy, void* sync, int flags) { + return EGL_TRUE; + } + + __eglMustCastToProperFunctionPointerType GetProcAddress(const char* name) { + MGLOG_D("eglGetProcAddress(%s)", name); +#if !defined(WIN32) && !defined(__APPLE__) + void* proc = dlsym(RTLD_DEFAULT, (const char*)name); +#else + void* proc = NULL; +#endif + if (!proc) { + MGLOG_W("Failed to get function: %s", (const char*)name); + return nullptr; + } + return (__eglMustCastToProperFunctionPointerType)proc; + } + } // namespace MG_Impl::EGLImpl +} // namespace MobileGL +#endif diff --git a/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.h b/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.h new file mode 100644 index 00000000..537eafcc --- /dev/null +++ b/MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.h @@ -0,0 +1,10 @@ +// MobileGL - MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include \ No newline at end of file diff --git a/MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp index bcd93d6a..ba2aa2d0 100644 --- a/MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp @@ -9,7 +9,7 @@ #include "TemporaryEGLImpl.h" #include -#if !(MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES) +#if MOBILEGL_BACKEND != MOBILEGL_BACKEND_TYPE_DIRECT_GLES && MOBILEGL_BACKEND != MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN namespace MobileGL { namespace MG_Impl::EGLImpl { // TODO: implement complete EGL functionality @@ -98,7 +98,7 @@ namespace MobileGL { return EGL_TRUE; } - char const * QueryString(EGLDisplay display, EGLint name) { + char const* QueryString(EGLDisplay display, EGLint name) { return ""; } @@ -132,7 +132,7 @@ namespace MobileGL { } EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, - const EGLint* attrib_list) { + const EGLint* attrib_list) { return (EGLSurface)1; } diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index b31bfc69..01a40af3 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -12,6 +12,9 @@ #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES #include #endif +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN +#include +#endif #include namespace MobileGL { @@ -22,6 +25,8 @@ namespace MobileGL { #endif #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES MG_Backend::DirectGLES::Clear(mask); +#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN + MG_Backend::DirectVulkan::Clear(mask); #endif } @@ -31,6 +36,8 @@ namespace MobileGL { #endif #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES MG_Backend::DirectGLES::DrawElements(mode, count, type, indices); +#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN + MG_Backend::DirectVulkan::DrawElements(mode, count, type, indices); #endif } diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp index bcd0ed7b..3d132e67 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp @@ -31,9 +31,8 @@ namespace MobileGL { // Compile for OpenGL here, so that we can do validation and link // like a real OpenGL driver at linking stage // Will compile for other backends later. - ShaderAttrib attrib{.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage), - .sourceStr = m_source, - .flags = ShaderCompileBits::CompileForOpenGL}; + ShaderAttrib attrib{ + .shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage), .sourceStr = m_source, .flags = 0}; auto result = ShaderCompiler::CompileShader(attrib); if (result) { @@ -42,7 +41,8 @@ namespace MobileGL { } else { m_compileStatus = false; m_infoLog = result.error().log; - MGLOG_D("ShaderObject::Compile: Shader %d compilation failed.\nSource:\n%s\nInfoLog:\n%s\nSetting m_compileStatus = false as a result.", + MGLOG_D("ShaderObject::Compile: Shader %d compilation failed.\nSource:\n%s\nInfoLog:\n%s\nSetting " + "m_compileStatus = false as a result.", m_externalIndex, m_source.c_str(), m_infoLog.c_str()); } }