[Feat] (Diligent): add real offscreen renderer with clear and triangle draw

- Add DiligentRenderer: creates an offscreen RGBA8 render target, compiles a
  GLSL vertex/pixel shader through Diligent's glslang path, creates a triangle
  vertex buffer and pipeline, and supports clear/draw/readback.
- BackendObject_Diligent now owns a DiligentRenderer after device creation.
- Extend local sanity test to clear green, draw a red triangle, and verify
  center is red and corner stays green.
- All Diligent local tests pass on Turnip Adreno 750.
This commit is contained in:
BZLZHH
2026-08-18 12:54:10 +08:00
parent e62f158c22
commit 3b0591e0ba
6 changed files with 381 additions and 0 deletions
+1
View File
@@ -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()
@@ -7,6 +7,7 @@
#include "BackendObject_Diligent.h"
#include "DiligentVulkan.h"
#include "Renderer/DiligentRenderer.h"
#include <MG_Backend/BackendObject.h>
#include <EngineFactoryVk.h>
@@ -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<DiligentRenderer>(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
@@ -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<DiligentRenderer> m_pRenderer;
Bool m_initialized = false;
};
} // namespace MobileGL::MG_Backend::DiligentBackend
@@ -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 <RenderDevice.h>
#include <DeviceContext.h>
#include <Texture.h>
#include <Buffer.h>
#include <Shader.h>
#include <PipelineState.h>
#include <InputLayout.h>
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<Float>(m_width), static_cast<Float>(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<const Uint8*>(mapped.pData) + y * mapped.Stride + x * 4;
Uint8* dst = static_cast<Uint8*>(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
@@ -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 <Includes.h>
// 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 <RefCntAutoPtr.hpp>
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
@@ -8,6 +8,7 @@
#include <gtest/gtest.h>
#include <MG_Backend/BackendObject.h>
#include <MG_Backend/Diligent/BackendObject_Diligent.h>
#include <MG_Backend/Diligent/Renderer/DiligentRenderer.h>
#include <MG_Backend/BackendObjects.h>
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";
}