diff --git a/CMakeLists.txt b/CMakeLists.txt index aafc750c..eb131bc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -416,6 +416,7 @@ if(MOBILEGL_ENABLE_DILIGENT) list(APPEND SOURCE_FILES MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp MobileGL/MG_Backend/Diligent/DiligentVulkan.cpp + MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp ) endif() diff --git a/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp b/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp index bf377f24..ea6b03f7 100644 --- a/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp +++ b/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp @@ -7,6 +7,7 @@ #include "BackendObject_Diligent.h" #include "DiligentVulkan.h" +#include "Renderer/DiligentRenderer.h" #include #include @@ -32,6 +33,7 @@ namespace MobileGL::MG_Backend::DiligentBackend { : m_rendererInfo(BuildInitialRendererInfo()) {} BackendObject_Diligent::~BackendObject_Diligent() { + m_pRenderer.reset(); m_pContext.Release(); m_pDevice.Release(); m_pFactoryVk = nullptr; @@ -96,9 +98,20 @@ namespace MobileGL::MG_Backend::DiligentBackend { MGLOG_W("Diligent: backend initialization failed"); return; } + + m_pRenderer = std::make_unique(m_pDevice, m_pContext); + if (!m_pRenderer->Initialize(256, 256)) { + MGLOG_W("Diligent: renderer initialization failed"); + m_pRenderer.reset(); + return; + } m_initialized = true; } + DiligentRenderer* BackendObject_Diligent::GetRenderer() { + return m_pRenderer.get(); + } + Bool BackendObject_Diligent::InitCapabilities() { // Skeleton: no format probing yet. The backend advertises GL 3.2 core // capability, and the capability tables will be filled as resource diff --git a/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h b/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h index 85759502..884c497e 100644 --- a/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h +++ b/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.h @@ -28,6 +28,8 @@ namespace Diligent { } namespace MobileGL::MG_Backend::DiligentBackend { + class DiligentRenderer; + // New Diligent/Vulkan backend, implemented from scratch on top of // DiligentCore. The backend object owns the Diligent device/context and // currently advertises OpenGL 3.2 core capability; the GL function table @@ -47,6 +49,8 @@ namespace MobileGL::MG_Backend::DiligentBackend { const DynamicBackendParameters& GetDynamicParameters() const override; BackendType GetBackendType() const override; + DiligentRenderer* GetRenderer(); + private: Bool CreateDiligentDevice(); @@ -56,6 +60,7 @@ namespace MobileGL::MG_Backend::DiligentBackend { ::Diligent::IEngineFactoryVk* m_pFactoryVk = nullptr; ::Diligent::RefCntAutoPtr<::Diligent::IRenderDevice> m_pDevice; ::Diligent::RefCntAutoPtr<::Diligent::IDeviceContext> m_pContext; + std::unique_ptr m_pRenderer; Bool m_initialized = false; }; } // namespace MobileGL::MG_Backend::DiligentBackend diff --git a/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp new file mode 100644 index 00000000..987fd567 --- /dev/null +++ b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp @@ -0,0 +1,270 @@ +// MobileGL - MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp +// Copyright (c) 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 + +#include "DiligentRenderer.h" +#include +#include +#include +#include +#include +#include +#include + +namespace MobileGL::MG_Backend::DiligentBackend { + namespace { + constexpr Uint32 kTriangleVertexCount = 3; + + const char* GetTriangleVS() { + return R"(layout(location = 0) in vec2 Position; +void main() +{ + gl_Position = vec4(Position, 0.0, 1.0); +} +)"; + } + + const char* GetTrianglePS() { + return R"(layout(location = 0) out vec4 Color; +void main() +{ + Color = vec4(1.0, 0.0, 0.0, 1.0); +} +)"; + } + + struct TriangleVertex { + Float X; + Float Y; + }; + } // namespace + + DiligentRenderer::DiligentRenderer(::Diligent::IRenderDevice* device, ::Diligent::IDeviceContext* context) + : m_pDevice(device), m_pContext(context) {} + + DiligentRenderer::~DiligentRenderer() { + m_pVertexBuffer.Release(); + m_pPSO.Release(); + m_pColorRTV.Release(); + m_pColorTarget.Release(); + } + + Bool DiligentRenderer::Initialize(Uint32 width, Uint32 height) { + if (m_initialized) { + return true; + } + if (m_pDevice == nullptr || m_pContext == nullptr) { + return false; + } + + m_width = width > 0 ? width : 256; + m_height = height > 0 ? height : 256; + + if (!CreateOffscreenTargets()) { + return false; + } + if (!CreatePipeline()) { + return false; + } + if (!CreateVertexBuffer()) { + return false; + } + + m_initialized = true; + return true; + } + + Bool DiligentRenderer::CreateOffscreenTargets() { + ::Diligent::TextureDesc texDesc; + texDesc.Name = "MobileGL Diligent offscreen color target"; + texDesc.Type = ::Diligent::RESOURCE_DIM_TEX_2D; + texDesc.Format = ::Diligent::TEX_FORMAT_RGBA8_UNORM; + texDesc.Width = m_width; + texDesc.Height = m_height; + texDesc.MipLevels = 1; + texDesc.BindFlags = ::Diligent::BIND_RENDER_TARGET | ::Diligent::BIND_SHADER_RESOURCE; + texDesc.Usage = ::Diligent::USAGE_DEFAULT; + + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> pColorTarget; + m_pDevice->CreateTexture(texDesc, nullptr, &pColorTarget); + if (!pColorTarget) { + MGLOG_E("DiligentRenderer: failed to create offscreen color target"); + return false; + } + + m_pColorTarget = pColorTarget; + m_pColorRTV = m_pColorTarget->GetDefaultView(::Diligent::TEXTURE_VIEW_RENDER_TARGET); + if (!m_pColorRTV) { + MGLOG_E("DiligentRenderer: failed to get offscreen RTV"); + return false; + } + return true; + } + + Bool DiligentRenderer::CreatePipeline() { + ::Diligent::GraphicsPipelineStateCreateInfo psoCI; + auto& psoDesc = psoCI.PSODesc; + auto& graphicsPipeline = psoCI.GraphicsPipeline; + + psoDesc.PipelineType = ::Diligent::PIPELINE_TYPE_GRAPHICS; + psoDesc.Name = "MobileGL Diligent triangle PSO"; + graphicsPipeline.NumRenderTargets = 1; + graphicsPipeline.RTVFormats[0] = ::Diligent::TEX_FORMAT_RGBA8_UNORM; + graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + graphicsPipeline.RasterizerDesc.CullMode = ::Diligent::CULL_MODE_NONE; + graphicsPipeline.DepthStencilDesc.DepthEnable = false; + + ::Diligent::LayoutElement layoutElems[] = { + {0, 0, 2, ::Diligent::VT_FLOAT32}, + }; + graphicsPipeline.InputLayout.LayoutElements = layoutElems; + graphicsPipeline.InputLayout.NumElements = 1; + + ::Diligent::ShaderCreateInfo shaderCI; + shaderCI.SourceLanguage = ::Diligent::SHADER_SOURCE_LANGUAGE_GLSL; + shaderCI.EntryPoint = "main"; + + ::Diligent::RefCntAutoPtr<::Diligent::IShader> pVS; + shaderCI.Desc = {"MobileGL Diligent triangle VS", ::Diligent::SHADER_TYPE_VERTEX, true}; + shaderCI.Source = GetTriangleVS(); + m_pDevice->CreateShader(shaderCI, &pVS); + if (!pVS) { + MGLOG_E("DiligentRenderer: failed to create vertex shader"); + return false; + } + + ::Diligent::RefCntAutoPtr<::Diligent::IShader> pPS; + shaderCI.Desc = {"MobileGL Diligent triangle PS", ::Diligent::SHADER_TYPE_PIXEL, true}; + shaderCI.Source = GetTrianglePS(); + m_pDevice->CreateShader(shaderCI, &pPS); + if (!pPS) { + MGLOG_E("DiligentRenderer: failed to create pixel shader"); + return false; + } + + psoCI.pVS = pVS; + psoCI.pPS = pPS; + m_pDevice->CreateGraphicsPipelineState(psoCI, &m_pPSO); + if (!m_pPSO) { + MGLOG_E("DiligentRenderer: failed to create pipeline state"); + return false; + } + return true; + } + + Bool DiligentRenderer::CreateVertexBuffer() { + const TriangleVertex vertices[] = { + {-0.5f, -0.5f}, + { 0.5f, -0.5f}, + { 0.0f, 0.5f}, + }; + + ::Diligent::BufferDesc buffDesc; + buffDesc.Name = "MobileGL Diligent triangle vertex buffer"; + buffDesc.BindFlags = ::Diligent::BIND_VERTEX_BUFFER; + buffDesc.Size = sizeof(vertices); + + ::Diligent::BufferData initialData; + initialData.pData = vertices; + initialData.DataSize = sizeof(vertices); + + m_pDevice->CreateBuffer(buffDesc, &initialData, &m_pVertexBuffer); + if (!m_pVertexBuffer) { + MGLOG_E("DiligentRenderer: failed to create vertex buffer"); + return false; + } + return true; + } + + void DiligentRenderer::Clear(Float r, Float g, Float b, Float a) { + if (!m_initialized || !m_pContext || !m_pColorRTV) { + return; + } + ::Diligent::ITextureView* rtvs[] = {m_pColorRTV}; + m_pContext->SetRenderTargets(1, rtvs, nullptr, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + const Float clearColor[] = {r, g, b, a}; + m_pContext->ClearRenderTarget(m_pColorRTV, clearColor, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + } + + void DiligentRenderer::DrawTriangle() { + if (!m_initialized || !m_pContext || !m_pPSO) { + return; + } + + ::Diligent::ITextureView* rtvs[] = {m_pColorRTV}; + m_pContext->SetRenderTargets(1, rtvs, nullptr, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + ::Diligent::Viewport viewport{0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f}; + m_pContext->SetViewports(1, &viewport, m_width, m_height); + + ::Diligent::IBuffer* pVBs[] = {m_pVertexBuffer}; + m_pContext->SetVertexBuffers(0, 1, pVBs, nullptr, + ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION, + ::Diligent::SET_VERTEX_BUFFERS_FLAG_RESET); + + m_pContext->SetPipelineState(m_pPSO); + ::Diligent::DrawAttribs drawAttrs; + drawAttrs.NumVertices = kTriangleVertexCount; + drawAttrs.Flags = ::Diligent::DRAW_FLAG_VERIFY_ALL; + m_pContext->Draw(drawAttrs); + } + + void DiligentRenderer::ReadPixels(Uint32 x, Uint32 y, Uint32 width, Uint32 height, void* pixels) { + if (!m_initialized || !m_pContext || !m_pColorTarget || pixels == nullptr) { + return; + } + + ::Diligent::TextureDesc stagingDesc; + stagingDesc.Name = "MobileGL Diligent staging readback"; + stagingDesc.Type = ::Diligent::RESOURCE_DIM_TEX_2D; + stagingDesc.Format = ::Diligent::TEX_FORMAT_RGBA8_UNORM; + stagingDesc.Width = m_width; + stagingDesc.Height = m_height; + stagingDesc.MipLevels = 1; + stagingDesc.Usage = ::Diligent::USAGE_STAGING; + stagingDesc.CPUAccessFlags = ::Diligent::CPU_ACCESS_READ; + + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> pStaging; + m_pDevice->CreateTexture(stagingDesc, nullptr, &pStaging); + if (!pStaging) { + MGLOG_E("DiligentRenderer: failed to create staging texture"); + return; + } + + ::Diligent::CopyTextureAttribs copyAttribs{ + m_pColorTarget, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION, + pStaging, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION}; + copyAttribs.SrcMipLevel = 0; + copyAttribs.DstMipLevel = 0; + copyAttribs.SrcSlice = 0; + copyAttribs.DstSlice = 0; + m_pContext->CopyTexture(copyAttribs); + m_pContext->WaitForIdle(); + + ::Diligent::MappedTextureSubresource mapped; + m_pContext->MapTextureSubresource(pStaging, 0, 0, ::Diligent::MAP_READ, + ::Diligent::MAP_FLAG_DO_NOT_WAIT, nullptr, mapped); + if (mapped.pData == nullptr) { + MGLOG_E("DiligentRenderer: failed to map staging texture"); + return; + } + + const Uint8* srcBase = static_cast(mapped.pData) + y * mapped.Stride + x * 4; + Uint8* dst = static_cast(pixels); + for (Uint32 row = 0; row < height; ++row) { + std::memcpy(dst + row * width * 4, srcBase + row * mapped.Stride, width * 4); + } + + m_pContext->UnmapTextureSubresource(pStaging, 0, 0); + } + + void DiligentRenderer::Present() { + // Offscreen renderer: nothing to present yet. + if (m_pContext) { + m_pContext->Flush(); + } + } +} // namespace MobileGL::MG_Backend::DiligentBackend diff --git a/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h new file mode 100644 index 00000000..2f94673e --- /dev/null +++ b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h @@ -0,0 +1,66 @@ +// MobileGL - MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h +// Copyright (c) 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 + +#pragma once + +#include + +// X11 (pulled in by Includes.h through Vulkan-Headers) defines True/False as +// macros, which collide with Diligent's Bool constants in BasicTypes.h. +#if defined(True) +#undef True +#endif +#if defined(False) +#undef False +#endif + +#include + +namespace Diligent { + struct IRenderDevice; + struct IDeviceContext; + struct ITexture; + struct ITextureView; + struct IPipelineState; + struct IBuffer; +} + +namespace MobileGL::MG_Backend::DiligentBackend { + // Minimal real Diligent renderer used to prove the GL 3.2 basic path: + // clear an offscreen color target, draw a hardcoded triangle, and read + // pixels back. This is the first concrete rendering layer on top of the + // Diligent device; it will be expanded into the full MobileGL backend. + class DiligentRenderer { + public: + DiligentRenderer(::Diligent::IRenderDevice* device, ::Diligent::IDeviceContext* context); + ~DiligentRenderer(); + + Bool Initialize(Uint32 width, Uint32 height); + void Clear(Float r, Float g, Float b, Float a); + void DrawTriangle(); + void ReadPixels(Uint32 x, Uint32 y, Uint32 width, Uint32 height, void* pixels); + void Present(); + + ::Diligent::IRenderDevice* GetDevice() const { return m_pDevice; } + ::Diligent::IDeviceContext* GetContext() const { return m_pContext; } + + private: + Bool CreateOffscreenTargets(); + Bool CreatePipeline(); + Bool CreateVertexBuffer(); + + ::Diligent::IRenderDevice* m_pDevice = nullptr; + ::Diligent::IDeviceContext* m_pContext = nullptr; + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> m_pColorTarget; + ::Diligent::RefCntAutoPtr<::Diligent::ITextureView> m_pColorRTV; + ::Diligent::RefCntAutoPtr<::Diligent::IPipelineState> m_pPSO; + ::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pVertexBuffer; + Uint32 m_width = 256; + Uint32 m_height = 256; + Bool m_initialized = false; + }; +} // namespace MobileGL::MG_Backend::DiligentBackend diff --git a/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp b/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp index ac15c947..8e1d7d65 100644 --- a/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp +++ b/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include using namespace MobileGL; @@ -29,3 +30,28 @@ TEST(DiligentVulkanBackend, CreatesDiligentDeviceAndAdvertisesGL32) { EXPECT_FALSE(backend.GetBackendAPIVersionString().empty()); EXPECT_FALSE(backend.GetRendererInfo().BackendName.empty()); } + +TEST(DiligentVulkanBackend, ClearsAndDrawsTriangleOffscreen) { + DiligentBackend::BackendObject_Diligent backend; + backend.Initialize(); + + auto* renderer = backend.GetRenderer(); + if (renderer == nullptr) { + GTEST_SKIP() << "No Vulkan adapter available; skipping offscreen rendering test"; + } + + // Clear to green, then draw a red triangle over the center. + renderer->Clear(0.0f, 1.0f, 0.0f, 1.0f); + renderer->DrawTriangle(); + renderer->Present(); + + std::uint8_t center[4] = {}; + renderer->ReadPixels(128, 128, 1, 1, center); + EXPECT_GT(center[0], 200) << "center should be red from the triangle"; + EXPECT_LT(center[1], 50) << "center should not be green"; + + std::uint8_t corner[4] = {}; + renderer->ReadPixels(0, 0, 1, 1, corner); + EXPECT_GT(corner[1], 200) << "corner should remain green after clear"; + EXPECT_LT(corner[0], 50) << "corner should not be red"; +}