diff --git a/HANDOFF_DILIGENT.md b/HANDOFF_DILIGENT.md index 8b081a1e..6804674a 100644 --- a/HANDOFF_DILIGENT.md +++ b/HANDOFF_DILIGENT.md @@ -127,13 +127,27 @@ Verified locally on Turnip Adreno 750: - Color write mask - Viewport - Scissor rect +- Texture/sampler full integration: + - `ITextureObject` → Diligent `ITexture` + SRV with automatic dirty upload + - `SamplerObject` / texture-object sampler → Diligent `ISampler` + - Real front-end `glTexImage2D` path (not only `CreateTestTexture`) verified +- Global UBO upload: + - Front-end `glUniform*` shadow → Diligent uniform buffer bound as `MGL_GLOBAL_UBO` +- User framebuffer mapping: + - Current draw/read FBO resolves texture attachments to Diligent RTV/DSV + - `ReadPixels` can read back from a user FBO color attachment +- More GL entry points wired: + - `DrawRangeElements` + - `DrawRangeElementsBaseVertex` + - `MultiDrawArrays` + - `MultiDrawElements` - Primitive expansion: - `GL_TRIANGLE_FAN` expanded to triangle list - `GL_LINE_LOOP` expanded to line strip - Local test result: ``` -[ PASSED ] 5 tests +[ PASSED ] 8 tests ``` --- @@ -180,9 +194,9 @@ Notes: ## 7. Known Limitations / Not Yet Implemented -- `DrawArrays`/`DrawElements` draw through real `MG_State`, but only the color offscreen target is used; no user-created framebuffer/renderbuffer mapping yet. -- Textures: only a test texture path (`CreateTestTexture`) is wired; no automatic sync of `TextureObject` to Diligent resources, no texture unit bindings, no mipmap/sampler state from `MG_State`. -- Uniforms / UBOs are not uploaded or bound yet. +- User framebuffers now support texture color attachments and depth/stencil texture or renderbuffer attachments; renderbuffer readback and multi-target color attachments remain partial. +- Textures auto-sync `ITextureObject` → Diligent resources, including mip levels and sampler state; compressed textures and integer/3-channel formats that Diligent lacks are still skipped. +- Global UBO (default-block `glUniform*`) now uploads and binds; named application UBO blocks and SSBOs are not fed from frontend buffer bindings yet. - No swapchain / EGL window surface presentation yet; `Present()` only flushes. - No transform feedback / queries / sync / readback of non-color resources. - Some GL 3.2 entry points are still not implemented in the backend (draw range, multi-draw, blit, buffer subdata paths, etc.). @@ -194,19 +208,19 @@ Notes: ## 8. Recommended Next Steps 1. **Framebuffer / Renderbuffer mapping** - - Map `MG_State::GLState::FramebufferObject` attachments to Diligent `ITextureView` / `ITexture`. - - Support default framebuffer as current offscreen target. - - Support `glBindFramebuffer`, `glFramebufferTexture2D`, renderbuffer color/depth attachments. + - [x] Map `MG_State::GLState::FramebufferObject` attachments to Diligent `ITextureView` / `ITexture`. + - [x] Support default framebuffer as current offscreen target. + - [~] Support `glBindFramebuffer`, `glFramebufferTexture2D`, renderbuffer color/depth attachments (texture color + depth/renderbuffer work; renderbuffer readback and multiple color targets still partial). 2. **Texture / Sampler full integration** - - Translate MobileGL `ITextureObject` to Diligent `ITexture` and cache by `GetLifetimeId()`. - - Propagate texture unit bindings into the PSO SRB. - - Translate `SamplerObject` state into Diligent `SamplerDesc`. + - [x] Translate MobileGL `ITextureObject` to Diligent `ITexture` and cache by `GetLifetimeId()`. + - [x] Propagate texture unit bindings into the PSO SRB. + - [x] Translate `SamplerObject` state into Diligent `SamplerDesc`. 3. **Uniform / UBO support** - - Create Diligent buffer for `ProgramObject::GetUBOData()` / `GetUBOSize()`. - - Bind the global UBO as a static shader resource. - - Handle per-program uniform block bindings. + - [x] Create Diligent buffer for `ProgramObject::GetUBOData()` / `GetUBOSize()`. + - [x] Bind the global UBO as a dynamic shader resource. + - [ ] Handle per-program uniform block bindings / named UBO blocks. 4. **PSO / resource caching** - Cache PSOs by program + VAO config + render state + topology. diff --git a/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp b/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp index 4f52cc58..d0d7c973 100644 --- a/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp +++ b/MobileGL/MG_Backend/Diligent/BackendObject_Diligent.cpp @@ -63,6 +63,49 @@ namespace MobileGL::MG_Backend::DiligentBackend { } } + void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, + const void* indices) { + // The CPU-side UploadVertexDataFromState path already honors the selected index + // range. start/end only restrict which indices may be referenced; they do not + // change the vertex buffer layout for this backend. + (void)start; + (void)end; + DrawElements(mode, count, type, indices); + } + + void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, + const void* indices, GLint basevertex) { + (void)start; + (void)end; + (void)basevertex; + DrawElements(mode, count, type, indices); + } + + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) { + auto* renderer = GetActiveRenderer(); + if (renderer == nullptr) { + return; + } + for (GLsizei i = 0; i < drawcount; ++i) { + if (count[i] > 0) { + renderer->DrawFromState(mode, first[i], count[i], 0, nullptr); + } + } + } + + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount) { + auto* renderer = GetActiveRenderer(); + if (renderer == nullptr) { + return; + } + for (GLsizei i = 0; i < drawcount; ++i) { + if (count[i] > 0) { + renderer->DrawFromState(mode, 0, count[i], type, indices[i]); + } + } + } + void Present() { auto* renderer = GetActiveRenderer(); if (renderer != nullptr) { @@ -151,6 +194,10 @@ namespace MobileGL::MG_Backend::DiligentBackend { m_functions.GL.Clear = Clear; m_functions.GL.DrawArrays = DrawArrays; m_functions.GL.DrawElements = DrawElements; + m_functions.GL.DrawRangeElements = DrawRangeElements; + m_functions.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex; + m_functions.GL.MultiDrawArrays = MultiDrawArrays; + m_functions.GL.MultiDrawElements = MultiDrawElements; m_functions.Present = Present; m_initialized = true; diff --git a/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp index 27048c2e..312b6cab 100644 --- a/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp +++ b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -180,6 +182,163 @@ void main() default: return ::Diligent::STENCIL_OP_KEEP; } } + + Bool IsDepthFormat(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthComponent16: + case TextureInternalFormat::DepthComponent24: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent32F: + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::Depth32FStencil8: + case TextureInternalFormat::DepthStencil: + return true; + default: + return false; + } + } + + ::Diligent::TEXTURE_FORMAT ConvertInternalFormatToDiligent(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::R8: return ::Diligent::TEX_FORMAT_R8_UNORM; + case TextureInternalFormat::R8Snorm: return ::Diligent::TEX_FORMAT_R8_SNORM; + case TextureInternalFormat::R8I: return ::Diligent::TEX_FORMAT_R8_SINT; + case TextureInternalFormat::R8UI: return ::Diligent::TEX_FORMAT_R8_UINT; + case TextureInternalFormat::R16: return ::Diligent::TEX_FORMAT_R16_UNORM; + case TextureInternalFormat::R16Snorm: return ::Diligent::TEX_FORMAT_R16_SNORM; + case TextureInternalFormat::R16I: return ::Diligent::TEX_FORMAT_R16_SINT; + case TextureInternalFormat::R16UI: return ::Diligent::TEX_FORMAT_R16_UINT; + case TextureInternalFormat::R16F: return ::Diligent::TEX_FORMAT_R16_FLOAT; + case TextureInternalFormat::R32I: return ::Diligent::TEX_FORMAT_R32_SINT; + case TextureInternalFormat::R32UI: return ::Diligent::TEX_FORMAT_R32_UINT; + case TextureInternalFormat::R32F: return ::Diligent::TEX_FORMAT_R32_FLOAT; + case TextureInternalFormat::RG8: return ::Diligent::TEX_FORMAT_RG8_UNORM; + case TextureInternalFormat::RG8Snorm: return ::Diligent::TEX_FORMAT_RG8_SNORM; + case TextureInternalFormat::RG8I: return ::Diligent::TEX_FORMAT_RG8_SINT; + case TextureInternalFormat::RG8UI: return ::Diligent::TEX_FORMAT_RG8_UINT; + case TextureInternalFormat::RG16: return ::Diligent::TEX_FORMAT_RG16_UNORM; + case TextureInternalFormat::RG16Snorm: return ::Diligent::TEX_FORMAT_RG16_SNORM; + case TextureInternalFormat::RG16I: return ::Diligent::TEX_FORMAT_RG16_SINT; + case TextureInternalFormat::RG16UI: return ::Diligent::TEX_FORMAT_RG16_UINT; + case TextureInternalFormat::RG16F: return ::Diligent::TEX_FORMAT_RG16_FLOAT; + case TextureInternalFormat::RG32I: return ::Diligent::TEX_FORMAT_RG32_SINT; + case TextureInternalFormat::RG32UI: return ::Diligent::TEX_FORMAT_RG32_UINT; + case TextureInternalFormat::RG32F: return ::Diligent::TEX_FORMAT_RG32_FLOAT; + case TextureInternalFormat::RGB8: + case TextureInternalFormat::RGB8I: + case TextureInternalFormat::RGB8UI: + case TextureInternalFormat::RGB16F: + // Diligent does not expose unsized/three-channel non-ETC2 RGB formats. + // RGB32 is the only three-channel format available. + return ::Diligent::TEX_FORMAT_UNKNOWN; + case TextureInternalFormat::RGB32F: return ::Diligent::TEX_FORMAT_RGB32_FLOAT; + case TextureInternalFormat::RGBA8: return ::Diligent::TEX_FORMAT_RGBA8_UNORM; + case TextureInternalFormat::RGBA8Snorm: return ::Diligent::TEX_FORMAT_RGBA8_SNORM; + case TextureInternalFormat::RGBA8I: return ::Diligent::TEX_FORMAT_RGBA8_SINT; + case TextureInternalFormat::RGBA8UI: return ::Diligent::TEX_FORMAT_RGBA8_UINT; + case TextureInternalFormat::RGBA16: return ::Diligent::TEX_FORMAT_RGBA16_UNORM; + case TextureInternalFormat::RGBA16Snorm: return ::Diligent::TEX_FORMAT_RGBA16_SNORM; + case TextureInternalFormat::RGBA16I: return ::Diligent::TEX_FORMAT_RGBA16_SINT; + case TextureInternalFormat::RGBA16UI: return ::Diligent::TEX_FORMAT_RGBA16_UINT; + case TextureInternalFormat::RGBA16F: return ::Diligent::TEX_FORMAT_RGBA16_FLOAT; + case TextureInternalFormat::RGBA32I: return ::Diligent::TEX_FORMAT_RGBA32_SINT; + case TextureInternalFormat::RGBA32UI: return ::Diligent::TEX_FORMAT_RGBA32_UINT; + case TextureInternalFormat::RGBA32F: return ::Diligent::TEX_FORMAT_RGBA32_FLOAT; + case TextureInternalFormat::SRGB8: return ::Diligent::TEX_FORMAT_UNKNOWN; + case TextureInternalFormat::SRGB8Alpha8: return ::Diligent::TEX_FORMAT_RGBA8_UNORM_SRGB; + case TextureInternalFormat::R11FG11FB10F: return ::Diligent::TEX_FORMAT_R11G11B10_FLOAT; + case TextureInternalFormat::RGB9E5: return ::Diligent::TEX_FORMAT_RGB9E5_SHAREDEXP; + case TextureInternalFormat::RGB10A2: return ::Diligent::TEX_FORMAT_RGB10A2_UNORM; + case TextureInternalFormat::RGB10A2UI: return ::Diligent::TEX_FORMAT_RGB10A2_UINT; + case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent32F: + return ::Diligent::TEX_FORMAT_D32_FLOAT; + case TextureInternalFormat::DepthComponent16: + return ::Diligent::TEX_FORMAT_D16_UNORM; + case TextureInternalFormat::DepthComponent24: + return ::Diligent::TEX_FORMAT_D24_UNORM_S8_UINT; + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::DepthStencil: + return ::Diligent::TEX_FORMAT_D24_UNORM_S8_UINT; + case TextureInternalFormat::Depth32FStencil8: + return ::Diligent::TEX_FORMAT_D32_FLOAT_S8X24_UINT; + case TextureInternalFormat::Red: + case TextureInternalFormat::RGBA: // The frontend keeps unsized RGBA as RGBA8 in practice. + return ::Diligent::TEX_FORMAT_RGBA8_UNORM; + case TextureInternalFormat::RG: + return ::Diligent::TEX_FORMAT_RG8_UNORM; + case TextureInternalFormat::RGB: + return ::Diligent::TEX_FORMAT_UNKNOWN; + default: + return ::Diligent::TEX_FORMAT_UNKNOWN; + } + } + + ::Diligent::RESOURCE_DIMENSION ConvertTextureTargetToDiligent(TextureTarget target) { + switch (target) { + case TextureTarget::Texture1D: return ::Diligent::RESOURCE_DIM_TEX_1D; + case TextureTarget::Texture1DArray: return ::Diligent::RESOURCE_DIM_TEX_1D_ARRAY; + case TextureTarget::Texture2D: + case TextureTarget::TextureRectangle: + case TextureTarget::Texture2DMultisample: + return ::Diligent::RESOURCE_DIM_TEX_2D; + case TextureTarget::Texture2DArray: + case TextureTarget::Texture2DMultisampleArray: + return ::Diligent::RESOURCE_DIM_TEX_2D_ARRAY; + case TextureTarget::Texture3D: return ::Diligent::RESOURCE_DIM_TEX_3D; + case TextureTarget::TextureCubeMap: return ::Diligent::RESOURCE_DIM_TEX_CUBE; + case TextureTarget::TextureCubeMapArray: return ::Diligent::RESOURCE_DIM_TEX_CUBE_ARRAY; + default: return ::Diligent::RESOURCE_DIM_UNDEFINED; + } + } + + ::Diligent::TEXTURE_ADDRESS_MODE ConvertWrapMode(SamplerWrapMode mode) { + switch (mode) { + case SamplerWrapMode::ClampToEdge: return ::Diligent::TEXTURE_ADDRESS_CLAMP; + case SamplerWrapMode::MirroredRepeat: return ::Diligent::TEXTURE_ADDRESS_MIRROR; + case SamplerWrapMode::Repeat: return ::Diligent::TEXTURE_ADDRESS_WRAP; + case SamplerWrapMode::ClampToBorder: return ::Diligent::TEXTURE_ADDRESS_BORDER; + case SamplerWrapMode::MirrorClampToEdge: return ::Diligent::TEXTURE_ADDRESS_MIRROR_ONCE; + default: return ::Diligent::TEXTURE_ADDRESS_WRAP; + } + } + + ::Diligent::COMPARISON_FUNCTION ConvertCompareFunc(SamplerCompareFunc func) { + switch (func) { + case SamplerCompareFunc::Never: return ::Diligent::COMPARISON_FUNC_NEVER; + case SamplerCompareFunc::Less: return ::Diligent::COMPARISON_FUNC_LESS; + case SamplerCompareFunc::Equal: return ::Diligent::COMPARISON_FUNC_EQUAL; + case SamplerCompareFunc::LessEqual: return ::Diligent::COMPARISON_FUNC_LESS_EQUAL; + case SamplerCompareFunc::Greater: return ::Diligent::COMPARISON_FUNC_GREATER; + case SamplerCompareFunc::NotEqual: return ::Diligent::COMPARISON_FUNC_NOT_EQUAL; + case SamplerCompareFunc::GreaterEqual: return ::Diligent::COMPARISON_FUNC_GREATER_EQUAL; + case SamplerCompareFunc::Always: return ::Diligent::COMPARISON_FUNC_ALWAYS; + default: return ::Diligent::COMPARISON_FUNC_ALWAYS; + } + } + + ::Diligent::FILTER_TYPE ConvertFilterType(SamplerFilterMode filter, SamplerMipmapMode mipmap, + Bool comparison, Float maxAnisotropy) { + if (maxAnisotropy > 1.0f) { + return comparison ? ::Diligent::FILTER_TYPE_COMPARISON_ANISOTROPIC + : ::Diligent::FILTER_TYPE_ANISOTROPIC; + } + const Bool linearFilter = filter == SamplerFilterMode::Linear; + const Bool linearMip = mipmap == SamplerMipmapMode::Linear; + const Bool pointMip = mipmap == SamplerMipmapMode::Nearest || mipmap == SamplerMipmapMode::None; + if (!comparison) { + if (linearFilter) { + return linearMip ? ::Diligent::FILTER_TYPE_LINEAR : ::Diligent::FILTER_TYPE_POINT; + } + return pointMip ? ::Diligent::FILTER_TYPE_POINT : ::Diligent::FILTER_TYPE_LINEAR; + } + if (linearFilter) { + return linearMip ? ::Diligent::FILTER_TYPE_COMPARISON_LINEAR : ::Diligent::FILTER_TYPE_COMPARISON_POINT; + } + return pointMip ? ::Diligent::FILTER_TYPE_COMPARISON_POINT : ::Diligent::FILTER_TYPE_COMPARISON_LINEAR; + } } // namespace DiligentRenderer::DiligentRenderer(::Diligent::IRenderDevice* device, ::Diligent::IDeviceContext* context) @@ -345,22 +504,34 @@ void main() } void DiligentRenderer::Clear(Float r, Float g, Float b, Float a) { - if (!m_initialized || !m_pContext || !m_pColorRTV) { + if (!m_initialized || !m_pContext) { return; } - ::Diligent::ITextureView* rtvs[] = {m_pColorRTV}; - m_pContext->SetRenderTargets(1, rtvs, m_pDepthDSV, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + Vector<::Diligent::ITextureView*> rtvs; + ::Diligent::ITextureView* dsv = nullptr; + if (!ResolveCurrentRenderTargets(rtvs, dsv) || rtvs.empty()) { + return; + } + m_pContext->SetRenderTargets(static_cast<::Diligent::Uint32>(rtvs.size()), rtvs.data(), dsv, + ::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); + for (auto* rtv : rtvs) { + m_pContext->ClearRenderTarget(rtv, clearColor, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + } } void DiligentRenderer::ClearDepth(Float depth) { - if (!m_initialized || !m_pContext || !m_pDepthDSV) { + if (!m_initialized || !m_pContext) { return; } - ::Diligent::ITextureView* rtvs[] = {m_pColorRTV}; - m_pContext->SetRenderTargets(1, rtvs, m_pDepthDSV, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - m_pContext->ClearDepthStencil(m_pDepthDSV, ::Diligent::CLEAR_DEPTH_FLAG, depth, 0, + Vector<::Diligent::ITextureView*> rtvs; + ::Diligent::ITextureView* dsv = nullptr; + if (!ResolveCurrentRenderTargets(rtvs, dsv) || dsv == nullptr) { + return; + } + m_pContext->SetRenderTargets(static_cast<::Diligent::Uint32>(rtvs.size()), rtvs.data(), dsv, + ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + m_pContext->ClearDepthStencil(dsv, ::Diligent::CLEAR_DEPTH_FLAG, depth, 0, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); } @@ -472,6 +643,449 @@ void main() return m_pTestSampler != nullptr; } + ::Diligent::ITextureView* DiligentRenderer::SyncTexture(MG_State::GLState::ITextureObject& texture) { + if (m_pDevice == nullptr || m_pContext == nullptr || texture.GetFormat() == TextureInternalFormat::Unknown) { + return nullptr; + } + + const auto format = ConvertInternalFormatToDiligent(texture.GetFormat()); + const auto dimension = ConvertTextureTargetToDiligent(texture.GetTarget()); + if (format == ::Diligent::TEX_FORMAT_UNKNOWN || dimension == ::Diligent::RESOURCE_DIM_UNDEFINED) { + MGLOG_W_ONCE("DiligentRenderer::SyncTexture: unsupported texture target/format (target=%d format=%d)", + static_cast(texture.GetTarget()), static_cast(texture.GetFormat())); + return nullptr; + } + + const Uint64 lifetimeId = texture.GetLifetimeId(); + TextureResource* resource = nullptr; + auto it = m_textureCache.find(lifetimeId); + if (it != m_textureCache.end()) { + resource = &it->second; + } + + const Bool needCreate = resource == nullptr || !resource->Texture; + const Bool needUpload = needCreate || resource->ContentVersion != texture.GetContentVersion(); + const Bool needViewUpdate = needCreate || resource->ParamsVersion != texture.GetTextureParamsVersion(); + + if (needCreate) { + TextureResource newResource; + newResource.ContentVersion = texture.GetContentVersion(); + newResource.ParamsVersion = texture.GetTextureParamsVersion(); + newResource.IsDepth = IsDepthFormat(texture.GetFormat()); + + ::Diligent::TextureDesc texDesc; + texDesc.Name = "MobileGL state texture"; + texDesc.Type = dimension; + texDesc.Format = format; + const IntVec3 baseSize = texture.GetBaseSize(); + texDesc.Width = static_cast<::Diligent::Uint32>(std::max(baseSize.x(), 1)); + texDesc.Height = static_cast<::Diligent::Uint32>(std::max(baseSize.y(), 1)); + texDesc.ArraySize = 1; + if (texture.GetTarget() == TextureTarget::TextureCubeMap || + texture.GetTarget() == TextureTarget::TextureCubeMapArray) { + texDesc.ArraySize = 6; + } else if (texture.GetTarget() == TextureTarget::Texture2DArray || + texture.GetTarget() == TextureTarget::Texture1DArray) { + texDesc.ArraySize = static_cast<::Diligent::Uint32>(std::max(baseSize.z(), 1)); + } + texDesc.MipLevels = 1; + if (auto* mip = AsMipmapTexture(&texture)) { + texDesc.MipLevels = std::max<::Diligent::Uint32>(mip->GetMipmapLevelCount(), 1); + } + texDesc.Usage = ::Diligent::USAGE_DEFAULT; + texDesc.BindFlags = ::Diligent::BIND_SHADER_RESOURCE; + if (newResource.IsDepth) { + texDesc.BindFlags |= ::Diligent::BIND_DEPTH_STENCIL; + } else { + texDesc.BindFlags |= ::Diligent::BIND_RENDER_TARGET; + } + + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> pTexture; + m_pDevice->CreateTexture(texDesc, nullptr, &pTexture); + if (!pTexture) { + MGLOG_W_ONCE("DiligentRenderer::SyncTexture: failed to create texture (id=%llu)", lifetimeId); + return nullptr; + } + newResource.Texture = pTexture; + newResource.SRV = pTexture->GetDefaultView(::Diligent::TEXTURE_VIEW_SHADER_RESOURCE); + if (!newResource.SRV) { + MGLOG_W_ONCE("DiligentRenderer::SyncTexture: failed to create SRV (id=%llu)", lifetimeId); + return nullptr; + } + if (newResource.IsDepth) { + newResource.DSV = pTexture->GetDefaultView(::Diligent::TEXTURE_VIEW_DEPTH_STENCIL); + } else if ((texDesc.BindFlags & ::Diligent::BIND_RENDER_TARGET) != 0) { + newResource.RTV = pTexture->GetDefaultView(::Diligent::TEXTURE_VIEW_RENDER_TARGET); + } + + auto inserted = m_textureCache.emplace(lifetimeId, std::move(newResource)); + resource = &inserted.first->second; + } else if (needViewUpdate && resource->Texture) { + // Recreate views if the texture's parameter/shape version moved. The content + // upload below keeps the same ITexture; for now only the default views are used. + resource->ParamsVersion = texture.GetTextureParamsVersion(); + } + + if (needUpload && resource->Texture) { + auto* mip = AsMipmapTexture(&texture); + if (mip != nullptr) { + const auto& uploadTargets = texture.GetUploadTargets(); + for (const auto uploadTarget : uploadTargets) { + Uint32 slice = 0; + if (texture.GetTarget() == TextureTarget::TextureCubeMap && + uploadTarget >= TextureUploadTarget::CubeMapPositiveX && + uploadTarget <= TextureUploadTarget::CubeMapNegativeZ) { + slice = static_cast(uploadTarget) - + static_cast(TextureUploadTarget::CubeMapPositiveX); + } + const Uint32 levelCount = mip->GetMipmapLevelCount(); + for (Uint32 level = 0; level < levelCount; ++level) { + const IntVec3 levelSize = mip->GetMipmapTexelSize(uploadTarget, level); + if (levelSize.x() <= 0 || levelSize.y() <= 0) { + continue; + } + const SizeT byteSize = mip->GetMipmapByteSize(uploadTarget, level); + if (byteSize == 0) { + continue; + } + const void* data = mip->MapMipmapData(uploadTarget, level); + if (data == nullptr) { + continue; + } + const Uint32 depth = std::max(levelSize.z(), 1); + const Uint32 rowSize = static_cast( + byteSize / static_cast(std::max(levelSize.y(), 1)) / static_cast(depth)); + ::Diligent::TextureSubResData subResData; + subResData.pData = data; + subResData.Stride = rowSize; + ::Diligent::Box dstBox{0, static_cast<::Diligent::Uint32>(levelSize.x()), + 0, static_cast<::Diligent::Uint32>(levelSize.y()), + 0, depth}; + m_pContext->UpdateTexture(resource->Texture, level, slice, dstBox, subResData, + ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION, + ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + mip->MarkStorageDirty(uploadTarget, level, false); + } + } + } + resource->ContentVersion = texture.GetContentVersion(); + } + + return resource->SRV; + } + + ::Diligent::ITextureView* DiligentRenderer::SyncTextureForAttachment(MG_State::GLState::ITextureObject& texture, + Bool depth) { + auto* srvOrView = SyncTexture(texture); + if (srvOrView == nullptr) { + return nullptr; + } + auto it = m_textureCache.find(texture.GetLifetimeId()); + if (it == m_textureCache.end()) { + return nullptr; + } + TextureResource& resource = it->second; + if (depth) { + return resource.DSV ? resource.DSV.RawPtr() : nullptr; + } + return resource.RTV ? resource.RTV.RawPtr() : nullptr; + } + + ::Diligent::ITextureView* DiligentRenderer::SyncRenderbuffer( + MG_State::GLState::RenderbufferObject& renderbuffer) { + if (m_pDevice == nullptr || !renderbuffer.IsAllocated()) { + return nullptr; + } + + const auto format = ConvertInternalFormatToDiligent(renderbuffer.GetInternalFormat()); + if (format == ::Diligent::TEX_FORMAT_UNKNOWN) { + return nullptr; + } + const Bool depth = IsDepthFormat(renderbuffer.GetInternalFormat()); + + ::Diligent::TextureDesc desc; + desc.Name = "MobileGL state renderbuffer"; + desc.Type = ::Diligent::RESOURCE_DIM_TEX_2D; + desc.Format = format; + desc.Width = static_cast<::Diligent::Uint32>(renderbuffer.GetWidth()); + desc.Height = static_cast<::Diligent::Uint32>(renderbuffer.GetHeight()); + desc.MipLevels = 1; + desc.Usage = ::Diligent::USAGE_DEFAULT; + desc.BindFlags = depth ? ::Diligent::BIND_DEPTH_STENCIL : ::Diligent::BIND_RENDER_TARGET; + + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> pTexture; + m_pDevice->CreateTexture(desc, nullptr, &pTexture); + if (!pTexture) { + return nullptr; + } + + const auto viewType = depth ? ::Diligent::TEXTURE_VIEW_DEPTH_STENCIL : ::Diligent::TEXTURE_VIEW_RENDER_TARGET; + return pTexture->GetDefaultView(viewType); + } + + Bool DiligentRenderer::ResolveCurrentRenderTargets(Vector<::Diligent::ITextureView*>& rtvs, + ::Diligent::ITextureView*& dsv) { + rtvs.clear(); + dsv = nullptr; + if (!m_initialized) { + return false; + } + + if (MG_State::pGLContext == nullptr) { + if (m_pColorRTV) { + rtvs.push_back(m_pColorRTV.RawPtr()); + } + dsv = m_pDepthDSV ? m_pDepthDSV.RawPtr() : nullptr; + return !rtvs.empty(); + } + + auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + if (!drawFbo || drawFbo->IsDefaultFramebuffer()) { + if (m_pColorRTV) { + rtvs.push_back(m_pColorRTV.RawPtr()); + } + dsv = m_pDepthDSV ? m_pDepthDSV.RawPtr() : nullptr; + return !rtvs.empty(); + } + + if (!drawFbo->CheckCompleteness()) { + return false; + } + + const auto& drawBuffers = drawFbo->GetDrawBuffers(); + for (const auto attachmentType : drawBuffers) { + if (attachmentType == FramebufferAttachmentType::None) { + continue; + } + const auto& attachment = drawFbo->GetAttachment(attachmentType); + if (attachment.IsEmpty()) { + continue; + } + ::Diligent::ITextureView* view = nullptr; + if (attachment.IsTexture()) { + view = SyncTextureForAttachment(*attachment.GetTexture(), false); + } else if (attachment.IsRenderbuffer()) { + view = SyncRenderbuffer(*attachment.GetRenderbuffer()); + } + if (view != nullptr) { + rtvs.push_back(view); + } + } + + const auto& depthAttachment = drawFbo->GetAttachment(FramebufferAttachmentType::Depth); + const auto& stencilAttachment = drawFbo->GetAttachment(FramebufferAttachmentType::Stencil); + if (depthAttachment.IsTexture()) { + dsv = SyncTextureForAttachment(*depthAttachment.GetTexture(), true); + } else if (depthAttachment.IsRenderbuffer()) { + dsv = SyncRenderbuffer(*depthAttachment.GetRenderbuffer()); + } else if (stencilAttachment.IsTexture()) { + dsv = SyncTextureForAttachment(*stencilAttachment.GetTexture(), true); + } else if (stencilAttachment.IsRenderbuffer()) { + dsv = SyncRenderbuffer(*stencilAttachment.GetRenderbuffer()); + } + return !rtvs.empty(); + } + + ::Diligent::ISampler* DiligentRenderer::SyncSampler(const MG_State::GLState::SamplerObject& sampler) { + if (m_pDevice == nullptr) { + return nullptr; + } + const Uint64 lifetimeId = sampler.GetLifetimeId(); + auto it = m_samplerCache.find(lifetimeId); + if (it != m_samplerCache.end() && it->second.Sampler && it->second.Version == sampler.GetVersion()) { + return it->second.Sampler; + } + + const auto& params = sampler.GetAllSamplerParameters(); + ::Diligent::SamplerDesc desc; + desc.AddressU = ConvertWrapMode(params.wrapS); + desc.AddressV = ConvertWrapMode(params.wrapT); + desc.AddressW = ConvertWrapMode(params.wrapR); + desc.MipLODBias = params.lodBias; + desc.MaxAnisotropy = static_cast<::Diligent::Uint32>(std::max(0.0f, params.maxAnisotropy)); + desc.MinLOD = params.minLod; + desc.MaxLOD = params.maxLod; + desc.ComparisonFunc = ConvertCompareFunc(params.compareFunc); + desc.BorderColor[0] = params.borderColor.x(); + desc.BorderColor[1] = params.borderColor.y(); + desc.BorderColor[2] = params.borderColor.z(); + desc.BorderColor[3] = params.borderColor.w(); + const Bool compare = params.compareMode == SamplerCompareMode::CompareToTexture; + desc.MinFilter = ConvertFilterType(params.minFilter, params.mipmapMode, compare, params.maxAnisotropy); + desc.MagFilter = ConvertFilterType(params.magFilter, params.mipmapMode, compare, params.maxAnisotropy); + desc.MipFilter = ConvertFilterType(params.minFilter, params.mipmapMode, compare, params.maxAnisotropy); + + ::Diligent::RefCntAutoPtr<::Diligent::ISampler> pSampler; + m_pDevice->CreateSampler(desc, &pSampler); + if (!pSampler) { + return nullptr; + } + + SamplerResource resource; + resource.Sampler = pSampler; + resource.Version = sampler.GetVersion(); + m_samplerCache[lifetimeId] = std::move(resource); + return pSampler; + } + + Bool DiligentRenderer::UploadUBOFromState(const MG_State::GLState::ProgramObject& program) { + if (m_pDevice == nullptr || m_pContext == nullptr) { + return false; + } + const Uint uboSize = program.GetUBOSize(); + if (uboSize == 0) { + m_pUBO.Release(); + m_uboSize = 0; + m_uboContentVersion = 0; + m_uboProgramLifetimeId = 0; + return true; + } + + const Bool recreate = !m_pUBO || m_uboProgramLifetimeId != program.GetLifetimeId() || + m_uboSize != uboSize; + if (recreate) { + m_pUBO.Release(); + ::Diligent::BufferDesc desc; + desc.Name = "MobileGL state global UBO"; + desc.BindFlags = ::Diligent::BIND_UNIFORM_BUFFER; + desc.Size = uboSize; + desc.Usage = ::Diligent::USAGE_DEFAULT; + + ::Diligent::BufferData initialData; + initialData.pData = program.GetUBOData(); + initialData.DataSize = uboSize; + m_pDevice->CreateBuffer(desc, &initialData, &m_pUBO); + if (!m_pUBO) { + MGLOG_E("DiligentRenderer::UploadUBOFromState: failed to create UBO buffer"); + return false; + } + m_uboSize = uboSize; + m_uboContentVersion = program.GetUBOContentVersion(); + m_uboProgramLifetimeId = program.GetLifetimeId(); + } else if (m_uboContentVersion != program.GetUBOContentVersion()) { + m_pContext->UpdateBuffer(m_pUBO, 0, uboSize, program.GetUBOData(), + ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + m_uboContentVersion = program.GetUBOContentVersion(); + } + return m_pUBO != nullptr; + } + + Bool DiligentRenderer::BindShaderResourcesFromState(const MG_State::GLState::ProgramObject& program) { + if (m_pStateSRB == nullptr) { + return false; + } + + const auto& spirvs = program.GetGeneratedSpirv(); + for (const auto& spv : spirvs) { + if (spv.empty()) { + continue; + } + SpvReflectShaderModule module{}; + if (spvReflectCreateShaderModule(spv.size() * sizeof(unsigned), spv.data(), &module) != + SPV_REFLECT_RESULT_SUCCESS) { + continue; + } + + ::Diligent::SHADER_TYPE stage = ::Diligent::SHADER_TYPE_UNKNOWN; + if (!GetSpirvStage(spv, stage)) { + spvReflectDestroyShaderModule(&module); + continue; + } + + Uint32 bindingCount = 0; + spvReflectEnumerateDescriptorBindings(&module, &bindingCount, nullptr); + Vector bindings(static_cast(bindingCount)); + if (bindingCount > 0) { + spvReflectEnumerateDescriptorBindings(&module, &bindingCount, bindings.data()); + } + + for (Uint32 b = 0; b < bindingCount; ++b) { + const auto* binding = bindings[b]; + if (binding == nullptr || binding->name == nullptr) { + continue; + } + if (binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) { + const Int location = program.GetUniformLocation(binding->name); + if (location < 0) { + continue; + } + const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + if (unit < 0 || MG_State::pGLContext == nullptr) { + continue; + } + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); + TextureTarget target = TextureTarget::Unknown; + switch (binding->image.dim) { + case SpvDim1D: target = TextureTarget::Texture1D; break; + case SpvDim2D: target = binding->image.arrayed ? TextureTarget::Texture2DArray : TextureTarget::Texture2D; break; + case SpvDim3D: target = TextureTarget::Texture3D; break; + case SpvDimCube: target = binding->image.arrayed ? TextureTarget::TextureCubeMapArray : TextureTarget::TextureCubeMap; break; + default: break; + } + if (target == TextureTarget::Unknown) { + continue; + } + auto texture = textureUnit.GetBindingSlot(target).GetBoundObject(); + if (!texture || MG_State::GLState::IsUndefinedDefaultTexture(texture.get())) { + // Preserve the legacy test-texture path: when no real front-end + // texture is bound, fall back to CreateTestTexture's SRV. + if (m_pTestSRV) { + if (auto* variable = m_pStateSRB->GetVariableByName(stage, binding->name)) { + variable->Set(m_pTestSRV); + } + } + continue; + } + const MG_State::GLState::SamplerObject* samplerToUse = textureUnit.GetSamplerObject().get(); + if (samplerToUse == nullptr) { + samplerToUse = texture->GetSamplerObject().get(); + } + const Bool incomplete = samplerToUse == nullptr || + MG_State::GLState::SamplesAsIncompleteTexture(texture.get(), samplerToUse); + if (incomplete) { + if (m_pTestSRV) { + if (auto* variable = m_pStateSRB->GetVariableByName(stage, binding->name)) { + variable->Set(m_pTestSRV); + } + } + continue; + } + auto* srv = SyncTexture(*texture); + if (srv == nullptr) { + continue; + } + auto* sampler = SyncSampler(*samplerToUse); + if (sampler == nullptr) { + continue; + } + srv->SetSampler(sampler); + auto* variable = m_pStateSRB->GetVariableByName(stage, binding->name); + if (variable != nullptr) { + variable->Set(srv); + } + } else if (binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + // MobileGL routes all default-block uniforms into one synthesized + // MGL_GLOBAL_UBO. SPIRV-Reflect may report an empty block name for the + // synthesized block, so always resolve the Diligent variable by the + // frontend's fixed global-UBO name. + if (!UploadUBOFromState(program) || !m_pUBO) { + continue; + } + auto* variable = m_pStateSRB->GetVariableByName(stage, "MGL_GLOBAL_UBO"); + if (variable == nullptr && binding->name != nullptr && binding->name[0] != '\0') { + variable = m_pStateSRB->GetVariableByName(stage, binding->name); + } + if (variable != nullptr) { + variable->Set(m_pUBO); + } + } + } + + spvReflectDestroyShaderModule(&module); + } + return true; + } + void DiligentRenderer::DrawFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices) { if (!m_initialized || !m_pContext || MG_State::pGLContext == nullptr) { return; @@ -483,8 +1097,13 @@ void main() return; } - ::Diligent::ITextureView* rtvs[] = {m_pColorRTV}; - m_pContext->SetRenderTargets(1, rtvs, m_pDepthDSV, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + Vector<::Diligent::ITextureView*> rtvs; + ::Diligent::ITextureView* dsv = nullptr; + if (!ResolveCurrentRenderTargets(rtvs, dsv)) { + return; + } + m_pContext->SetRenderTargets(static_cast<::Diligent::Uint32>(rtvs.size()), rtvs.data(), dsv, + ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION); auto glViewport = MG_State::pGLContext->GetViewport(); if (glViewport.z() <= 0 || glViewport.w() <= 0) { @@ -517,6 +1136,10 @@ void main() ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION, ::Diligent::SET_VERTEX_BUFFERS_FLAG_RESET); m_pContext->SetPipelineState(m_pPSO); + const auto& drawProgram = MG_State::pGLContext->GetProgramForDraw(); + if (drawProgram) { + BindShaderResourcesFromState(*drawProgram); + } if (MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest)) { const auto& front = MG_State::pGLContext->GetStencilState(StencilFace::Front); m_pContext->SetStencilRef(static_cast<::Diligent::Uint32>(front.Ref)); @@ -599,8 +1222,10 @@ void main() auto& graphicsPipeline = psoCI.GraphicsPipeline; psoDesc.PipelineType = ::Diligent::PIPELINE_TYPE_GRAPHICS; psoDesc.Name = "MobileGL Diligent state PSO"; + psoDesc.ResourceLayout.DefaultVariableType = ::Diligent::SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC; graphicsPipeline.NumRenderTargets = 1; graphicsPipeline.RTVFormats[0] = ::Diligent::TEX_FORMAT_RGBA8_UNORM; + graphicsPipeline.DSVFormat = ::Diligent::TEX_FORMAT_D32_FLOAT; switch (mode) { case GL_POINTS: graphicsPipeline.PrimitiveTopology = ::Diligent::PRIMITIVE_TOPOLOGY_POINT_LIST; @@ -695,16 +1320,12 @@ void main() } m_pStateSRB = nullptr; - if (m_pTestSRV) { - auto* staticVar = m_pPSO->GetStaticVariableByName(::Diligent::SHADER_TYPE_PIXEL, "g_Texture"); - if (staticVar != nullptr) { - staticVar->Set(m_pTestSRV); - } - m_pPSO->CreateShaderResourceBinding(&m_pStateSRB, false); - if (m_pStateSRB) { - m_pPSO->InitializeStaticSRBResources(m_pStateSRB); - } + m_pPSO->CreateShaderResourceBinding(&m_pStateSRB, true); + if (!m_pStateSRB) { + MGLOG_E("DiligentRenderer: failed to create state SRB"); + return false; } + BindShaderResourcesFromState(*program); return true; } @@ -842,12 +1463,34 @@ void main() return; } + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> pSrcTexture = m_pColorTarget; + if (MG_State::pGLContext != nullptr) { + auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + if (readFbo && !readFbo->IsDefaultFramebuffer()) { + auto readBuffer = readFbo->GetReadBuffer(); + if (readBuffer == FramebufferAttachmentType::None) { + readBuffer = FramebufferAttachmentType::Color0; + } + const auto& attachment = readFbo->GetAttachment(readBuffer); + if (attachment.IsTexture()) { + if (SyncTexture(*attachment.GetTexture()) != nullptr) { + auto it = m_textureCache.find(attachment.GetTexture()->GetLifetimeId()); + if (it != m_textureCache.end() && it->second.Texture) { + pSrcTexture = it->second.Texture; + } + } + } + // Renderbuffer readback is not wired yet; fall back to the default target. + } + } + + const auto& srcDesc = pSrcTexture->GetDesc(); ::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.Width = srcDesc.Width; + stagingDesc.Height = srcDesc.Height; stagingDesc.MipLevels = 1; stagingDesc.Usage = ::Diligent::USAGE_STAGING; stagingDesc.CPUAccessFlags = ::Diligent::CPU_ACCESS_READ; @@ -860,7 +1503,7 @@ void main() } ::Diligent::CopyTextureAttribs copyAttribs{ - m_pColorTarget, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION, + pSrcTexture, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION, pStaging, ::Diligent::RESOURCE_STATE_TRANSITION_MODE_TRANSITION}; copyAttribs.SrcMipLevel = 0; copyAttribs.DstMipLevel = 0; diff --git a/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h index ae450741..4ac1dec8 100644 --- a/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h +++ b/MobileGL/MG_Backend/Diligent/Renderer/DiligentRenderer.h @@ -31,6 +31,13 @@ namespace Diligent { struct IShaderResourceBinding; } +namespace MobileGL::MG_State::GLState { + class ITextureObject; + class SamplerObject; + class ProgramObject; + class RenderbufferObject; +} + 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 @@ -59,11 +66,34 @@ namespace MobileGL::MG_Backend::DiligentBackend { ::Diligent::IDeviceContext* GetContext() const { return m_pContext; } private: + struct TextureResource { + ::Diligent::RefCntAutoPtr<::Diligent::ITexture> Texture; + ::Diligent::RefCntAutoPtr<::Diligent::ITextureView> SRV; + ::Diligent::RefCntAutoPtr<::Diligent::ITextureView> RTV; + ::Diligent::RefCntAutoPtr<::Diligent::ITextureView> DSV; + Uint64 ContentVersion = 0; + Uint16 ParamsVersion = 0; + Bool IsDepth = false; + }; + + struct SamplerResource { + ::Diligent::RefCntAutoPtr<::Diligent::ISampler> Sampler; + Uint16 Version = 0; + }; + Bool CreateOffscreenTargets(); Bool CreatePipeline(); Bool CreateVertexBuffer(); Bool CreatePipelineFromState(GLenum mode); Bool UploadVertexDataFromState(GLenum mode, GLint first, GLsizei count, GLenum type, const void* indices); + ::Diligent::ITextureView* SyncTexture(MG_State::GLState::ITextureObject& texture); + ::Diligent::ITextureView* SyncTextureForAttachment(MG_State::GLState::ITextureObject& texture, Bool depth); + ::Diligent::ITextureView* SyncRenderbuffer(MG_State::GLState::RenderbufferObject& renderbuffer); + ::Diligent::ISampler* SyncSampler(const MG_State::GLState::SamplerObject& sampler); + Bool BindShaderResourcesFromState(const MG_State::GLState::ProgramObject& program); + Bool UploadUBOFromState(const MG_State::GLState::ProgramObject& program); + Bool ResolveCurrentRenderTargets(Vector<::Diligent::ITextureView*>& rtvs, + ::Diligent::ITextureView*& dsv); ::Diligent::IRenderDevice* m_pDevice = nullptr; ::Diligent::IDeviceContext* m_pContext = nullptr; @@ -77,6 +107,13 @@ namespace MobileGL::MG_Backend::DiligentBackend { ::Diligent::RefCntAutoPtr<::Diligent::IShaderResourceBinding> m_pStateSRB; ::Diligent::RefCntAutoPtr<::Diligent::IPipelineState> m_pPSO; ::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pVertexBuffer; + ::Diligent::RefCntAutoPtr<::Diligent::IBuffer> m_pUBO; + Uint32 m_uboSize = 0; + Uint32 m_uboContentVersion = 0; + Uint64 m_uboProgramLifetimeId = 0; + UnorderedMap m_textureCache; + UnorderedMap m_samplerCache; + UnorderedMap m_renderbufferCache; Uint32 m_width = 256; Uint32 m_height = 256; Uint32 m_lastDrawVertexCount = 0; diff --git a/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp b/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp index 40e36369..04d3c0d1 100644 --- a/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp +++ b/MobileGL/MG_Test/Backend/Diligent/SanityTest.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -275,3 +277,221 @@ void main() { Color = vec4(1.0, 0.0, 0.0, 1.0); } EXPECT_GT(center[0], 200) << "center should be red from indexed triangle"; EXPECT_LT(center[1], 50) << "center should not be green"; } + +TEST(DiligentVulkanBackend, DrawsRealTexturedFromMobileGLState) { + MobileGL::Initialize(); + + const char* vsSrc = R"(#version 330 core +layout(location = 0) in vec2 Position; +layout(location = 1) in vec2 UV; +out vec2 vUV; +void main() { gl_Position = vec4(Position, 0.0, 1.0); vUV = UV; } +)"; + const char* fsSrc = R"(#version 330 core +uniform sampler2D g_Texture; +in vec2 vUV; +out vec4 Color; +void main() { Color = texture(g_Texture, vUV); } +)"; + + const GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 1, &vsSrc, nullptr); + CompileShader(vs); + const GLuint fs = CreateShader(GL_FRAGMENT_SHADER); + ShaderSource(fs, 1, &fsSrc, nullptr); + CompileShader(fs); + const GLuint program = CreateProgram(); + AttachShader(program, vs); + AttachShader(program, fs); + LinkProgram(program); + UseProgram(program); + Viewport(0, 0, 256, 256); + + const float vertices[] = { + -0.5f, -0.5f, 0.0f, 0.0f, + 0.5f, -0.5f, 1.0f, 0.0f, + 0.0f, 0.5f, 0.5f, 1.0f, + }; + GLuint vbo = 0; + GenBuffers(1, &vbo); + BindBuffer(GL_ARRAY_BUFFER, vbo); + BufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + GLuint vao = 0; + GenVertexArrays(1, &vao); + BindVertexArray(vao); + EnableVertexAttribArray(0); + VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, nullptr); + EnableVertexAttribArray(1); + VertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, reinterpret_cast(8)); + + // Create a real front-end texture object instead of relying on the backend's + // test helper. This exercises the automatic TextureObject -> Diligent sync. + const std::uint8_t redTexture[2 * 2 * 4] = { + 255, 0, 0, 255, 255, 0, 0, 255, + 255, 0, 0, 255, 255, 0, 0, 255, + }; + GLuint tex = 0; + GenTextures(1, &tex); + BindTexture(GL_TEXTURE_2D, tex); + TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, redTexture); + // A single-level 2x2 texture is incomplete for the default mipmapped min filter, + // so choose a non-mipmapped filter to exercise the real TextureObject sync path. + TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + DiligentBackend::BackendObject_Diligent backend; + backend.Initialize(); + auto* renderer = backend.GetRenderer(); + if (renderer == nullptr) { + GTEST_SKIP() << "No Vulkan adapter available; skipping real-texture state test"; + } + + renderer->Clear(0.0f, 1.0f, 0.0f, 1.0f); + renderer->DrawFromState(GL_TRIANGLES, 0, 3, 0, nullptr); + renderer->Present(); + + std::uint8_t center[4] = {}; + renderer->ReadPixels(128, 128, 1, 1, center); + EXPECT_GT(center[0], 200) << "center should be red from state texture"; + EXPECT_LT(center[1], 50) << "center should not be green"; +} + +TEST(DiligentVulkanBackend, DrawsUniformFromMobileGLState) { + MobileGL::Initialize(); + + const char* vsSrc = R"(#version 330 core +layout(location = 0) in vec2 Position; +void main() { gl_Position = vec4(Position, 0.0, 1.0); } +)"; + const char* fsSrc = R"(#version 330 core +uniform vec4 u_color; +out vec4 Color; +void main() { Color = u_color; } +)"; + + const GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 1, &vsSrc, nullptr); + CompileShader(vs); + const GLuint fs = CreateShader(GL_FRAGMENT_SHADER); + ShaderSource(fs, 1, &fsSrc, nullptr); + CompileShader(fs); + const GLuint program = CreateProgram(); + AttachShader(program, vs); + AttachShader(program, fs); + LinkProgram(program); + UseProgram(program); + Viewport(0, 0, 256, 256); + + const GLint colorLoc = GetUniformLocation(program, "u_color"); + ASSERT_GE(colorLoc, 0); + Uniform4f(colorLoc, 0.0f, 0.0f, 1.0f, 1.0f); + + const float vertices[] = { + -0.5f, -0.5f, + 0.5f, -0.5f, + 0.0f, 0.5f, + }; + GLuint vbo = 0; + GenBuffers(1, &vbo); + BindBuffer(GL_ARRAY_BUFFER, vbo); + BufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + GLuint vao = 0; + GenVertexArrays(1, &vao); + BindVertexArray(vao); + EnableVertexAttribArray(0); + VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + + DiligentBackend::BackendObject_Diligent backend; + backend.Initialize(); + auto* renderer = backend.GetRenderer(); + if (renderer == nullptr) { + GTEST_SKIP() << "No Vulkan adapter available; skipping uniform state test"; + } + + renderer->Clear(0.0f, 1.0f, 0.0f, 1.0f); + renderer->DrawFromState(GL_TRIANGLES, 0, 3, 0, nullptr); + renderer->Present(); + + std::uint8_t center[4] = {}; + renderer->ReadPixels(128, 128, 1, 1, center); + EXPECT_LT(center[0], 50) << "center should not be red"; + EXPECT_LT(center[1], 50) << "center should not be green"; + EXPECT_GT(center[2], 200) << "center should be blue from uniform"; +} + + +TEST(DiligentVulkanBackend, DrawsToOffscreenFramebufferFromMobileGLState) { + MobileGL::Initialize(); + + const char* vsSrc = R"(#version 330 core +layout(location = 0) in vec2 Position; +void main() { gl_Position = vec4(Position, 0.0, 1.0); } +)"; + const char* fsSrc = R"(#version 330 core +out vec4 Color; +void main() { Color = vec4(1.0, 0.0, 0.0, 1.0); } +)"; + + const GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 1, &vsSrc, nullptr); + CompileShader(vs); + const GLuint fs = CreateShader(GL_FRAGMENT_SHADER); + ShaderSource(fs, 1, &fsSrc, nullptr); + CompileShader(fs); + const GLuint program = CreateProgram(); + AttachShader(program, vs); + AttachShader(program, fs); + LinkProgram(program); + UseProgram(program); + Viewport(0, 0, 256, 256); + + const float vertices[] = { + -0.5f, -0.5f, + 0.5f, -0.5f, + 0.0f, 0.5f, + }; + GLuint vbo = 0; + GenBuffers(1, &vbo); + BindBuffer(GL_ARRAY_BUFFER, vbo); + BufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + GLuint vao = 0; + GenVertexArrays(1, &vao); + BindVertexArray(vao); + EnableVertexAttribArray(0); + VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + + // 256x256 color texture attached to a user framebuffer. + GLuint tex = 0; + GenTextures(1, &tex); + BindTexture(GL_TEXTURE_2D, tex); + TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + + GLuint fbo = 0; + GenFramebuffers(1, &fbo); + BindFramebuffer(GL_FRAMEBUFFER, fbo); + FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + + DiligentBackend::BackendObject_Diligent backend; + backend.Initialize(); + auto* renderer = backend.GetRenderer(); + if (renderer == nullptr) { + GTEST_SKIP() << "No Vulkan adapter available; skipping framebuffer state test"; + } + + renderer->Clear(0.0f, 1.0f, 0.0f, 1.0f); + renderer->DrawFromState(GL_TRIANGLES, 0, 3, 0, nullptr); + renderer->Present(); + + std::uint8_t center[4] = {}; + renderer->ReadPixels(128, 128, 1, 1, center); + EXPECT_GT(center[0], 200) << "center should be red in the user framebuffer"; + 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"; +}