diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index a0468bbe..e50459e9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -931,6 +931,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { case SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE: return ProgramFactory::DescriptorBindingKind::CombinedImageSampler; + case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + return ProgramFactory::DescriptorBindingKind::UniformTexelBuffer; default: MOBILEGL_ASSERT(false, "ProgramFactory: unsupported reflected descriptor type %d", static_cast(descriptorType)); @@ -951,7 +953,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { static_cast(binding.descriptor_type)); String name = rawName; - if (kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { + if (kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler || + kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) { const auto arraySuffix = name.find("[0]"); if (arraySuffix != String::npos) { name = name.substr(0, arraySuffix); @@ -1404,7 +1407,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.uniformBlockIndexByBinding[binding] = static_cast(blockIndex); } - // Reflect sampled images + // Reflect sampled images and samplerBuffer uniforms. uint32_t reflectedBindingCount = 0; SpvReflectResult reflectResult = spvReflectEnumerateDescriptorBindings(&reflectModule, &reflectedBindingCount, nullptr); @@ -1426,13 +1429,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (sampler == nullptr) { continue; } - if (sampler->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER && - sampler->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { + const auto descriptorKind = ReflectDescriptorTypeToBindingKind(sampler->descriptor_type); + if (descriptorKind != DescriptorBindingKind::CombinedImageSampler && + descriptorKind != DescriptorBindingKind::UniformTexelBuffer) { continue; } const Uint32 binding = sampler->binding; - const String uniformName = NormalizeDescriptorName(*sampler, DescriptorBindingKind::CombinedImageSampler); + const String uniformName = NormalizeDescriptorName(*sampler, descriptorKind); MOBILEGL_ASSERT(binding < m_maxBindings, "ProgramFactory::ReflectLayout: sampler binding %u exceeds maxBindings=%u for '%s'", binding, m_maxBindings, uniformName.c_str()); @@ -1443,10 +1447,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || - entry.bindingKinds[binding] == DescriptorBindingKind::CombinedImageSampler, + entry.bindingKinds[binding] == descriptorKind, "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for sampler '%s'", binding, uniformName.c_str()); - entry.bindingKinds[binding] = DescriptorBindingKind::CombinedImageSampler; + entry.bindingKinds[binding] = descriptorKind; const TextureTarget target = UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); MOBILEGL_ASSERT(target != TextureTarget::Unknown, @@ -1493,6 +1497,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (kind == DescriptorBindingKind::UniformBufferDynamic) { layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; entry.dynamicBindings.push_back(binding); + } else if (kind == DescriptorBindingKind::UniformTexelBuffer) { + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; } else { layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index f7eaa715..0b53724e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -21,7 +21,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { enum class DescriptorBindingKind : Uint8 { None = 0, UniformBufferDynamic, - CombinedImageSampler + CombinedImageSampler, + UniformTexelBuffer }; enum class CompileOptionBit : Uint { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 9277bdbb..3ce46fe0 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -11,7 +11,10 @@ #include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/TextureState/TextureObject2D.h" +#include "MG_State/GLState/TextureState/TextureObjectBuffer.h" #include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" +#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" +#include "MG_Util/Metrics/TextureMetrics.h" #include namespace MobileGL::MG_Backend::DirectVulkan { @@ -126,6 +129,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { void UniformManager::Shutdown() { for (auto& frame : m_frames) { if (m_device != VK_NULL_HANDLE) { + for (auto& view : frame.texelBufferViews) { + if (view != VK_NULL_HANDLE) { + vkDestroyBufferView(m_device, view, nullptr); + } + } + frame.texelBufferViews.clear(); for (auto& bucket : frame.descriptorPools) { if (bucket.handle != VK_NULL_HANDLE) { vkDestroyDescriptorPool(m_device, bucket.handle, nullptr); @@ -156,6 +165,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { void UniformManager::BeginFrame(Uint32 frameIndex) { MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index"); auto& frame = m_frames[frameIndex]; + for (auto& view : frame.texelBufferViews) { + if (view != VK_NULL_HANDLE) { + vkDestroyBufferView(m_device, view, nullptr); + } + } + frame.texelBufferViews.clear(); if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) { m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame; MGLOG_D( @@ -304,6 +319,84 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Bool UniformManager::ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 binding, Uint32 frameIndex, + VkBufferView& outBufferView) { + outBufferView = VK_NULL_HANDLE; + MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveTexelBufferDescriptor: buffer manager is null"); + MOBILEGL_ASSERT(frameIndex < m_frames.size(), "ResolveTexelBufferDescriptor: frame index out of range"); + + SharedPtr texture; + if (!ResolveSamplerTexture(program, programObj, binding, texture) || texture == nullptr) { + MGLOG_E("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') is unbound", binding, + programObj.samplerNameByBinding[binding].c_str()); + return false; + } + + if (texture->GetStorageType() != TextureStorageType::Buffer || + texture->GetTarget() != TextureTarget::TextureBuffer) { + MGLOG_E( + "ResolveTexelBufferDescriptor: binding %u ('%s') expected texture buffer, got textureId=%u target=%d storage=%d", + binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), + static_cast(texture->GetTarget()), static_cast(texture->GetStorageType())); + return false; + } + + auto* textureBuffer = static_cast(texture.get()); + const auto bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject(); + if (bufferObject == nullptr) { + MGLOG_E("ResolveTexelBufferDescriptor: texture buffer binding %u ('%s') has no GL buffer bound", + binding, programObj.samplerNameByBinding[binding].c_str()); + return false; + } + + BufferSlice slice{}; + if (!m_bufferManager->SyncResidentBuffer(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) { + MGLOG_E("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u", + bufferObject->GetExternalIndex(), texture->GetExternalIndex()); + return false; + } + + const auto internalFormat = textureBuffer->GetFormat(); + const VkFormat vkFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat); + if (vkFormat == VK_FORMAT_UNDEFINED) { + MGLOG_E("ResolveTexelBufferDescriptor: unsupported texture buffer internal format %d", + static_cast(internalFormat)); + return false; + } + + const VkDeviceSize texelSize = + static_cast(MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat)); + VkDeviceSize viewRange = slice.size; + if (texelSize > 0) { + viewRange = (viewRange / texelSize) * texelSize; + } + if (viewRange == 0) { + MGLOG_E("ResolveTexelBufferDescriptor: texture buffer %u has empty view range", texture->GetExternalIndex()); + return false; + } + + VkBufferViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; + viewInfo.buffer = slice.buffer; + viewInfo.format = vkFormat; + viewInfo.offset = slice.offset; + viewInfo.range = viewRange; + + VkBufferView bufferView = VK_NULL_HANDLE; + const VkResult result = vkCreateBufferView(m_device, &viewInfo, nullptr, &bufferView); + if (result != VK_SUCCESS || bufferView == VK_NULL_HANDLE) { + MGLOG_E("ResolveTexelBufferDescriptor: vkCreateBufferView failed result=%d format=%d range=%zu", + result, static_cast(vkFormat), static_cast(viewRange)); + return false; + } + + m_frames[frameIndex].texelBufferViews.push_back(bufferView); + outBufferView = bufferView; + return true; + } + SharedPtr UniformManager::GetFallbackTexture(TextureTarget target) const { MOBILEGL_ASSERT(target == TextureTarget::Texture2D || target == TextureTarget::TextureRectangle, "UniformManager::GetFallbackTexture: unsupported fallback target=%d", @@ -441,11 +534,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const Uint32 descriptorCount = static_cast(descriptorCount64); - VkDescriptorPoolSize poolSizes[2]{}; + VkDescriptorPoolSize poolSizes[3]{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; poolSizes[0].descriptorCount = descriptorCount; poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[1].descriptorCount = descriptorCount; + poolSizes[2].type = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; + poolSizes[2].descriptorCount = descriptorCount; VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; @@ -543,9 +638,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { writes.reserve(m_maxBindings); Vector bufferInfos; Vector imageInfos; + Vector texelBufferViews; Vector dynamicOffsets; bufferInfos.reserve(m_maxBindings); imageInfos.reserve(m_maxBindings); + texelBufferViews.reserve(m_maxBindings); dynamicOffsets.reserve(programObj.dynamicBindings.size()); const Uint32 bindingCount = @@ -589,6 +686,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { write.pBufferInfo = &bufferInfos.back(); writes.push_back(write); dynamicOffsets.push_back(static_cast(slice.offset)); + } else if (kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) { + VkBufferView bufferView = VK_NULL_HANDLE; + if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) || + bufferView == VK_NULL_HANDLE) { + MGLOG_E( + "UniformDescriptorBinder::BindProgramUniformBuffers failed: texture buffer binding %u has no valid descriptor", + binding); + return false; + } + + texelBufferViews.push_back(bufferView); + write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; + write.pTexelBufferView = &texelBufferViews.back(); + writes.push_back(write); } else { VkDescriptorImageInfo imageInfo{}; Bool hasImage = false; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 3070ce76..640c169c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -57,6 +57,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct FrameResources { Vector descriptorPools; + Vector texelBufferViews; Uint32 activeDescriptorPoolIndex = 0; Uint32 allocatedSetsThisFrame = 0; Uint32 peakAllocatedSetsThisFrame = 0; @@ -71,6 +72,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorImageInfo& outImageInfo) const; Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const; + Bool ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + Uint32 frameIndex, VkBufferView& outBufferView); Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, const void*& outData, VkDeviceSize& outSize) const; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index 69f13c68..3bb4e2cb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -80,7 +80,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { BufferSlice& outSlice) { const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind); MOBILEGL_ASSERT(requiredUsage != 0, - "VkBufferManager::SyncResidentBuffer only supports resident vertex/index buffers"); + "VkBufferManager::SyncResidentBuffer unsupported resident buffer kind"); MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::SyncResidentBuffer requires valid buffer object"); CollectResidentGarbageIfNeeded(); @@ -196,6 +196,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // never need to recreate a buffer after it has already been bound. return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; case BufferKind::Uniform: + return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + case BufferKind::TextureBuffer: + return VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; default: return 0; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index 096cdb90..32082393 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -19,6 +19,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vertex, Index, Uniform, + TextureBuffer, }; struct VkBufferManagerInitInfo { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index f43b4424..af2526e3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -43,6 +43,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 arrayLayers = 1; }; + static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) { + return target >= TextureUploadTarget::CubeMapPositiveX && + target <= TextureUploadTarget::CubeMapNegativeZ; + } + + static Uint32 ResolveUploadArrayLayer(TextureUploadTarget target) { + if (!IsCubeMapFaceUploadTarget(target)) { + return 0; + } + return static_cast(target) - static_cast(TextureUploadTarget::CubeMapPositiveX); + } + static Bool IsValidSampledImageLayout(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: @@ -174,7 +186,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool ok = VkTextureManager::TransitionImageLayout( commandBuffer, newResource.image, newResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, VK_ACCESS_TRANSFER_WRITE_BIT, newResource.aspect, 0, newResource.mipLevels); + 0, VK_ACCESS_TRANSFER_WRITE_BIT, newResource.aspect, 0, newResource.mipLevels, + newResource.arrayLayers); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare destination image"); VkImageLayout srcTrackedLayout = oldResource.layout; @@ -184,7 +197,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { ok = VkTextureManager::TransitionImageLayout( commandBuffer, oldResource.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, oldResource.aspect, 0, preservedMipLevels); + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, oldResource.aspect, 0, preservedMipLevels, + oldResource.arrayLayers); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare source image"); Vector copyRegions; @@ -216,7 +230,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { ok = VkTextureManager::TransitionImageLayout( commandBuffer, newResource.image, newResource.layout, oldResource.layout, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels); + VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels, + newResource.arrayLayers); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to restore destination layout"); VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture preserve)"); @@ -351,6 +366,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { outShape.viewType = VK_IMAGE_VIEW_TYPE_3D; outShape.depth = static_cast(texelSize.z()); return true; + case TextureUploadTarget::CubeMapPositiveX: + case TextureUploadTarget::CubeMapNegativeX: + case TextureUploadTarget::CubeMapPositiveY: + case TextureUploadTarget::CubeMapNegativeY: + case TextureUploadTarget::CubeMapPositiveZ: + case TextureUploadTarget::CubeMapNegativeZ: + case TextureUploadTarget::ProxyCubeMap: + MOBILEGL_ASSERT(texture.GetTarget() == TextureTarget::TextureCubeMap, + "TryResolveTextureShapeInfo: cube upload target on non-cube textureId=%d target=%s", + texture.GetExternalIndex(), + MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str()); + MOBILEGL_ASSERT(texelSize.x() == texelSize.y(), + "TryResolveTextureShapeInfo: cube map textureId=%d is not square (%d x %d)", + texture.GetExternalIndex(), texelSize.x(), texelSize.y()); + outShape.imageType = VK_IMAGE_TYPE_2D; + outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + outShape.depth = 1; + outShape.arrayLayers = 6; + return true; default: return false; } @@ -518,7 +553,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool lowerTransitioned = TransitionImageLayout( commandBuffer, resource.image, lowerMipLayout, newLayout, srcStageMask, dstStageMask, srcAccessMask, dstAccessMask, - resource.aspect, 0, writtenMipLevel); + resource.aspect, 0, writtenMipLevel, resource.arrayLayers); MOBILEGL_ASSERT(lowerTransitioned, "UpdateTrackedImageLayoutAfterAttachmentWrite: failed to transition lower mip levels for textureId=%d", texture->GetExternalIndex()); @@ -530,7 +565,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool upperTransitioned = TransitionImageLayout( commandBuffer, resource.image, upperMipLayout, newLayout, srcStageMask, dstStageMask, srcAccessMask, dstAccessMask, - resource.aspect, upperBaseMipLevel, resource.mipLevels - upperBaseMipLevel); + resource.aspect, upperBaseMipLevel, resource.mipLevels - upperBaseMipLevel, + resource.arrayLayers); MOBILEGL_ASSERT(upperTransitioned, "UpdateTrackedImageLayoutAfterAttachmentWrite: failed to transition upper mip levels for textureId=%d", texture->GetExternalIndex()); @@ -583,7 +619,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask, kGraphicsSampledReadStages, srcAccessMask, - VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels); + VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels, + resource->arrayLayers); MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex()); return ok; } @@ -592,7 +629,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageLayout& trackedLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, - VkImageAspectFlags aspectMask, Uint32 baseMipLevel, Uint32 levelCount) { + VkImageAspectFlags aspectMask, Uint32 baseMipLevel, Uint32 levelCount, + Uint32 layerCount) { MOBILEGL_ASSERT(image != VK_NULL_HANDLE, "TransitionImageLayout: m_image == VK_NULL_HANDLE"); MOBILEGL_ASSERT(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_BIT) != 0 && (dstStageMask & VK_PIPELINE_STAGE_TRANSFER_BIT) == 0), @@ -617,7 +655,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { barrier.subresourceRange.baseMipLevel = baseMipLevel; barrier.subresourceRange.levelCount = levelCount; barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.layerCount = layerCount; vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); trackedLayout = newLayout; @@ -666,10 +704,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } + Vector dirtyTargets; + if (outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { + dirtyTargets = mipTexture->GetUploadTargets(); + } else { + dirtyTargets.push_back(uploadTarget); + } Bool hasDirtyMipLevel = false; - for (Uint32 level = 0; level < mipLevelCount; ++level) { - if (mipTexture->IsStorageDirty(uploadTarget, level)) { - hasDirtyMipLevel = true; + for (const TextureUploadTarget target : dirtyTargets) { + const Uint32 targetMipLevelCount = std::min(mipLevelCount, GetUploadMipLevelCount(*mipTexture, target)); + for (Uint32 level = 0; level < targetMipLevelCount; ++level) { + if (mipTexture->IsStorageDirty(target, level)) { + hasDirtyMipLevel = true; + break; + } + } + if (hasDirtyMipLevel) { break; } } @@ -933,7 +983,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { TextureUploadTarget uploadTarget, TextureResource &outResource) { struct UploadItem { + TextureUploadTarget target = TextureUploadTarget::Unknown; Uint32 level = 0; + Uint32 baseArrayLayer = 0; SizeT uploadByteSize = 0; IntVec3 texelSize = {0, 0, 0}; const void* source = nullptr; @@ -942,52 +994,64 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; Vector uploadItems; - const Uint32 definedMipLevels = GetUploadMipLevelCount(mipmapTexture, uploadTarget); - MOBILEGL_ASSERT(definedMipLevels <= outResource.mipLevels, - "UploadDirtyMipLevels: defined mip level count %u exceeds backing mip level count %u for textureId=%d", - definedMipLevels, outResource.mipLevels, mipmapTexture.GetExternalIndex()); - - uploadItems.reserve(definedMipLevels); + Vector targets; + if (outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { + targets = mipmapTexture.GetUploadTargets(); + } else { + targets.push_back(uploadTarget); + } const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(mipmapTexture.GetFormat()); VkDeviceSize stagingSize = 0; - for (Uint32 level = 0; level < definedMipLevels; ++level) { - if (!mipmapTexture.IsStorageDirty(uploadTarget, level)) { - continue; - } + for (const TextureUploadTarget target : targets) { + const Uint32 definedMipLevels = GetUploadMipLevelCount(mipmapTexture, target); + MOBILEGL_ASSERT(definedMipLevels <= outResource.mipLevels, + "UploadDirtyMipLevels: defined mip level count %u exceeds backing mip level count %u for textureId=%d target=%s", + definedMipLevels, outResource.mipLevels, mipmapTexture.GetExternalIndex(), + MG_Util::ConvertTextureUploadTargetToString(target).c_str()); - const auto texelSize = mipmapTexture.GetMipmapTexelSize(uploadTarget, level); - const auto byteSize = mipmapTexture.GetMipmapByteSize(uploadTarget, level); - if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) { - mipmapTexture.MarkStorageDirty(uploadTarget, level, false); - continue; - } + for (Uint32 level = 0; level < definedMipLevels; ++level) { + if (!mipmapTexture.IsStorageDirty(target, level)) { + continue; + } - const void* source = mipmapTexture.MapMipmapData(uploadTarget, level); - if (source == nullptr) { - MGLOG_D("%s: MapmipmapData failed at level %d", __func__, level); - return false; - } + const auto texelSize = mipmapTexture.GetMipmapTexelSize(target, level); + const auto byteSize = mipmapTexture.GetMipmapByteSize(target, level); + if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) { + mipmapTexture.MarkStorageDirty(target, level, false); + continue; + } - UploadItem uploadItem{}; - uploadItem.level = level; - uploadItem.texelSize = texelSize; - uploadItem.source = source; - uploadItem.offset = stagingSize; - uploadItem.uploadByteSize = byteSize; - if (formatInfo.expandRgbToRgba) { - const Bool expanded = ExpandRgbSourceToRgba(source, byteSize, texelSize, formatInfo, - uploadItem.expandedData); - MOBILEGL_ASSERT(expanded, - "UploadDirtyMipLevels: failed to expand RGB textureId=%d level=%u to RGBA staging data", - mipmapTexture.GetExternalIndex(), level); - uploadItem.uploadByteSize = uploadItem.expandedData.size(); + const void* source = mipmapTexture.MapMipmapData(target, level); + if (source == nullptr) { + MGLOG_D("%s: MapmipmapData failed at target %s level %d", __func__, + MG_Util::ConvertTextureUploadTargetToString(target).c_str(), level); + return false; + } + + UploadItem uploadItem{}; + uploadItem.target = target; + uploadItem.level = level; + uploadItem.baseArrayLayer = ResolveUploadArrayLayer(target); + uploadItem.texelSize = texelSize; + uploadItem.source = source; + uploadItem.offset = stagingSize; + uploadItem.uploadByteSize = byteSize; + if (formatInfo.expandRgbToRgba) { + const Bool expanded = ExpandRgbSourceToRgba(source, byteSize, texelSize, formatInfo, + uploadItem.expandedData); + MOBILEGL_ASSERT(expanded, + "UploadDirtyMipLevels: failed to expand RGB textureId=%d target=%s level=%u to RGBA staging data", + mipmapTexture.GetExternalIndex(), + MG_Util::ConvertTextureUploadTargetToString(target).c_str(), level); + uploadItem.uploadByteSize = uploadItem.expandedData.size(); + } + uploadItems.push_back(Move(uploadItem)); + if (!uploadItems.back().expandedData.empty()) { + uploadItems.back().source = uploadItems.back().expandedData.data(); + } + stagingSize += static_cast(uploadItems.back().uploadByteSize); } - uploadItems.push_back(Move(uploadItem)); - if (!uploadItems.back().expandedData.empty()) { - uploadItems.back().source = uploadItems.back().expandedData.data(); - } - stagingSize += static_cast(uploadItems.back().uploadByteSize); } if (uploadItems.empty()) { @@ -1034,13 +1098,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool ok = TransitionImageLayout(commandBuffer, outResource.image, outResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? - kGraphicsSampledReadStages : - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0, - VK_ACCESS_TRANSFER_WRITE_BIT, - aspectMask, 0, outResource.mipLevels); + outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? + kGraphicsSampledReadStages : + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0, + VK_ACCESS_TRANSFER_WRITE_BIT, + aspectMask, 0, outResource.mipLevels, outResource.arrayLayers); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed"); for (const auto& item : uploadItems) { @@ -1050,7 +1114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { copy.bufferImageHeight = 0; copy.imageSubresource.aspectMask = aspectMask; copy.imageSubresource.mipLevel = item.level; - copy.imageSubresource.baseArrayLayer = 0; + copy.imageSubresource.baseArrayLayer = item.baseArrayLayer; copy.imageSubresource.layerCount = 1; copy.imageOffset = {0, 0, 0}; copy.imageExtent = {static_cast(item.texelSize.x()), static_cast(item.texelSize.y()), @@ -1066,7 +1130,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { kGraphicsSampledReadStages, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - aspectMask, 0, outResource.mipLevels); + aspectMask, 0, outResource.mipLevels, outResource.arrayLayers); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL failed"); outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; @@ -1094,7 +1158,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } for (const auto& item : uploadItems) { - mipmapTexture.MarkStorageDirty(uploadTarget, item.level, false); + mipmapTexture.MarkStorageDirty(item.target, item.level, false); } outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; return true; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 37d9a16f..23fd86fc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -130,7 +130,8 @@ public: VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask, - Uint32 baseMipLevel = 0, Uint32 levelCount = 1); + Uint32 baseMipLevel = 0, Uint32 levelCount = 1, + Uint32 layerCount = 1); SizeT CollectGarbage(); private: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 047c1941..f93ba4d7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -332,12 +332,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - static Bool HasTransientVertexIndexBufferThisFrame( - const Vector& buffers, - const MG_State::GLState::BufferObject* buffer) { - return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end(); - } - static Uint32 BuildVertexInputAttributeMask(const Vector& attributes) { Uint32 attributeMask = 0; for (const auto& attribute : attributes) { @@ -1218,7 +1212,7 @@ void main() { "Initialize, WaitAndAcquireNextImage"); m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); - m_transientVertexIndexBuffersThisFrame.clear(); + m_transientVertexIndexBufferSlicesThisFrame.clear(); MGLOG_D("VulkanRenderer initialized"); } @@ -1241,7 +1235,7 @@ void main() { } m_vertexInputStateFactory.reset(); m_bufferManager.Shutdown(); - m_transientVertexIndexBuffersThisFrame.clear(); + m_transientVertexIndexBufferSlicesThisFrame.clear(); m_frameContext.Destroy(m_device, m_commandPool); @@ -1328,10 +1322,11 @@ void main() { MOBILEGL_ASSERT(sourceBufferShared != nullptr, "UploadAndBindVertexStreams failed to resolve shared source buffer"); BufferSlice slice{}; - const Bool transientThisFrame = - HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, sourceBufferShared.get()); const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit); - if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || transientThisFrame || isDirty) { + auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(sourceBufferShared.get()); + if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty) { + slice = cachedTransient->second; + } else if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || isDirty) { const auto sourceData = sourceBufferShared->GetDataReadOnly(); const SizeT sourceSize = sourceBufferShared->GetSize(); if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), @@ -1340,9 +1335,7 @@ void main() { MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding); return false; } - if (!transientThisFrame) { - m_transientVertexIndexBuffersThisFrame.push_back(sourceBufferShared.get()); - } + m_transientVertexIndexBufferSlicesThisFrame[sourceBufferShared.get()] = slice; m_bufferManager.DowngradeResidentBufferToTransient(sourceBufferShared); sourceBufferShared->ClearDirty(); } else { @@ -1421,24 +1414,33 @@ void main() { BufferSlice slice{}; auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex()); MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO"); - const Bool transientThisFrame = - HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, indexBufferShared.get()); const Bool isDirty = (indexBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit); - if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared) || transientThisFrame || isDirty) { + auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(indexBufferShared.get()); + if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty) { + slice = cachedTransient->second; + vkCmdBindIndexBuffer(frame.commandBuffer, + slice.buffer, + slice.offset + static_cast(pIndexBufferView->indexByteOffset), + vkIndexType); + return true; + } + if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared) || isDirty) { const auto indexData = indexBufferShared->GetDataReadOnly(); MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), - indexData->data() + pIndexBufferView->indexByteOffset, - static_cast(indexDataSizeBytes), indexSize, slice)) { + indexData->data(), + static_cast(indexBufferShared->GetSize()), indexSize, + slice)) { MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer"); return false; } - if (!transientThisFrame) { - m_transientVertexIndexBuffersThisFrame.push_back(indexBufferShared.get()); - } + m_transientVertexIndexBufferSlicesThisFrame[indexBufferShared.get()] = slice; m_bufferManager.DowngradeResidentBufferToTransient(indexBufferShared); indexBufferShared->ClearDirty(); - vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, vkIndexType); + vkCmdBindIndexBuffer(frame.commandBuffer, + slice.buffer, + slice.offset + static_cast(pIndexBufferView->indexByteOffset), + vkIndexType); return true; } if (!m_bufferManager.SyncResidentBuffer(BufferKind::Index, indexBufferShared, slice)) { @@ -3576,7 +3578,7 @@ void main() { CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex()); m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); - m_transientVertexIndexBuffersThisFrame.clear(); + m_transientVertexIndexBufferSlicesThisFrame.clear(); } void VulkanRenderer::CreateInstance() { @@ -4178,7 +4180,7 @@ void main() { m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); } m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); - m_transientVertexIndexBuffersThisFrame.clear(); + m_transientVertexIndexBufferSlicesThisFrame.clear(); } } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 9c4d39d3..57ac30e8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -198,7 +198,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkCommandPool m_commandPool = VK_NULL_HANDLE; VkBufferManager m_bufferManager; - Vector m_transientVertexIndexBuffersThisFrame; + UnorderedMap m_transientVertexIndexBufferSlicesThisFrame; Uint m_imageIndexAcquired = 0; FrameContext m_frameContext;