[Feat] (MG_Backend/DirectVulkan): half-assed glClear implementation

This commit is contained in:
2026-02-17 01:48:32 +08:00
parent a3b440e7c5
commit d06720f534
4 changed files with 183 additions and 28 deletions
@@ -7,13 +7,25 @@
// End of Source File Header
#include "DirectVulkan.h"
#include "MG_State/GLState/Core.h"
namespace MobileGL::MG_Backend::DirectVulkan {
UniquePtr<VulkanRenderer> pVulkanRenderer = nullptr;
void Clear(GLbitfield mask) {}
void Clear(GLbitfield mask) {
if (!pVulkanRenderer || !MG_State::pGLContext) {
return;
}
const auto& clearColor = MG_State::pGLContext->GetClearColor();
pVulkanRenderer->RequestClear(mask, clearColor);
}
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
if (pVulkanRenderer) {
pVulkanRenderer->EnsureFrameRecordingStarted();
}
(void)mode;
(void)count;
(void)type;
@@ -163,7 +163,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
gpi.pColorBlendState = &blend;
gpi.pDynamicState = &dynamicState;
gpi.layout = m_pipelineLayout;
gpi.renderPass = m_renderPass;
gpi.renderPass = m_renderPassLoad;
gpi.subpass = 0;
VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_pipeline),
@@ -247,21 +247,127 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_I("VulkanRenderer shut down completed");
}
void VulkanRenderer::Render() {
void VulkanRenderer::RequestClear(GLbitfield mask, const FloatVec4& color) {
if ((mask & GL_COLOR_BUFFER_BIT) == 0) {
return;
}
m_pendingClearColor.float32[0] = color.x();
m_pendingClearColor.float32[1] = color.y();
m_pendingClearColor.float32[2] = color.z();
m_pendingClearColor.float32[3] = color.w();
m_pendingColorClear = true;
}
Bool VulkanRenderer::ConsumePendingColorClear(VkClearColorValue& outClearColor) {
if (!m_pendingColorClear) {
return false;
}
outClearColor = m_pendingClearColor;
m_pendingColorClear = false;
return true;
}
void VulkanRenderer::TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex) {
const auto oldLayout = m_swapchainObject.GetImageLayout(imageIndex);
if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
return;
}
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barrier.oldLayout = oldLayout;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = m_swapchainObject.GetImage(imageIndex);
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
m_swapchainObject.SetImageLayout(imageIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}
void VulkanRenderer::RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor) {
VkClearAttachment clearAttachment{};
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clearAttachment.colorAttachment = 0;
clearAttachment.clearValue.color = clearColor;
VkClearRect clearRect{};
clearRect.rect.offset = {0, 0};
clearRect.rect.extent = m_swapchainObject.GetExtent();
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect);
}
void VulkanRenderer::EnsureFrameRecordingStarted() {
auto& frame = m_frameContext.GetCurrent();
if (frame.isCommandRecording) {
return;
}
if (frame.hasCommandBufferRecorded) {
MGLOG_W("EnsureFrameRecordingStarted skipped: current frame command buffer is already finalized");
return;
}
VkCommandBuffer& commandBuffer = m_frameContext.BeginCommandRecording();
TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired);
const Bool useRenderPassClearValue = m_pendingColorClear;
VkClearValue clearValue{};
if (useRenderPassClearValue) {
clearValue.color = m_pendingClearColor;
m_pendingColorClear = false;
}
// Begin render pass
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = m_renderPass;
renderPassInfo.renderPass = useRenderPassClearValue ? m_renderPassClear : m_renderPassLoad;
renderPassInfo.framebuffer = m_framebuffers[m_imageIndexAcquired];
renderPassInfo.renderArea.offset = {0, 0};
const auto swapchainExtent = m_swapchainObject.GetExtent();
renderPassInfo.renderArea.extent = swapchainExtent;
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
renderPassInfo.renderArea.extent = m_swapchainObject.GetExtent();
renderPassInfo.clearValueCount = useRenderPassClearValue ? 1 : 0;
renderPassInfo.pClearValues = useRenderPassClearValue ? &clearValue : nullptr;
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
m_isMainRenderPassActive = true;
}
void VulkanRenderer::EndFrameRecordingIfNeeded() {
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
return;
}
if (m_isMainRenderPassActive) {
vkCmdEndRenderPass(frame.commandBuffer);
m_isMainRenderPassActive = false;
}
m_frameContext.EndCommandRecording();
}
void VulkanRenderer::Render() {
// Route test rendering through the same frame-start logic used by draw calls,
// so pending glClear() state can be consumed consistently.
EnsureFrameRecordingStarted();
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording || !m_isMainRenderPassActive) {
MGLOG_W("Render skipped: frame recording was not started");
return;
}
VkCommandBuffer& commandBuffer = frame.commandBuffer;
const auto swapchainExtent = m_swapchainObject.GetExtent();
// Render commands
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline);
@@ -281,18 +387,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
// End render pass
vkCmdEndRenderPass(commandBuffer);
// End command buffer
m_frameContext.EndCommandRecording();
}
void VulkanRenderer::Present() {
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
"Present, acquired image index out of range");
auto& frame = m_frameContext.GetCurrent();
if (m_pendingColorClear && frame.isCommandRecording && m_isMainRenderPassActive) {
RecordColorClear(frame.commandBuffer, m_pendingClearColor);
m_pendingColorClear = false;
} else if (m_pendingColorClear && frame.hasCommandBufferRecorded) {
MGLOG_W("Dropping pending clear for current frame because command buffer is already finalized");
VkClearColorValue droppedClearColor{};
ConsumePendingColorClear(droppedClearColor);
} else if (m_pendingColorClear) {
EnsureFrameRecordingStarted();
}
EndFrameRecordingIfNeeded();
const auto acquiredImageLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
const Bool needsLayoutTransitionForPresent =
m_frameContext.TransitionToPresent(m_swapchainObject.GetImage(m_imageIndexAcquired), acquiredImageLayout);
@@ -671,13 +783,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_I("Command pool created");
}
void VulkanRenderer::CreateDefaultRenderPass() {
VkRenderPass VulkanRenderer::CreateDefaultRenderPass(VkAttachmentLoadOp loadOp) {
VkAttachmentDescription color{};
color.format = m_swapchainObject.GetSurfaceFormat().format;
color.samples = VK_SAMPLE_COUNT_1_BIT;
color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color.loadOp = loadOp;
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
color.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
@@ -692,9 +804,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
rpci.subpassCount = 1;
rpci.pSubpasses = &sub;
VK_VERIFY(vkCreateRenderPass(m_device, &rpci, nullptr, &m_renderPass), "vkCreateRenderPass");
VkRenderPass renderPass = VK_NULL_HANDLE;
VK_VERIFY(vkCreateRenderPass(m_device, &rpci, nullptr, &renderPass), "vkCreateRenderPass");
return renderPass;
}
MGLOG_D("RenderPass created.");
void VulkanRenderer::CreateDefaultRenderPass() {
m_renderPassLoad = CreateDefaultRenderPass(VK_ATTACHMENT_LOAD_OP_LOAD);
m_renderPassClear = CreateDefaultRenderPass(VK_ATTACHMENT_LOAD_OP_CLEAR);
MGLOG_D("RenderPasses created (LOAD/CLEAR).");
}
void VulkanRenderer::CreateDefaultFramebuffers() {
@@ -706,7 +824,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (auto iv : imageViews) {
VkImageView attachments[] = {iv};
VkFramebufferCreateInfo fbci{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
fbci.renderPass = m_renderPass;
fbci.renderPass = m_renderPassLoad;
fbci.attachmentCount = 1;
fbci.pAttachments = attachments;
fbci.width = swapchainExtent.width;
@@ -815,9 +933,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
m_framebuffers.clear();
if (m_renderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, m_renderPass, nullptr);
m_renderPass = VK_NULL_HANDLE;
if (m_renderPassClear != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, m_renderPassClear, nullptr);
m_renderPassClear = VK_NULL_HANDLE;
}
if (m_renderPassLoad != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, m_renderPassLoad, nullptr);
m_renderPassLoad = VK_NULL_HANDLE;
}
m_swapchainObject.Shutdown(m_device);
@@ -842,6 +965,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_frameContext.GetCurrent().isCommandRecording = false;
m_frameContext.GetCurrent().hasCommandBufferRecorded = false;
}
m_isMainRenderPassActive = false;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -11,6 +11,7 @@
#include "FrameContext.h"
#include "ProgramFactory.h"
#include "SwapchainObject.h"
#include "MG_Util/Math/VectorTypes.h"
#include <Includes.h>
#include "../VkIncludes.h"
@@ -24,6 +25,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void Initialize();
void Shutdown();
void RequestClear(GLbitfield mask, const FloatVec4& color);
Bool ConsumePendingColorClear(VkClearColorValue& outClearColor);
void EnsureFrameRecordingStarted();
void Render();
void Present();
@@ -69,7 +73,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandPool m_commandPool = VK_NULL_HANDLE;
VkRenderPass m_renderPass = VK_NULL_HANDLE;
VkRenderPass m_renderPassLoad = VK_NULL_HANDLE;
VkRenderPass m_renderPassClear = VK_NULL_HANDLE;
Vector<VkFramebuffer> m_framebuffers;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
@@ -77,6 +82,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint m_imageIndexAcquired = 0;
FrameContext m_frameContext;
Bool m_pendingColorClear = false;
VkClearColorValue m_pendingClearColor = {{0.0f, 0.0f, 0.0f, 1.0f}};
Bool m_isMainRenderPassActive = false;
UniquePtr<ProgramFactory> m_programFactory;
@@ -91,8 +99,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void CreateCommandPool();
void CreateFrameContexts();
void CreateDefaultRenderPass();
VkRenderPass CreateDefaultRenderPass(VkAttachmentLoadOp loadOp);
void CreateDefaultFramebuffers();
void PrepareDemoPipeline();
void TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor);
void EndFrameRecordingIfNeeded();
void ShutdownSwapchain();
@@ -19,6 +19,7 @@
#elif defined(__APPLE__)
#define GLFW_EXPOSE_NATIVE_COCOA
#endif
#include "Init.h"
#include "MG_Backend/DirectVulkan/DirectVulkan.h"
#include "MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h"
@@ -72,12 +73,18 @@ int main() {
EGLSurface surface = eglCreateWindowSurface(display, config, nativewindow, nullptr);
eglMakeCurrent(display, surface, surface, context);
MobileGL::MG_Initialize();
int i = 0;
while(!glfwWindowShouldClose(window)) {
glfwPollEvents();
if (i % 2 == 0)
MobileGL::MG_Backend::DirectVulkan::pVulkanRenderer->Render();
if (i % 1000 > 500)
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
else
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
MobileGL::MG_Backend::DirectVulkan::pVulkanRenderer->Render();
eglSwapBuffers(display, surface);
}