mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Feat] (MG_Backend/DirectVulkan): Basic Vulkan renderer and EGL impl for Vulkan.
This commit is contained in:
+16
-6
@@ -149,6 +149,7 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp
|
MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp
|
||||||
MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp
|
MobileGL/MG_Impl/EGLImpl/Temporary/TemporaryEGLImpl.cpp
|
||||||
MobileGL/MG_Impl/EGLImpl/EGLWrapper/EGLWrapper.cpp
|
MobileGL/MG_Impl/EGLImpl/EGLWrapper/EGLWrapper.cpp
|
||||||
|
MobileGL/MG_Impl/EGLImpl/EGLForVulkan/EGLForVulkan.cpp
|
||||||
|
|
||||||
# @INSERTION_POINT:SOURCE_FILE_GLIMPL@ #
|
# @INSERTION_POINT:SOURCE_FILE_GLIMPL@ #
|
||||||
MobileGL/MG_Impl/GLImpl/Sampler/Validators.cpp
|
MobileGL/MG_Impl/GLImpl/Sampler/Validators.cpp
|
||||||
@@ -178,6 +179,13 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Backend/DirectGLES/Utils.cpp
|
MobileGL/MG_Backend/DirectGLES/Utils.cpp
|
||||||
MobileGL/MG_Backend/DirectGLES/Managers.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/Core.cpp
|
||||||
MobileGL/MG_State/GLState/ErrorState/Error.cpp
|
MobileGL/MG_State/GLState/ErrorState/Error.cpp
|
||||||
MobileGL/MG_State/GLState/BufferState/BufferState.cpp
|
MobileGL/MG_State/GLState/BufferState/BufferState.cpp
|
||||||
@@ -295,10 +303,12 @@ if (ANDROID)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (MOBILEGL_BUILD_TEST)
|
if (NOT ANDROID)
|
||||||
add_subdirectory(MobileGL/MG_Test)
|
if (MOBILEGL_BUILD_TEST)
|
||||||
endif()
|
add_subdirectory(MobileGL/MG_Test)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (MOBILEGL_BUILD_BENCHMARK)
|
if (MOBILEGL_BUILD_BENCHMARK)
|
||||||
add_subdirectory(MobileGL/MG_Benchmark)
|
add_subdirectory(MobileGL/MG_Benchmark)
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cassert>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include "DirectVulkan.h"
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
UniquePtr<VulkanRenderer> pVulkanRenderer = nullptr;
|
||||||
|
|
||||||
|
void Clear(GLbitfield mask) {}
|
||||||
|
|
||||||
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||||
|
pVulkanRenderer->RenderFrame();
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
#include "Renderer/VulkanRenderer.h"
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
extern UniquePtr<VulkanRenderer> 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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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<uint32_t>& vsSpv,
|
||||||
|
const std::vector<uint32_t>& 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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
class VulkanContext;
|
||||||
|
|
||||||
|
class PipelineManager {
|
||||||
|
public:
|
||||||
|
PipelineManager(VulkanContext& ctx);
|
||||||
|
~PipelineManager();
|
||||||
|
|
||||||
|
VkPipeline CreateGraphicsPipelineFromSpv(const std::string& key, const std::vector<uint32_t>& vsSpv,
|
||||||
|
const std::vector<uint32_t>& 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<std::string, VkPipeline> Pipelines;
|
||||||
|
|
||||||
|
void EnsurePipelineLayout();
|
||||||
|
};
|
||||||
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
@@ -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<VkFramebuffer>&& 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<uint32_t>(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
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
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<VkImageView>& GetImageViews() const { return ImageViews; }
|
||||||
|
const std::vector<VkFramebuffer>& GetFramebuffers() const { return Framebuffers; }
|
||||||
|
|
||||||
|
void SetFramebuffers(std::vector<VkFramebuffer>&& fbs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
VulkanContext& Ctx;
|
||||||
|
VkSwapchainKHR Swapchain = VK_NULL_HANDLE;
|
||||||
|
VkFormat ImageFormat = VK_FORMAT_UNDEFINED;
|
||||||
|
VkExtent2D Extent{0, 0};
|
||||||
|
std::vector<VkImage> Images;
|
||||||
|
std::vector<VkImageView> ImageViews;
|
||||||
|
std::vector<VkFramebuffer> Framebuffers;
|
||||||
|
|
||||||
|
void CreateSwapchainInternal();
|
||||||
|
void CreateImageViews();
|
||||||
|
void DestroyImageViews();
|
||||||
|
};
|
||||||
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
@@ -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<VkPhysicalDevice> devs(count);
|
||||||
|
ThrowIfFailed(vkEnumeratePhysicalDevices(Instance, &count, devs.data()), "vkEnumeratePhysicalDevices");
|
||||||
|
|
||||||
|
for (auto d : devs) {
|
||||||
|
uint32_t qcount = 0;
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(d, &qcount, nullptr);
|
||||||
|
std::vector<VkQueueFamilyProperties> 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
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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<VulkanContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanRenderer::~VulkanRenderer() {
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::Initialize() {
|
||||||
|
if (!Window) throw MobileGL::RuntimeError("ANativeWindow is null");
|
||||||
|
Ctx->Initialize(Window, Config.AppName);
|
||||||
|
|
||||||
|
Swapchain = std::make_unique<SwapchainManager>(*Ctx);
|
||||||
|
Swapchain->Initialize();
|
||||||
|
|
||||||
|
CreateRenderPass();
|
||||||
|
|
||||||
|
PipelineMgr = std::make_unique<PipelineManager>(*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<VkFramebuffer> 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<uint32_t>(Swapchain->GetImageViews().size());
|
||||||
|
if (imageCount == 0) throw MobileGL::RuntimeError("Swapchain has zero images");
|
||||||
|
uint32_t frames = std::min<uint32_t>(Config.MaxFramesInFlight, imageCount);
|
||||||
|
Frames.clear();
|
||||||
|
for (uint32_t i = 0; i < frames; ++i) {
|
||||||
|
auto fr = std::make_unique<FrameContext>();
|
||||||
|
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<uint32_t>& vsSpv,
|
||||||
|
const std::vector<uint32_t>& 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
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
|
class VulkanContext;
|
||||||
|
class SwapchainManager;
|
||||||
|
class PipelineManager;
|
||||||
|
struct FrameContext;
|
||||||
|
|
||||||
|
using RenderCallback = std::function<void(VkCommandBuffer cmdBuf, uint32_t imageIndex, VkExtent2D extent)>;
|
||||||
|
|
||||||
|
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<uint32_t>& vsSpv,
|
||||||
|
const std::vector<uint32_t>& fsSpv);
|
||||||
|
|
||||||
|
VkExtent2D GetExtent() const;
|
||||||
|
|
||||||
|
void WaitIdle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ANativeWindow* Window = nullptr;
|
||||||
|
RendererConfig Config;
|
||||||
|
|
||||||
|
std::unique_ptr<VulkanContext> Ctx;
|
||||||
|
std::unique_ptr<SwapchainManager> Swapchain;
|
||||||
|
std::unique_ptr<PipelineManager> PipelineMgr;
|
||||||
|
|
||||||
|
VkRenderPass RenderPass = VK_NULL_HANDLE;
|
||||||
|
VkCommandPool CommandPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<FrameContext>> Frames;
|
||||||
|
uint32_t CurrentFrame = 0;
|
||||||
|
|
||||||
|
// Render callbacks map
|
||||||
|
std::vector<std::pair<std::string, RenderCallback>> 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
|
||||||
@@ -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 <Config.h>
|
||||||
|
#include <MG_State/GLState/ProgramState/ProgramObject.h>
|
||||||
|
|
||||||
|
#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<MG_State::GLState::ShaderObject>(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<MG_State::GLState::ShaderObject>(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<Uint> vsSpv;
|
||||||
|
Vector<Uint> 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<MG_Backend::DirectVulkan::VulkanRenderer>(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<ANativeWindow*>(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<EGLSync>(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<EGLImage>(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<EGLDisplay>(0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLSurface CreatePlatformWindowSurface(void* dpy, void* config, void* native_window, long const* attrib_list) {
|
||||||
|
return reinterpret_cast<EGLSurface>(0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLSurface CreatePlatformPixmapSurface(void* dpy, void* config, void* native_pixmap, long const* attrib_list) {
|
||||||
|
return reinterpret_cast<EGLSurface>(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
|
||||||
@@ -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 <Includes.h>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "TemporaryEGLImpl.h"
|
#include "TemporaryEGLImpl.h"
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
|
|
||||||
#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 MobileGL {
|
||||||
namespace MG_Impl::EGLImpl {
|
namespace MG_Impl::EGLImpl {
|
||||||
// TODO: implement complete EGL functionality
|
// TODO: implement complete EGL functionality
|
||||||
@@ -98,7 +98,7 @@ namespace MobileGL {
|
|||||||
return EGL_TRUE;
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
char const * QueryString(EGLDisplay display, EGLint name) {
|
char const* QueryString(EGLDisplay display, EGLint name) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap,
|
EGLSurface CreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap,
|
||||||
const EGLint* attrib_list) {
|
const EGLint* attrib_list) {
|
||||||
return (EGLSurface)1;
|
return (EGLSurface)1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
|
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
|
||||||
#include <MG_Backend/DirectGLES/DirectGLES.h>
|
#include <MG_Backend/DirectGLES/DirectGLES.h>
|
||||||
#endif
|
#endif
|
||||||
|
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN
|
||||||
|
#include <MG_Backend/DirectVulkan/DirectVulkan.h>
|
||||||
|
#endif
|
||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
@@ -22,6 +25,8 @@ namespace MobileGL {
|
|||||||
#endif
|
#endif
|
||||||
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
|
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
|
||||||
MG_Backend::DirectGLES::Clear(mask);
|
MG_Backend::DirectGLES::Clear(mask);
|
||||||
|
#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN
|
||||||
|
MG_Backend::DirectVulkan::Clear(mask);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +36,8 @@ namespace MobileGL {
|
|||||||
#endif
|
#endif
|
||||||
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
|
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
|
||||||
MG_Backend::DirectGLES::DrawElements(mode, count, type, indices);
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ namespace MobileGL {
|
|||||||
// Compile for OpenGL here, so that we can do validation and link
|
// Compile for OpenGL here, so that we can do validation and link
|
||||||
// like a real OpenGL driver at linking stage
|
// like a real OpenGL driver at linking stage
|
||||||
// Will compile for other backends later.
|
// Will compile for other backends later.
|
||||||
ShaderAttrib attrib{.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage),
|
ShaderAttrib attrib{
|
||||||
.sourceStr = m_source,
|
.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage), .sourceStr = m_source, .flags = 0};
|
||||||
.flags = ShaderCompileBits::CompileForOpenGL};
|
|
||||||
|
|
||||||
auto result = ShaderCompiler::CompileShader(attrib);
|
auto result = ShaderCompiler::CompileShader(attrib);
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -42,7 +41,8 @@ namespace MobileGL {
|
|||||||
} else {
|
} else {
|
||||||
m_compileStatus = false;
|
m_compileStatus = false;
|
||||||
m_infoLog = result.error().log;
|
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());
|
m_externalIndex, m_source.c_str(), m_infoLog.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user