diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index b7f736e5..bd1d0f2c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -15,6 +15,51 @@ #include "MG_Util/Metrics/TextureMetrics.h" namespace MobileGL::MG_Backend::DirectVulkan { + static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { + switch (requestedSamples <= 0 ? 1 : requestedSamples) { + case 1: + outSampleCount = VK_SAMPLE_COUNT_1_BIT; + return true; + case 2: + outSampleCount = VK_SAMPLE_COUNT_2_BIT; + return true; + case 4: + outSampleCount = VK_SAMPLE_COUNT_4_BIT; + return true; + case 8: + outSampleCount = VK_SAMPLE_COUNT_8_BIT; + return true; + case 16: + outSampleCount = VK_SAMPLE_COUNT_16_BIT; + return true; + case 32: + outSampleCount = VK_SAMPLE_COUNT_32_BIT; + return true; + case 64: + outSampleCount = VK_SAMPLE_COUNT_64_BIT; + return true; + default: + return false; + } + } + + static VkImageAspectFlags ResolveImageAspectMaskForFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_X8_D24_UNORM_PACK32: + case VK_FORMAT_D32_SFLOAT: + return VK_IMAGE_ASPECT_DEPTH_BIT; + case VK_FORMAT_S8_UINT: + return VK_IMAGE_ASPECT_STENCIL_BIT; + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + default: + return VK_IMAGE_ASPECT_COLOR_BIT; + } + } + static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) { if (texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3) { return 1.0f; @@ -112,15 +157,36 @@ namespace MobileGL::MG_Backend::DirectVulkan { return {attachmentExtent.x(), attachmentExtent.y()}; } + void VkRenderPassManager::RenderbufferResource::Destroy(VkDevice device, VmaAllocator allocator) { + if (view != VK_NULL_HANDLE) { + vkDestroyImageView(device, view, nullptr); + } + if (image != VK_NULL_HANDLE && allocation != nullptr) { + vmaDestroyImage(allocator, image, allocation); + } + renderbuffer.reset(); + image = VK_NULL_HANDLE; + allocation = nullptr; + view = VK_NULL_HANDLE; + layout = VK_IMAGE_LAYOUT_UNDEFINED; + format = VK_FORMAT_UNDEFINED; + aspect = VK_IMAGE_ASPECT_NONE; + extent = {0, 0}; + sampleCount = VK_SAMPLE_COUNT_1_BIT; + internalFormat = TextureInternalFormat::Unknown; + samples = 0; + } + VkRenderPassManager::VkRenderPassManager(VkDevice device, - const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager, - SwapchainObject& swapchainObject): - m_device(device), m_config(config), m_clearManager(clearManager), m_textureManager(textureManager), - m_swapchainObject(swapchainObject) { + VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config, + VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject): + m_device(device), m_physicalDevice(physicalDevice), m_allocator(allocator), m_config(config), + m_clearManager(clearManager), m_textureManager(textureManager), m_swapchainObject(swapchainObject) { RenderPassEntry::s_device = m_device; s_clearManager = &m_clearManager; s_textureManager = &m_textureManager; s_swapchainObject = &m_swapchainObject; + s_renderPassManager = this; } VkRenderPassManager::~VkRenderPassManager() {} @@ -131,13 +197,209 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkRenderPassManager::Shutdown() { m_renderPasses.clear(); + for (auto& [_, resource] : m_renderbufferResources) { + resource.Destroy(m_device, m_allocator); + } + m_renderbufferResources.clear(); + m_pendingRenderbufferClears.clear(); RenderPassEntry::s_textureResourcesScratch.clear(); s_activeRenderPass = {}; s_hasActiveRenderPass = false; } + void VkRenderPassManager::CollectRenderbufferGarbage() { + Vector deadRenderbuffers; + deadRenderbuffers.reserve(m_renderbufferResources.size()); + for (auto& [renderbuffer, resource] : m_renderbufferResources) { + const auto liveRenderbuffer = resource.renderbuffer.lock(); + if (!liveRenderbuffer || liveRenderbuffer.get() != renderbuffer) { + deadRenderbuffers.emplace_back(renderbuffer); + } + } + for (auto* renderbuffer : deadRenderbuffers) { + auto resourceIt = m_renderbufferResources.find(renderbuffer); + if (resourceIt != m_renderbufferResources.end()) { + resourceIt->second.Destroy(m_device, m_allocator); + m_renderbufferResources.erase(resourceIt); + } + m_pendingRenderbufferClears.erase(renderbuffer); + } + } + + VkRenderPassManager::RenderbufferResource* VkRenderPassManager::GetOrCreateRenderbufferResource( + const SharedPtr& renderbuffer) { + if (!renderbuffer || !renderbuffer->IsAllocated()) { + return nullptr; + } + + CollectRenderbufferGarbage(); + + VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; + if (!TryResolveSampleCountFlagBits(renderbuffer->GetSamples(), sampleCount)) { + MGLOG_E("GetOrCreateRenderbufferResource: unsupported renderbuffer sample count %d for renderbuffer %u", + renderbuffer->GetSamples(), + renderbuffer->GetExternalIndex()); + return nullptr; + } + + const auto internalFormat = renderbuffer->GetInternalFormat(); + const VkFormat format = MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat); + const VkImageAspectFlags aspect = ResolveImageAspectMaskForFormat(format); + if ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { + MGLOG_E("GetOrCreateRenderbufferResource: color renderbuffer %u is not supported by DirectVulkan render passes yet", + renderbuffer->GetExternalIndex()); + return nullptr; + } + + auto& resource = m_renderbufferResources[renderbuffer.get()]; + const Bool needsCreate = + resource.image == VK_NULL_HANDLE || + resource.format != format || + resource.extent.width != static_cast(renderbuffer->GetWidth()) || + resource.extent.height != static_cast(renderbuffer->GetHeight()) || + resource.sampleCount != sampleCount || + resource.internalFormat != internalFormat || + resource.samples != renderbuffer->GetSamples(); + if (!needsCreate) { + resource.renderbuffer = renderbuffer; + return &resource; + } + + resource.Destroy(m_device, m_allocator); + resource.renderbuffer = renderbuffer; + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = static_cast(renderbuffer->GetWidth()); + imageInfo.extent.height = static_cast(renderbuffer->GetHeight()); + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.samples = sampleCount; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkImageFormatProperties imageFormatProperties{}; + const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( + m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, + &imageFormatProperties); + if (imageFormatResult != VK_SUCCESS || (imageFormatProperties.sampleCounts & sampleCount) == 0) { + MGLOG_E("GetOrCreateRenderbufferResource: unsupported renderbuffer format=%d samples=%d for renderbuffer %u", + static_cast(format), + static_cast(sampleCount), + renderbuffer->GetExternalIndex()); + return nullptr; + } + + VmaAllocationCreateInfo allocationInfo{}; + allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), + "vmaCreateImage(renderbuffer)"); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = resource.image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; + viewInfo.subresourceRange.aspectMask = aspect; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view), + "vkCreateImageView(renderbuffer)"); + + resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; + resource.format = format; + resource.aspect = aspect; + resource.extent = {imageInfo.extent.width, imageInfo.extent.height}; + resource.sampleCount = sampleCount; + resource.internalFormat = internalFormat; + resource.samples = renderbuffer->GetSamples(); + return &resource; + } + + Bool VkRenderPassManager::GetPendingRenderbufferClear( + MG_State::GLState::RenderbufferObject* renderbuffer, ClearAttachmentPayload& outPayload) const { + if (renderbuffer == nullptr) { + return false; + } + auto it = m_pendingRenderbufferClears.find(renderbuffer); + if (it == m_pendingRenderbufferClears.end()) { + return false; + } + const auto liveRenderbuffer = it->second.renderbuffer.lock(); + if (!liveRenderbuffer || liveRenderbuffer.get() != renderbuffer) { + return false; + } + outPayload = it->second.payload; + return outPayload.mask != 0; + } + + Bool VkRenderPassManager::HasPendingRenderbufferClear( + const MG_State::GLState::FramebufferAttachmentObject& attachment) const { + if (!attachment.IsRenderbuffer() || !attachment.GetRenderbuffer()) { + return false; + } + ClearAttachmentPayload payload{}; + return GetPendingRenderbufferClear(attachment.GetRenderbuffer().get(), payload); + } + + void VkRenderPassManager::QueueRenderbufferClear( + const ClearAttachmentPayload& clearPayload, + const MG_State::GLState::FramebufferAttachmentObject& attachment) { + if (clearPayload.mask == 0 || !attachment.IsRenderbuffer() || !attachment.IsComplete()) { + return; + } + const auto renderbuffer = attachment.GetRenderbuffer(); + if (!renderbuffer) { + return; + } + auto& pending = m_pendingRenderbufferClears[renderbuffer.get()]; + pending.renderbuffer = renderbuffer; + pending.payload.mask |= clearPayload.mask; + if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { + pending.payload.color = clearPayload.color; + } + if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { + pending.payload.depth = clearPayload.depth; + } + if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { + pending.payload.stencil = clearPayload.stencil; + } + } + + void VkRenderPassManager::QueueRenderbufferClear( + GLbitfield mask, const ClearFramebufferPayload& clearPayload, + const MG_State::GLState::FramebufferObject& drawFbo) { + if ((mask & GL_DEPTH_BUFFER_BIT) != 0) { + QueueRenderbufferClear( + ClearAttachmentPayload{.mask = GL_DEPTH_BUFFER_BIT, .depth = clearPayload.depth}, + drawFbo.GetAttachment(FramebufferAttachmentType::Depth)); + } + if ((mask & GL_STENCIL_BUFFER_BIT) != 0) { + QueueRenderbufferClear( + ClearAttachmentPayload{.mask = GL_STENCIL_BUFFER_BIT, .stencil = clearPayload.stencil}, + drawFbo.GetAttachment(FramebufferAttachmentType::Stencil)); + } + } + + void VkRenderPassManager::PopPendingRenderbufferClear( + MG_State::GLState::RenderbufferObject* renderbuffer) { + if (renderbuffer != nullptr) { + m_pendingRenderbufferClears.erase(renderbuffer); + } + } + VkRenderPassManager::HashType VkRenderPassManager::ComputeHash( - const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) const { + const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) { XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); if (isDefaultFbo) { @@ -224,6 +486,44 @@ namespace MobileGL::MG_Backend::DirectVulkan { } XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout))); } + if (att.IsRenderbuffer() && att.GetRenderbuffer()) { + const auto& renderbuffer = att.GetRenderbuffer(); + const auto internalFormat = renderbuffer->GetInternalFormat(); + const Int width = renderbuffer->GetWidth(); + const Int height = renderbuffer->GetHeight(); + const Int samples = renderbuffer->GetSamples(); + XXHASH_VERIFY(XXH64_update(m_hashState, &internalFormat, sizeof(internalFormat))); + XXHASH_VERIFY(XXH64_update(m_hashState, &width, sizeof(width))); + XXHASH_VERIFY(XXH64_update(m_hashState, &height, sizeof(height))); + XXHASH_VERIFY(XXH64_update(m_hashState, &samples, sizeof(samples))); + + Uint64 imageIdentity = 0; + VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + auto* resource = GetOrCreateRenderbufferResource(renderbuffer); + if (resource != nullptr) { + imageIdentity = reinterpret_cast(resource->image); + currentLayout = resource->layout; + XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount))); + } else { + const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT; + XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount))); + } + XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity))); + + if (includePendingClear) { + const Bool hasClear = HasPendingRenderbufferClear(att); + XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear))); + if (hasClear) { + ClearAttachmentPayload clearPayload{}; + const Bool hasPayload = GetPendingRenderbufferClear(renderbuffer.get(), clearPayload); + XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload))); + if (hasPayload) { + XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask))); + } + } + XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout))); + } + } }; for (Int i = 0; i < validDrawBufCount; ++i) { @@ -250,17 +550,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (att.IsTexture() && m_clearManager.HasPendingClear(att)) { return true; } + if (HasPendingRenderbufferClear(att)) { + return true; + } } const auto& depthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth); if (depthAtt.IsTexture() && m_clearManager.HasPendingClear(depthAtt)) { return true; } + if (HasPendingRenderbufferClear(depthAtt)) { + return true; + } const auto& stencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil); if (stencilAtt.IsTexture() && m_clearManager.HasPendingClear(stencilAtt)) { return true; } + if (HasPendingRenderbufferClear(stencilAtt)) { + return true; + } return false; }; @@ -444,49 +753,74 @@ namespace MobileGL::MG_Backend::DirectVulkan { depthAttachmentRef.attachment = VK_ATTACHMENT_UNUSED; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkTextureManager::TextureResource* depthTextureResource = nullptr; + RenderbufferResource* depthRenderbufferResource = nullptr; const auto isUsableDepthStencilAttachment = [](const auto& attachment) { - return attachment.IsComplete() && attachment.IsTexture(); + return attachment.IsComplete() && (attachment.IsTexture() || attachment.IsRenderbuffer()); + }; + const auto sameDepthStencilAttachmentObject = [](const auto& a, const auto& b) { + if (a.IsTexture() && b.IsTexture()) { + return a.GetTexture().get() == b.GetTexture().get() && + a.GetTextureUploadTarget() == b.GetTextureUploadTarget() && + a.GetTextureLevel() == b.GetTextureLevel(); + } + if (a.IsRenderbuffer() && b.IsRenderbuffer()) { + return a.GetRenderbuffer().get() == b.GetRenderbuffer().get(); + } + return false; }; const auto* selectedDepthStencilAttachment = isUsableDepthStencilAttachment(depthAtt) ? &depthAtt : (isUsableDepthStencilAttachment(stencilAtt) ? &stencilAtt : nullptr); const Bool hasDistinctDepthAndStencilAttachments = isUsableDepthStencilAttachment(depthAtt) && isUsableDepthStencilAttachment(stencilAtt) && - (depthAtt.GetTexture().get() != stencilAtt.GetTexture().get() || - depthAtt.GetTextureUploadTarget() != stencilAtt.GetTextureUploadTarget() || - depthAtt.GetTextureLevel() != stencilAtt.GetTextureLevel()); + !sameDepthStencilAttachmentObject(depthAtt, stencilAtt); if (hasDistinctDepthAndStencilAttachments) { MGLOG_E("GetOrCreateRenderPass: separate depth/stencil attachments are not supported yet; using the depth attachment and ignoring the standalone stencil attachment for framebuffer %u", fbo.GetExternalIndex()); } if (selectedDepthStencilAttachment != nullptr) { - auto& texture = *selectedDepthStencilAttachment->GetTexture(); - const Uint32 attachmentMipLevel = - static_cast(std::max(selectedDepthStencilAttachment->GetTextureLevel(), 0)); const Uint32 depthAttachmentIndex = static_cast(attachmentDescriptions.size()); ClearAttachmentPayload clearPayload{}; - Bool hasClear = m_clearManager.GetPendingClear(*selectedDepthStencilAttachment, clearPayload); + Bool hasClear = selectedDepthStencilAttachment->IsTexture() + ? m_clearManager.GetPendingClear(*selectedDepthStencilAttachment, clearPayload) + : GetPendingRenderbufferClear(selectedDepthStencilAttachment->GetRenderbuffer().get(), clearPayload); Bool clearDepth = hasClear && (clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0; Bool clearStencil = hasClear && (clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0; VkImageLayout trackedDepthLayout = isDefaultFbo ? m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex) : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - if (!isDefaultFbo) { + depthAttachmentDescription.flags = 0; + VkSampleCountFlagBits depthAttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; + Int depthAttachmentId = 0; + IntVec2 attachmentExtent = {0, 0}; + if (isDefaultFbo) { + depthAttachmentDescription.format = m_swapchainObject.GetDepthStencilFormat(); + depthAttachmentId = 0; + } else if (selectedDepthStencilAttachment->IsTexture()) { + auto& texture = *selectedDepthStencilAttachment->GetTexture(); depthTextureResource = m_textureManager.SyncTextureAndGetDescriptor(texture); MOBILEGL_ASSERT(depthTextureResource, "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at depth attachment"); trackedDepthLayout = depthTextureResource->layout; - } - depthAttachmentDescription.flags = 0; - depthAttachmentDescription.format = isDefaultFbo ? - m_swapchainObject.GetDepthStencilFormat() : - MG_Util::ConvertTextureInternalFormatToVkEnum(texture.GetFormat()); - VkSampleCountFlagBits depthAttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; - if (!isDefaultFbo) { depthAttachmentDescription.format = depthTextureResource->format; depthAttachmentSampleCount = depthTextureResource->sampleCount; + depthAttachmentId = static_cast(texture.GetExternalIndex()); + attachmentExtent = + ResolveRenderPassFramebufferExtent(isDefaultFbo, selectedDepthStencilAttachment->GetSize(), + swapchainExtent); + } else { + const auto& renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer(); + depthRenderbufferResource = GetOrCreateRenderbufferResource(renderbuffer); + MOBILEGL_ASSERT(depthRenderbufferResource, + "GetOrCreateRenderPass: GetOrCreateRenderbufferResource failed at depth attachment"); + trackedDepthLayout = depthRenderbufferResource->layout; + depthAttachmentDescription.format = depthRenderbufferResource->format; + depthAttachmentSampleCount = depthRenderbufferResource->sampleCount; + depthAttachmentId = static_cast(renderbuffer->GetExternalIndex()); + attachmentExtent = {static_cast(depthRenderbufferResource->extent.width), + static_cast(depthRenderbufferResource->extent.height)}; } depthAttachmentDescription.samples = depthAttachmentSampleCount; - adoptRenderPassSampleCount(depthAttachmentSampleCount, "depth/stencil", texture.GetExternalIndex()); + adoptRenderPassSampleCount(depthAttachmentSampleCount, "depth/stencil", depthAttachmentId); const auto loadInfo = ResolveDepthStencilAttachmentLoadInfo(trackedDepthLayout, clearDepth, clearStencil); depthAttachmentDescription.loadOp = loadInfo.depthLoadOp; @@ -496,15 +830,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { depthAttachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; depthAttachmentDescription.initialLayout = loadInfo.initialLayout; if (trackedDepthLayout == VK_IMAGE_LAYOUT_UNDEFINED && (!clearDepth || !clearStencil)) { - MGLOG_W("GetOrCreateRenderPass: depth/stencil attachment textureId=%d starts with undefined layout " + MGLOG_W("GetOrCreateRenderPass: depth/stencil attachment id=%d starts with undefined layout " "and partial/no clear; using DONT_CARE for uncleared aspects", - texture.GetExternalIndex()); + depthAttachmentId); } if (hasClear) { - pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { - .attachmentIndex = depthAttachmentIndex, - .key = VkClearManager::MakePendingClearKey(*selectedDepthStencilAttachment) - }); + if (selectedDepthStencilAttachment->IsTexture()) { + pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { + .attachmentIndex = depthAttachmentIndex, + .key = VkClearManager::MakePendingClearKey(*selectedDepthStencilAttachment) + }); + } else { + pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { + .attachmentIndex = depthAttachmentIndex, + .renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer().get(), + .hasInlinePayload = true, + .inlinePayload = clearPayload + }); + } } if (isDefaultFbo) { trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { @@ -513,7 +856,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { .finalLayout = depthAttachmentDescription.finalLayout, }); attachmentViews.emplace_back(m_swapchainObject.GetDepthStencilImageView(swapchainImageIndex)); - } else { + } else if (selectedDepthStencilAttachment->IsTexture()) { + auto& texture = *selectedDepthStencilAttachment->GetTexture(); + const Uint32 attachmentMipLevel = + static_cast(std::max(selectedDepthStencilAttachment->GetTextureLevel(), 0)); MOBILEGL_ASSERT(depthTextureResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || depthAttachmentDescription.loadOp != VK_ATTACHMENT_LOAD_OP_LOAD, "GetOrCreateRenderPass: depth attachment textureId=%d has undefined tracked layout with LOAD_OP_LOAD", @@ -538,9 +884,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment"); - const IntVec2 attachmentExtent = - ResolveRenderPassFramebufferExtent(isDefaultFbo, selectedDepthStencilAttachment->GetSize(), - swapchainExtent); + if (width == 0 || height == 0) { + width = attachmentExtent.x(); + height = attachmentExtent.y(); + } + } else { + const auto& renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer(); + MOBILEGL_ASSERT(depthRenderbufferResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || + depthAttachmentDescription.loadOp != VK_ATTACHMENT_LOAD_OP_LOAD, + "GetOrCreateRenderPass: depth renderbuffer %u has undefined tracked layout with LOAD_OP_LOAD", + renderbuffer->GetExternalIndex()); + MOBILEGL_ASSERT(depthRenderbufferResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || + depthAttachmentDescription.stencilLoadOp != VK_ATTACHMENT_LOAD_OP_LOAD, + "GetOrCreateRenderPass: stencil renderbuffer %u has undefined tracked layout with LOAD_OP_LOAD", + renderbuffer->GetExternalIndex()); + trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { + .target = TrackedAttachmentTarget::Renderbuffer, + .renderbuffer = renderbuffer, + .finalLayout = depthAttachmentDescription.finalLayout, + }); + textureResources.emplace_back(nullptr); + attachmentViews.emplace_back(depthRenderbufferResource->view); if (width == 0 || height == 0) { width = attachmentExtent.x(); height = attachmentExtent.y(); @@ -635,13 +999,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { clearValue.depthStencil = {1.0f, 0}; } for (const auto& pending: renderPassEntry.pendingClearAttachments) { - if (pending.key.texture == nullptr || pending.attachmentIndex >= clearValues.size()) { + if (pending.attachmentIndex >= clearValues.size()) { continue; } ClearAttachmentPayload clearPayload{}; SharedPtr liveTexture; - if (!s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { - continue; + if (pending.hasInlinePayload) { + clearPayload = pending.inlinePayload; + } else { + if (pending.key.texture == nullptr || + !s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { + continue; + } } if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { clearValues[pending.attachmentIndex].color = { @@ -664,7 +1033,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); for (const auto& pending: renderPassEntry.pendingClearAttachments) { - s_clearManager->PopPendingClear(pending.key); + if (pending.hasInlinePayload) { + if (s_renderPassManager != nullptr) { + s_renderPassManager->PopPendingRenderbufferClear(pending.renderbuffer); + } + } else { + s_clearManager->PopPendingClear(pending.key); + } } s_activeRenderPass.hash = renderPassEntry.hash; s_activeRenderPass.compatibilityHash = renderPassEntry.compatibilityHash; @@ -691,6 +1066,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { trackedAttachment.finalLayout); } break; + case TrackedAttachmentTarget::Renderbuffer: + MOBILEGL_ASSERT(s_renderPassManager != nullptr, "EndRenderPass: render pass manager is null"); + if (const auto renderbuffer = trackedAttachment.renderbuffer.lock()) { + auto resourceIt = + s_renderPassManager->m_renderbufferResources.find(renderbuffer.get()); + if (resourceIt != s_renderPassManager->m_renderbufferResources.end()) { + resourceIt->second.layout = trackedAttachment.finalLayout; + } + } + break; case TrackedAttachmentTarget::SwapchainColor: MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null"); s_swapchainObject->SetImageLayout(trackedAttachment.swapchainImageIndex, trackedAttachment.finalLayout); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index c6eec76a..15040626 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -16,10 +16,12 @@ #include "MG_State/GLState/FramebufferState/FramebufferObject.h" #include +#include namespace MobileGL::MG_Backend::DirectVulkan { enum class TrackedAttachmentTarget : Uint8 { Texture, + Renderbuffer, SwapchainColor, SwapchainDepthStencil }; @@ -27,11 +29,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct PendingClearAttachmentInfo { Uint32 attachmentIndex = 0; PendingClearKey key{}; + MG_State::GLState::RenderbufferObject* renderbuffer = nullptr; + Bool hasInlinePayload = false; + ClearAttachmentPayload inlinePayload{}; }; struct TrackedAttachmentLayoutInfo { TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture; WeakPtr texture; + WeakPtr renderbuffer; Uint32 textureMipLevel = 0; Uint32 swapchainImageIndex = 0; VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -143,8 +149,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { public: using HashType = Uint64; VkRenderPassManager(VkDevice device, - const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager, - SwapchainObject& swapchainObject); + VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config, + VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject); ~VkRenderPassManager(); Bool Initialize(); @@ -153,23 +159,64 @@ namespace MobileGL::MG_Backend::DirectVulkan { HashType ComputeHash( const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, - Bool includePendingClear = true) const; + Bool includePendingClear = true); RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex); + void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, + const MG_State::GLState::FramebufferObject& drawFbo); + void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload, + const MG_State::GLState::FramebufferAttachmentObject& attachment); + void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer); static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry); static Bool EndRenderPass(VkCommandBuffer commandBuffer); static ActiveRenderPassInfo* GetActiveRenderPass(); private: VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VmaAllocator m_allocator = nullptr; const VulkanRendererConfig& m_config; VkClearManager& m_clearManager; VkTextureManager& m_textureManager; SwapchainObject& m_swapchainObject; UnorderedMap m_renderPasses; + + struct RenderbufferResource { + WeakPtr renderbuffer; + VkImage image = VK_NULL_HANDLE; + VmaAllocation allocation = nullptr; + VkImageView view = VK_NULL_HANDLE; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; + VkExtent2D extent = {0, 0}; + VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; + TextureInternalFormat internalFormat = TextureInternalFormat::Unknown; + Int samples = 0; + + void Destroy(VkDevice device, VmaAllocator allocator); + }; + + struct PendingRenderbufferClear { + WeakPtr renderbuffer; + ClearAttachmentPayload payload{}; + }; + + UnorderedMap m_renderbufferResources; + UnorderedMap m_pendingRenderbufferClears; + + RenderbufferResource* GetOrCreateRenderbufferResource( + const SharedPtr& renderbuffer); + Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer, + ClearAttachmentPayload& outPayload) const; + Bool HasPendingRenderbufferClear( + const MG_State::GLState::FramebufferAttachmentObject& attachment) const; + void CollectRenderbufferGarbage(); + static inline XXH64_state_t* m_hashState = XXH64_createState(); static inline ActiveRenderPassInfo s_activeRenderPass{}; static inline Bool s_hasActiveRenderPass = false; static inline VkClearManager* s_clearManager = nullptr; static inline VkTextureManager* s_textureManager = nullptr; static inline SwapchainObject* s_swapchainObject = nullptr; + static inline VkRenderPassManager* s_renderPassManager = nullptr; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2bfff5ca..b7ce0d5a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -897,23 +897,47 @@ namespace MobileGL::MG_Backend::DirectVulkan { depthAttachment.GetTextureLevel() != stencilAttachment.GetTextureLevel(); } - static Bool HasCompleteRenderbufferAttachment(const MG_State::GLState::FramebufferObject& framebufferObject) { + static Bool IsColorAttachment(FramebufferAttachmentType attachmentType) { + return attachmentType >= FramebufferAttachmentType::Color0 && + attachmentType <= FramebufferAttachmentType::Color31; + } + + static Bool HasUnsupportedCompleteRenderbufferAttachment( + const MG_State::GLState::FramebufferObject& framebufferObject) { if (framebufferObject.GetExternalIndex() == 0) { return false; } - for (const auto& attachment : framebufferObject.GetAllAttachmentObjects()) { + const auto& attachments = framebufferObject.GetAllAttachmentObjects(); + for (SizeT i = 0; i < attachments.size(); ++i) { + const auto attachmentType = static_cast(i); + const auto& attachment = attachments[i]; if (attachment.IsRenderbuffer() && attachment.IsComplete()) { - return true; + if (IsColorAttachment(attachmentType)) { + return true; + } } } + + const auto& depthAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Depth); + const auto& stencilAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Stencil); + if (!depthAttachment.IsComplete() || !stencilAttachment.IsComplete()) { + return false; + } + if (depthAttachment.IsRenderbuffer() && stencilAttachment.IsRenderbuffer()) { + return depthAttachment.GetRenderbuffer().get() != stencilAttachment.GetRenderbuffer().get(); + } + if ((depthAttachment.IsRenderbuffer() || stencilAttachment.IsRenderbuffer()) && + (depthAttachment.IsTexture() || stencilAttachment.IsTexture())) { + return true; + } return false; } static Bool IsUnsupportedFramebufferForDirectVulkan( const MG_State::GLState::FramebufferObject& framebufferObject) { return HasDistinctCompleteDepthStencilTextureAttachments(framebufferObject) || - HasCompleteRenderbufferAttachment(framebufferObject); + HasUnsupportedCompleteRenderbufferAttachment(framebufferObject); } static void RecordUnsupportedFramebufferError(const char* func) { @@ -1842,7 +1866,8 @@ void main() { succeeded = m_clearManager->Initialize(); MOBILEGL_ASSERT(succeeded, "VkClearManager initialization failed."); m_renderPassManager = - MakeUnique(m_device, m_config, *m_clearManager, *m_textureManager, m_swapchainObject); + MakeUnique(m_device, m_physicalDevice.handle, m_allocator, m_config, *m_clearManager, + *m_textureManager, m_swapchainObject); MOBILEGL_ASSERT(m_renderPassManager != nullptr, "VkRenderPassManager creation failed."); succeeded = m_renderPassManager->Initialize(); MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed."); @@ -3438,6 +3463,7 @@ void main() { .stencil = MG_State::pGLContext->GetClearStencil() }; m_clearManager->QueueClear(mask, payload, *fbo); + m_renderPassManager->QueueRenderbufferClear(mask, payload, *fbo); } void VulkanRenderer::QueueClearBufferPayloadForFramebuffer( @@ -3454,7 +3480,11 @@ void main() { return; } const auto& attachment = framebuffer.GetAttachment(attachmentType); - if (!attachment.IsTexture() || attachment.IsRenderbuffer()) { + if (attachment.IsRenderbuffer()) { + m_renderPassManager->QueueRenderbufferClear(clearPayload, attachment); + return; + } + if (!attachment.IsTexture()) { return; } m_clearManager->QueueClear(clearPayload, attachment); @@ -6097,14 +6127,18 @@ void main() { clearRect.layerCount = 1; for (const auto& pending : compatibleRenderPassEntry.pendingClearAttachments) { - if (pending.key.texture == nullptr) { + if (!pending.hasInlinePayload && pending.key.texture == nullptr) { continue; } ClearAttachmentPayload clearPayload{}; SharedPtr liveTexture; - if (!m_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { - continue; + if (pending.hasInlinePayload) { + clearPayload = pending.inlinePayload; + } else { + if (!m_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { + continue; + } } VkClearAttachment clearAttachment{}; @@ -6133,7 +6167,11 @@ void main() { } vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect); - m_clearManager->PopPendingClear(pending.key); + if (pending.hasInlinePayload) { + m_renderPassManager->PopPendingRenderbufferClear(pending.renderbuffer); + } else { + m_clearManager->PopPendingClear(pending.key); + } } } diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 22d0dfd3..a2fc634c 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -110,6 +110,12 @@ namespace { private: UniquePtr m_previous; }; + + const Uint8* GetBoundTexture2DLevelBytes(GLuint texture, Uint level = 0) { + const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); + auto* mipmapObject = static_cast(textureObject.get()); + return static_cast(mipmapObject->MapMipmapData(TextureUploadTarget::Texture2D, level)); + } } // namespace TEST_F(TextureTest, CreateTexturesCreatesObjectsWithoutBinding) { @@ -187,6 +193,133 @@ TEST_F(TextureTest, BoundTexSubImage2DUsesCompactRowsAfterUnpackProcessing) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedBgra8888ToRgba8) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, nullptr); + + const Uint8 pixels[] = { + 10, 20, 30, 40, + 50, 60, 70, 80, + }; + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, pixels); + + const auto* stored = GetBoundTexture2DLevelBytes(texture); + const Uint8 expected[] = { + 20, 30, 40, 10, + 60, 70, 80, 50, + }; + for (SizeT i = 0; i < sizeof(expected); ++i) { + EXPECT_EQ(stored[i], expected[i]) << "byte " << i; + } + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(TextureTest, BoundTexImage2DUnpacksPackedBgra8888ToRgba8WithPixelStoreSkips) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + + const Uint8 pixels[] = { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + 17, 18, 19, 20, + 10, 20, 30, 40, + 50, 60, 70, 80, + 21, 22, 23, 24, + 25, 26, 27, 28, + 90, 100, 110, 120, + 130, 140, 150, 160, + 29, 30, 31, 32, + }; + + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 4); + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_PIXELS, 1); + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_ROWS, 1); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, pixels); + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 0); + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_ROWS, 0); + + const auto* stored = GetBoundTexture2DLevelBytes(texture); + const Uint8 expected[] = { + 20, 30, 40, 10, + 60, 70, 80, 50, + 100, 110, 120, 90, + 140, 150, 160, 130, + }; + for (SizeT i = 0; i < sizeof(expected); ++i) { + EXPECT_EQ(stored[i], expected[i]) << "byte " << i; + } + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedBgra8888RevToRgba8) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + + const Uint8 pixels[] = { + 10, 20, 30, 40, + 50, 60, 70, 80, + }; + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); + + const auto* stored = GetBoundTexture2DLevelBytes(texture); + const Uint8 expected[] = { + 30, 20, 10, 40, + 70, 60, 50, 80, + }; + for (SizeT i = 0; i < sizeof(expected); ++i) { + EXPECT_EQ(stored[i], expected[i]) << "byte " << i; + } + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedRgba8888ToRgba8) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr); + + const Uint8 pixels[] = { + 10, 20, 30, 40, + 50, 60, 70, 80, + }; + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, pixels); + + const auto* stored = GetBoundTexture2DLevelBytes(texture); + const Uint8 expected[] = { + 40, 30, 20, 10, + 80, 70, 60, 50, + }; + for (SizeT i = 0; i < sizeof(expected); ++i) { + EXPECT_EQ(stored[i], expected[i]) << "byte " << i; + } + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(TextureTest, BoundTexSubImage2DKeepsPackedRgba8888RevAsRgba8) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + + const Uint8 pixels[] = { + 10, 20, 30, 40, + 50, 60, 70, 80, + }; + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); + + const auto* stored = GetBoundTexture2DLevelBytes(texture); + EXPECT_EQ(std::memcmp(stored, pixels, sizeof(pixels)), 0); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + TEST_F(TextureTest, BoundTexStorage2DAllocatesRedTextureForSubImageUpdates) { GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp index dc897154..122fe7ce 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp @@ -70,6 +70,31 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { } } + static Bool GetRgba8ByteSwizzleForUnpack(TextureInputFormat inputFormat, TexturePixelDataType inputDataType, + Vector& swizzle) { + if (inputFormat == TextureInputFormat::RGBA) { + if (inputDataType == TexturePixelDataType::UnsignedInt8888) { + swizzle = {TextureSwizzleParam::Alpha, TextureSwizzleParam::Blue, TextureSwizzleParam::Green, + TextureSwizzleParam::Red}; + return true; + } + return false; + } + + if (inputFormat == TextureInputFormat::BGRA) { + if (inputDataType == TexturePixelDataType::UnsignedInt8888) { + swizzle = {TextureSwizzleParam::Green, TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha, + TextureSwizzleParam::Red}; + } else { + swizzle = {TextureSwizzleParam::Blue, TextureSwizzleParam::Green, TextureSwizzleParam::Red, + TextureSwizzleParam::Alpha}; + } + return true; + } + + return false; + } + // assume 8 bit per channel // swizzle.size() == channel count void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector& swizzle) { @@ -132,6 +157,10 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Bool isByteType = (inputDataType == TexturePixelDataType::UnsignedByte || inputDataType == TexturePixelDataType::Byte); + Vector colorSwizzle; + const Bool needColorSwizzle = + targetInternalFormat == TextureInternalFormat::RGBA8 && + GetRgba8ByteSwizzleForUnpack(textureInputFormat, inputDataType, colorSwizzle); for (Int z = 0; z < copyDepth; ++z) { const Uint8* layerSrc = src; Uint8* layerDst = dst; @@ -149,13 +178,10 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { ProcessLSBFirst(layerDst, static_cast(copyWidth), 1); } - if (textureInputFormat == TextureInputFormat::BGRA && - targetInternalFormat == TextureInternalFormat::RGBA8) { - MGLOG_D("%s: Swizzle (BGRA)", __func__); + if (needColorSwizzle) { + MGLOG_D("%s: Swizzle RGBA8 unpack", __func__); // MGLOG_D("%s: pixel0 before = %x", __func__, *((Uint32*)layerDst)); - ProcessColorSwizzle(layerDst, static_cast(copyWidth), - {TextureSwizzleParam::Blue, TextureSwizzleParam::Green, - TextureSwizzleParam::Red, TextureSwizzleParam::Alpha}); + ProcessColorSwizzle(layerDst, static_cast(copyWidth), colorSwizzle); // MGLOG_D("%s: pixel0 after = %x", __func__, *((Uint32*)layerDst)); } // else