// 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 #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; }; SizeT GetDataTypeSize(DataType type) { switch (type) { case DataType::Int8: case DataType::Uint8: return 1; case DataType::Int16: case DataType::Uint16: case DataType::Float16: return 2; case DataType::Int32: case DataType::Uint32: case DataType::Float32: case DataType::Fixed32: case DataType::Int2101010Rev: case DataType::Uint2101010Rev: return 4; case DataType::Float64: return 8; default: return 4; } } ::Diligent::VALUE_TYPE GetValueType(DataType type) { switch (type) { case DataType::Int8: return ::Diligent::VT_INT8; case DataType::Uint8: return ::Diligent::VT_UINT8; case DataType::Int16: return ::Diligent::VT_INT16; case DataType::Uint16: return ::Diligent::VT_UINT16; case DataType::Int32: return ::Diligent::VT_INT32; case DataType::Uint32: return ::Diligent::VT_UINT32; case DataType::Float16: return ::Diligent::VT_FLOAT16; case DataType::Float32: return ::Diligent::VT_FLOAT32; case DataType::Float64: return ::Diligent::VT_FLOAT64; default: return ::Diligent::VT_FLOAT32; } } Bool GetSpirvStage(const Vector& spv, ::Diligent::SHADER_TYPE& outStage) { if (spv.empty()) { return false; } SpvReflectShaderModule module{}; if (spvReflectCreateShaderModule(spv.size() * sizeof(unsigned), spv.data(), &module) != SPV_REFLECT_RESULT_SUCCESS) { return false; } const SpvReflectShaderStageFlagBits stage = module.shader_stage; spvReflectDestroyShaderModule(&module); switch (stage) { case SPV_REFLECT_SHADER_STAGE_VERTEX_BIT: outStage = ::Diligent::SHADER_TYPE_VERTEX; return true; case SPV_REFLECT_SHADER_STAGE_FRAGMENT_BIT: outStage = ::Diligent::SHADER_TYPE_PIXEL; return true; default: return false; } } } // 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::DrawVertices(const Float* vertices, Uint32 vertexCount) { if (!m_initialized || !m_pContext || !m_pPSO || vertices == nullptr || vertexCount == 0) { return; } const Uint64 dataSize = static_cast(vertexCount) * 2 * sizeof(Float); if (dataSize > m_pVertexBuffer->GetDesc().Size) { ::Diligent::BufferDesc buffDesc; buffDesc.Name = "MobileGL Diligent dynamic vertex buffer"; buffDesc.BindFlags = ::Diligent::BIND_VERTEX_BUFFER; buffDesc.Size = dataSize; ::Diligent::BufferData initialData; initialData.pData = vertices; initialData.DataSize = static_cast(dataSize); m_pDevice->CreateBuffer(buffDesc, &initialData, &m_pVertexBuffer); if (!m_pVertexBuffer) { MGLOG_E("DiligentRenderer: failed to create dynamic vertex buffer"); return; } } else { m_pContext->UpdateBuffer(m_pVertexBuffer, 0, dataSize, vertices, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); } ::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 = vertexCount; drawAttrs.Flags = ::Diligent::DRAW_FLAG_VERIFY_ALL; m_pContext->Draw(drawAttrs); } void DiligentRenderer::DrawFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices) { if (!m_initialized || !m_pContext || MG_State::pGLContext == nullptr) { return; } if (!CreatePipelineFromState(mode)) { return; } if (!UploadVertexDataFromState(mode, first, count, type, indices)) { 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 = m_lastDrawVertexCount; drawAttrs.Flags = ::Diligent::DRAW_FLAG_VERIFY_ALL; m_pContext->Draw(drawAttrs); } Bool DiligentRenderer::CreatePipelineFromState(GLenum mode) { if (MG_State::pGLContext == nullptr) { return false; } const auto& program = MG_State::pGLContext->GetProgramForDraw(); if (!program || !program->GetSpirvStatus()) { return false; } const auto& spirvs = program->GetGeneratedSpirv(); ::Diligent::RefCntAutoPtr<::Diligent::IShader> pVS; ::Diligent::RefCntAutoPtr<::Diligent::IShader> pPS; for (const auto& spv : spirvs) { ::Diligent::SHADER_TYPE stage = ::Diligent::SHADER_TYPE_UNKNOWN; if (!GetSpirvStage(spv, stage)) { continue; } ::Diligent::ShaderCreateInfo shaderCI; shaderCI.ByteCode = spv.data(); shaderCI.ByteCodeSize = spv.size() * sizeof(unsigned); shaderCI.Desc = {"MobileGL Diligent state shader", stage, true}; ::Diligent::RefCntAutoPtr<::Diligent::IShader> pShader; m_pDevice->CreateShader(shaderCI, &pShader); if (!pShader) { MGLOG_E("DiligentRenderer: failed to create state shader"); return false; } if (stage == ::Diligent::SHADER_TYPE_VERTEX) { pVS = pShader; } else if (stage == ::Diligent::SHADER_TYPE_PIXEL) { pPS = pShader; } } if (!pVS || !pPS) { MGLOG_W("DiligentRenderer: state program has no vertex/pixel SPIR-V"); return false; } const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto& attributes = vao.GetAllAttributes(); Vector<::Diligent::LayoutElement> layoutElements; Vector activeAttribs; for (Uint32 i = 0; i < vao.MAX_VERTEX_ATTRIBS; ++i) { const auto& attr = attributes[i]; if (!attr.Enabled) { continue; } ::Diligent::LayoutElement elem{}; elem.InputIndex = i; elem.BufferSlot = 0; elem.NumComponents = static_cast<::Diligent::Uint32>(attr.Size); elem.ValueType = GetValueType(attr.Type); elem.IsNormalized = attr.Normalized; activeAttribs.push_back(i); layoutElements.push_back(elem); } if (layoutElements.empty()) { MGLOG_W("DiligentRenderer: no enabled vertex attributes"); return false; } ::Diligent::GraphicsPipelineStateCreateInfo psoCI; auto& psoDesc = psoCI.PSODesc; auto& graphicsPipeline = psoCI.GraphicsPipeline; psoDesc.PipelineType = ::Diligent::PIPELINE_TYPE_GRAPHICS; psoDesc.Name = "MobileGL Diligent state PSO"; graphicsPipeline.NumRenderTargets = 1; graphicsPipeline.RTVFormats[0] = ::Diligent::TEX_FORMAT_RGBA8_UNORM; switch (mode) { case GL_POINTS: graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_POINT_LIST; break; case GL_LINES: graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_LINE_LIST; break; case GL_LINE_STRIP: graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_LINE_STRIP; break; case GL_TRIANGLES: graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; break; case GL_TRIANGLE_STRIP: graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; break; default: // GL_TRIANGLE_FAN and GL_LINE_LOOP are emulated in UploadVertexData. graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; break; } graphicsPipeline.RasterizerDesc.CullMode = ::Diligent::CULL_MODE_NONE; graphicsPipeline.DepthStencilDesc.DepthEnable = false; graphicsPipeline.InputLayout.LayoutElements = layoutElements.data(); graphicsPipeline.InputLayout.NumElements = static_cast<::Diligent::Uint32>(layoutElements.size()); psoCI.pVS = pVS; psoCI.pPS = pPS; m_pDevice->CreateGraphicsPipelineState(psoCI, &m_pPSO); if (!m_pPSO) { MGLOG_E("DiligentRenderer: failed to create state pipeline"); return false; } return true; } Bool DiligentRenderer::UploadVertexDataFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices) { if (MG_State::pGLContext == nullptr) { return false; } const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto& attributes = vao.GetAllAttributes(); Vector activeAttribs; for (Uint32 i = 0; i < vao.MAX_VERTEX_ATTRIBS; ++i) { if (attributes[i].Enabled) { activeAttribs.push_back(i); } } if (activeAttribs.empty()) { return false; } // Resolve the vertex index list (DrawArrays first..first+count or // DrawElements index buffer/client memory). Vector indicesData; const Uint32 vertexCount = static_cast(count); if (mode == GL_TRIANGLE_FAN) { // Expand a triangle fan into a triangle list. indicesData.reserve((vertexCount - 2) * 3); for (Uint32 i = 1; i + 1 < vertexCount; ++i) { indicesData.push_back(0); indicesData.push_back(i); indicesData.push_back(i + 1); } } else if (mode == GL_LINE_LOOP) { indicesData.reserve(vertexCount + 1); for (Uint32 i = 0; i < vertexCount; ++i) { indicesData.push_back(i); } if (vertexCount > 0) { indicesData.push_back(0); } } else if (type != 0 || indices != nullptr) { // DrawElements path. const auto& indexSlot = vao.GetIndexBufferBindingSlot(); const auto& indexBuffer = indexSlot.GetBoundObject(); const Uint8* indexBase = nullptr; if (indexBuffer) { indexBuffer->SyncPersistentMappedRange(); indexBase = indexBuffer->MappedData(); if (indexBase == nullptr) { return false; } indexBase += reinterpret_cast(indices); } else { indexBase = static_cast(indices); if (indexBase == nullptr) { return false; } } const SizeT indexSize = GetDataTypeSize( type == GL_UNSIGNED_BYTE ? DataType::Uint8 : type == GL_UNSIGNED_SHORT ? DataType::Uint16 : DataType::Uint32); indicesData.reserve(vertexCount); for (Uint32 i = 0; i < vertexCount; ++i) { if (indexSize == 1) { indicesData.push_back(indexBase[i]); } else if (indexSize == 2) { indicesData.push_back(reinterpret_cast(indexBase)[i]); } else { indicesData.push_back(reinterpret_cast(indexBase)[i]); } } } const Uint32 drawVertexCount = indicesData.empty() ? vertexCount : static_cast(indicesData.size()); if (drawVertexCount == 0) { return false; } // Pack enabled attributes into a single interleaved vertex buffer. SizeT vertexStride = 0; for (Uint32 attrIndex : activeAttribs) { vertexStride += GetDataTypeSize(attributes[attrIndex].Type) * static_cast(attributes[attrIndex].Size); } Vector vertexData(static_cast(drawVertexCount) * vertexStride); for (Uint32 vi = 0; vi < drawVertexCount; ++vi) { const Uint32 srcIndex = indicesData.empty() ? (static_cast(first) + vi) : indicesData[vi]; Uint8* dst = vertexData.data() + static_cast(vi) * vertexStride; for (Uint32 attrIndex : activeAttribs) { const auto& attr = attributes[attrIndex]; if (!attr.Buffer) { return false; } attr.Buffer->SyncPersistentMappedRange(); const Uint8* src = attr.Buffer->MappedData(); if (src == nullptr) { return false; } const SizeT elemSize = GetDataTypeSize(attr.Type) * static_cast(attr.Size); const SizeT srcOffset = attr.Offset + static_cast(srcIndex) * static_cast(attr.Stride); if (srcOffset + elemSize > attr.Buffer->GetSize()) { return false; } std::memcpy(dst, src + srcOffset, elemSize); dst += elemSize; } } if (!m_pVertexBuffer || m_pVertexBuffer->GetDesc().Size < vertexData.size()) { ::Diligent::BufferDesc buffDesc; buffDesc.Name = "MobileGL Diligent state vertex buffer"; buffDesc.BindFlags = ::Diligent::BIND_VERTEX_BUFFER; buffDesc.Size = vertexData.size(); ::Diligent::BufferData initialData; initialData.pData = vertexData.data(); initialData.DataSize = static_cast<::Diligent::Uint32>(vertexData.size()); m_pDevice->CreateBuffer(buffDesc, &initialData, &m_pVertexBuffer); if (!m_pVertexBuffer) { return false; } } else { m_pContext->UpdateBuffer(m_pVertexBuffer, 0, vertexData.size(), vertexData.data(), ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); } m_lastDrawVertexCount = drawVertexCount; return true; } 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