diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp index 5da047ff..4b8c9244 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp @@ -8,6 +8,7 @@ #include "UniformDescriptorBinder.h" +#include "VkFramebufferManager.h" #include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_Util/ShaderTranspiler/Types.h" @@ -82,10 +83,62 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + TextureTarget UniformDescriptorBinder::UniformTypeToTextureTarget(GLenum glType) { + switch (glType) { + case GL_SAMPLER_1D: + case GL_INT_SAMPLER_1D: + case GL_UNSIGNED_INT_SAMPLER_1D: + return TextureTarget::Texture1D; + case GL_SAMPLER_3D: + case GL_INT_SAMPLER_3D: + case GL_UNSIGNED_INT_SAMPLER_3D: + return TextureTarget::Texture3D; + case GL_SAMPLER_CUBE: + case GL_SAMPLER_CUBE_SHADOW: + case GL_INT_SAMPLER_CUBE: + case GL_UNSIGNED_INT_SAMPLER_CUBE: + return TextureTarget::TextureCubeMap; + case GL_SAMPLER_2D_MULTISAMPLE: + case GL_INT_SAMPLER_2D_MULTISAMPLE: + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: + return TextureTarget::Texture2DMultisample; + case GL_SAMPLER_BUFFER: + case GL_INT_SAMPLER_BUFFER: + case GL_UNSIGNED_INT_SAMPLER_BUFFER: + return TextureTarget::TextureBuffer; + case GL_SAMPLER_1D_ARRAY: + case GL_SAMPLER_1D_ARRAY_SHADOW: + case GL_INT_SAMPLER_1D_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: + return TextureTarget::Texture1DArray; + case GL_SAMPLER_2D_ARRAY: + case GL_SAMPLER_2D_ARRAY_SHADOW: + case GL_INT_SAMPLER_2D_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return TextureTarget::Texture2DArray; + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + return TextureTarget::Texture2DMultisampleArray; + case GL_SAMPLER_2D_RECT: + case GL_SAMPLER_2D_RECT_SHADOW: + case GL_INT_SAMPLER_2D_RECT: + case GL_UNSIGNED_INT_SAMPLER_2D_RECT: + return TextureTarget::TextureRectangle; + case GL_SAMPLER_2D: + case GL_SAMPLER_2D_SHADOW: + case GL_INT_SAMPLER_2D: + case GL_UNSIGNED_INT_SAMPLER_2D: + default: + return TextureTarget::Texture2D; + } + } + Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes, - VkTextureSamplerManager* textureSamplerManager) { + VkTextureSamplerManager* textureSamplerManager, + VkFramebufferManager* framebufferManager) { Shutdown(); MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice"); @@ -102,6 +155,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_maxBindings = maxBindings; m_setsPerFrame = setsPerFrame; m_textureSamplerManager = textureSamplerManager; + m_framebufferManager = framebufferManager; m_frames.resize(m_frameCount); for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) { @@ -156,6 +210,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_maxBindings = 0; m_setsPerFrame = 0; m_textureSamplerManager = nullptr; + m_framebufferManager = nullptr; } void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) { @@ -232,6 +287,130 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Bool UniformDescriptorBinder::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, + ProgramLayout& layout) const { + layout.samplerUniformLocationByBinding.assign(m_maxBindings, -1); + layout.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D); + + const auto& spirv = program.GetGeneratedSpirv(); + for (const auto& module : spirv) { + if (module.empty()) { + continue; + } + + spvc_context context = nullptr; + spvc_parsed_ir ir = nullptr; + spvc_compiler compiler = nullptr; + spvc_resources resources = nullptr; + + if (spvc_context_create(&context) != SPVC_SUCCESS) { + return false; + } + if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) { + spvc_context_destroy(context); + continue; + } + if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, + &compiler) != SPVC_SUCCESS) { + spvc_context_destroy(context); + continue; + } + if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) { + spvc_context_destroy(context); + continue; + } + + const spvc_reflected_resource* list = nullptr; + size_t count = 0; + if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, &list, &count) == + SPVC_SUCCESS) { + for (size_t i = 0; i < count; ++i) { + const Uint32 binding = + spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding); + if (binding >= m_maxBindings) { + continue; + } + + String uniformName = list[i].name ? list[i].name : ""; + Int location = program.GetUniformLocation(uniformName); + if (location < 0) { + const auto arraySuffix = uniformName.find("[0]"); + if (arraySuffix != String::npos) { + uniformName = uniformName.substr(0, arraySuffix); + location = program.GetUniformLocation(uniformName); + } + } + if (location < 0) { + continue; + } + + layout.samplerUniformLocationByBinding[binding] = location; + layout.samplerTextureTargetByBinding[binding] = + UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); + } + } + + spvc_context_destroy(context); + } + + return true; + } + + Bool UniformDescriptorBinder::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, + const MG_State::GLState::ProgramObject& program, + const ProgramLayout& layout, Uint32 binding, + VkDescriptorImageInfo& outImageInfo) const { + if (!m_textureSamplerManager || !MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) { + return false; + } + + const Int location = layout.samplerUniformLocationByBinding[binding]; + if (location < 0) { + return false; + } + + const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + if (unit < 0) { + return false; + } + + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); + const auto samplerOverride = textureUnit.GetSamplerObject(); + + const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding]; + auto texture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); + if (!texture) { + auto& slots = textureUnit.GetAllBindingSlots(); + for (auto& slot : slots) { + texture = slot.GetBoundObject(); + if (texture) { + break; + } + } + } + if (!texture) { + return false; + } + + if (m_framebufferManager && + m_framebufferManager->TransitionOffscreenColorTextureToShaderRead(commandBuffer, texture->GetExternalIndex())) { + VkImageView offscreenView = VK_NULL_HANDLE; + if (m_framebufferManager->GetOffscreenColorViewByTexture(texture->GetExternalIndex(), offscreenView) && + offscreenView != VK_NULL_HANDLE) { + VkDescriptorImageInfo sampledInfo{}; + if (!m_textureSamplerManager->SyncTextureAndGetDescriptor(*texture, samplerOverride.get(), sampledInfo)) { + return false; + } + sampledInfo.imageView = offscreenView; + sampledInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + outImageInfo = sampledInfo; + return true; + } + } + + return m_textureSamplerManager->SyncTextureAndGetDescriptor(*texture, samplerOverride.get(), outImageInfo); + } + UniformDescriptorBinder::ProgramLayout* UniformDescriptorBinder::GetOrCreateProgramLayout( const MG_State::GLState::ProgramObject& program) { const Uint64 hash = ComputeProgramHash(program); @@ -246,6 +425,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: reflection failed"); return nullptr; } + if (!ReflectSamplerBindings(program, layout)) { + MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: sampler reflection failed"); + return nullptr; + } Vector bindings; bindings.reserve(m_maxBindings); @@ -476,11 +659,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { writes.push_back(write); dynamicOffsets.push_back(static_cast(payloadOffset)); } else { - if (!hasFallbackImage) { + VkDescriptorImageInfo imageInfo{}; + Bool hasImage = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo); + if (!hasImage) { + if (!hasFallbackImage) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: fallback sampler/texture is unavailable"); + return false; + } + imageInfo = fallbackImageInfo; + } + if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) { MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: fallback sampler/texture is unavailable"); return false; } - imageInfos.push_back(fallbackImageInfo); + imageInfos.push_back(imageInfo); write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &imageInfos.back(); writes.push_back(write); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h index 06ce550d..debf530a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h @@ -19,6 +19,8 @@ namespace MobileGL::MG_State::GLState { } namespace MobileGL::MG_Backend::DirectVulkan { + class VkFramebufferManager; + class UniformDescriptorBinder { public: enum class BindingKind : Uint8 { @@ -30,7 +32,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024, - VkTextureSamplerManager* textureSamplerManager = nullptr); + VkTextureSamplerManager* textureSamplerManager = nullptr, + VkFramebufferManager* framebufferManager = nullptr); void Shutdown(); void BeginFrame(Uint32 frameIndex); @@ -51,11 +54,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; Vector bindingKinds; Vector dynamicBindings; + Vector samplerUniformLocationByBinding; + Vector samplerTextureTargetByBinding; }; static VkDeviceSize AlignUp(VkDeviceSize value, VkDeviceSize alignment); static Uint64 ComputeProgramHash(const MG_State::GLState::ProgramObject& program); static Bool IsSamplerUniformType(GLenum glType); + static TextureTarget UniformTypeToTextureTarget(GLenum glType); + Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const; + Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, + const ProgramLayout& layout, Uint32 binding, + VkDescriptorImageInfo& outImageInfo) const; Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, Vector& outKinds) const; ProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program); Bool AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset); @@ -74,6 +84,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 m_maxBindings = 0; Uint32 m_setsPerFrame = 0; VkTextureSamplerManager* m_textureSamplerManager = nullptr; + VkFramebufferManager* m_framebufferManager = nullptr; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp index 83f19a6c..5f2c46f9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp @@ -107,6 +107,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_IMAGE_ASPECT_COLOR_BIT); } + Bool VkFramebufferManager::TransitionOffscreenColorTextureToShaderRead(VkCommandBuffer commandBuffer, + Uint textureExternalIndex) { + for (auto& [_, target] : m_offscreenColorTargets) { + if (target.colorTextureExternalIndex != textureExternalIndex || target.image == VK_NULL_HANDLE) { + continue; + } + const Bool fromUndefined = (target.layout == VK_IMAGE_LAYOUT_UNDEFINED); + return TransitionImageLayout(commandBuffer, + target.image, + target.layout, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + fromUndefined ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT + : (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT), + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + fromUndefined ? 0 + : (VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT), + VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + return false; + } + Bool VkFramebufferManager::GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const { auto it = m_offscreenColorTargets.find(glFboExternalIndex); @@ -118,6 +142,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Bool VkFramebufferManager::GetOffscreenColorViewByTexture(Uint textureExternalIndex, VkImageView& outImageView) const { + for (const auto& [_, target] : m_offscreenColorTargets) { + if (target.colorTextureExternalIndex != textureExternalIndex || target.imageView == VK_NULL_HANDLE) { + continue; + } + outImageView = target.imageView; + return true; + } + return false; + } + Bool VkFramebufferManager::GetOffscreenRenderTarget(Uint glFboExternalIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer, VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const { @@ -162,7 +197,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &target.image), "vkCreateImage(offscreen color)"); @@ -329,6 +365,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED; target.depthStencilFormat = depthStencilFormat; target.glObjectVersion = glObjectVersion; + target.colorTextureExternalIndex = + (colorAttachment.IsTexture() && colorAttachment.GetTexture()) ? colorAttachment.GetTexture()->GetExternalIndex() + : 0; return true; } @@ -371,6 +410,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { target.format = VK_FORMAT_UNDEFINED; target.depthStencilFormat = VK_FORMAT_UNDEFINED; target.glObjectVersion = 0; + target.colorTextureExternalIndex = 0; } Bool VkFramebufferManager::TransitionImageLayout(VkCommandBuffer commandBuffer, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h index ac3bb3a1..da977278 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h @@ -29,7 +29,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo); Bool TransitionOffscreenColorToAttachment(VkCommandBuffer commandBuffer, Uint glFboExternalIndex); Bool TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex); + Bool TransitionOffscreenColorTextureToShaderRead(VkCommandBuffer commandBuffer, Uint textureExternalIndex); Bool GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const; + Bool GetOffscreenColorViewByTexture(Uint textureExternalIndex, VkImageView& outImageView) const; Bool GetOffscreenRenderTarget(Uint glFboExternalIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer, VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const; @@ -49,6 +51,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkRenderPass renderPassLoad = VK_NULL_HANDLE; VkFramebuffer framebuffer = VK_NULL_HANDLE; Uint16 glObjectVersion = 0; + Uint colorTextureExternalIndex = 0; }; Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp index 46e7325e..221f0ace 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.cpp @@ -8,9 +8,18 @@ #include "VkTextureSamplerManager.h" +#include "MG_State/GLState/Core.h" + namespace MobileGL::MG_Backend::DirectVulkan { + namespace { + constexpr Uint64 BuildSamplerKey(Uint externalIndex, Uint16 version) { + return (static_cast(externalIndex) << 16) | static_cast(version); + } + } // namespace + Bool VkTextureSamplerManager::Initialize(const InitInfo& initInfo) { Shutdown(); + m_device = initInfo.device; m_physicalDevice = initInfo.physicalDevice; m_commandPool = initInfo.commandPool; @@ -18,74 +27,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_device == VK_NULL_HANDLE || m_physicalDevice == VK_NULL_HANDLE || m_commandPool == VK_NULL_HANDLE || m_graphicsQueue == VK_NULL_HANDLE) { - MGLOG_E("VkTextureSamplerManager::Initialize failed: invalid init handles"); + MGLOG_E("VkTextureSamplerManager::Initialize failed: invalid Vulkan handles"); Shutdown(); return false; } - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = 1; - imageInfo.extent.height = 1; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &m_fallbackImage), - "VkTextureSamplerManager::Initialize, vkCreateImage"); - - VkMemoryRequirements memoryRequirements{}; - vkGetImageMemoryRequirements(m_device, m_fallbackImage, &memoryRequirements); - - VkMemoryAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.allocationSize = memoryRequirements.size; - allocInfo.memoryTypeIndex = FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &m_fallbackImageMemory), - "VkTextureSamplerManager::Initialize, vkAllocateMemory"); - VK_VERIFY(vkBindImageMemory(m_device, m_fallbackImage, m_fallbackImageMemory, 0), - "VkTextureSamplerManager::Initialize, vkBindImageMemory"); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = m_fallbackImage; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &m_fallbackImageView), - "VkTextureSamplerManager::Initialize, vkCreateImageView"); - - VkSamplerCreateInfo samplerInfo{}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = VK_FILTER_LINEAR; - samplerInfo.minFilter = VK_FILTER_LINEAR; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 0.0f; - samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; - samplerInfo.unnormalizedCoordinates = VK_FALSE; - VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &m_fallbackSampler), - "VkTextureSamplerManager::Initialize, vkCreateSampler"); - if (!UploadFallbackTexture()) { + MGLOG_E("VkTextureSamplerManager::Initialize failed: fallback texture creation failed"); Shutdown(); return false; } @@ -93,24 +41,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkTextureSamplerManager::Shutdown() { + for (auto& [_, resource] : m_textureResources) { + DestroyTextureResource(resource); + } + m_textureResources.clear(); + + for (auto& [_, sampler] : m_samplers) { + if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) { + vkDestroySampler(m_device, sampler.handle, nullptr); + } + sampler.handle = VK_NULL_HANDLE; + } + m_samplers.clear(); + if (m_device != VK_NULL_HANDLE && m_fallbackSampler != VK_NULL_HANDLE) { vkDestroySampler(m_device, m_fallbackSampler, nullptr); } - m_fallbackSampler = VK_NULL_HANDLE; - if (m_device != VK_NULL_HANDLE && m_fallbackImageView != VK_NULL_HANDLE) { vkDestroyImageView(m_device, m_fallbackImageView, nullptr); } - m_fallbackImageView = VK_NULL_HANDLE; - if (m_device != VK_NULL_HANDLE && m_fallbackImage != VK_NULL_HANDLE) { vkDestroyImage(m_device, m_fallbackImage, nullptr); } - m_fallbackImage = VK_NULL_HANDLE; - if (m_device != VK_NULL_HANDLE && m_fallbackImageMemory != VK_NULL_HANDLE) { vkFreeMemory(m_device, m_fallbackImageMemory, nullptr); } + m_fallbackSampler = VK_NULL_HANDLE; + m_fallbackImageView = VK_NULL_HANDLE; + m_fallbackImage = VK_NULL_HANDLE; m_fallbackImageMemory = VK_NULL_HANDLE; m_device = VK_NULL_HANDLE; @@ -129,20 +87,247 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Uint32 VkTextureSamplerManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const { - VkPhysicalDeviceMemoryProperties memoryProperties{}; - vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties); - for (Uint32 i = 0; i < memoryProperties.memoryTypeCount; ++i) { - if ((typeFilter & (1U << i)) && - (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; + Bool VkTextureSamplerManager::SyncTextureAndGetDescriptor(const MG_State::GLState::ITextureObject& texture, + const MG_State::GLState::SamplerObject* samplerOverride, + VkDescriptorImageInfo& outImageInfo) { + if (m_device == VK_NULL_HANDLE) { + return GetFallbackDescriptor(outImageInfo); + } + + auto it = m_textureResources.find(texture.GetExternalIndex()); + if (it == m_textureResources.end()) { + TextureResource initial{}; + initial.textureExternalIndex = texture.GetExternalIndex(); + auto [insertIt, _] = m_textureResources.emplace(texture.GetExternalIndex(), initial); + it = insertIt; + } + + if (!EnsureTextureSynced(it->second, texture)) { + return GetFallbackDescriptor(outImageInfo); + } + + const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride; + if (!samplerToUse) { + auto textureSampler = texture.GetSamplerObject(); + if (textureSampler) { + samplerToUse = textureSampler.get(); } } - MOBILEGL_ASSERT(false, "VkTextureSamplerManager::FindMemoryType failed"); - return 0; + + VkSampler sampler = m_fallbackSampler; + if (samplerToUse) { + sampler = GetOrCreateSampler(*samplerToUse); + } + if (sampler == VK_NULL_HANDLE) { + sampler = m_fallbackSampler; + } + + if (it->second.view == VK_NULL_HANDLE || sampler == VK_NULL_HANDLE) { + return GetFallbackDescriptor(outImageInfo); + } + + outImageInfo.sampler = sampler; + outImageInfo.imageView = it->second.view; + outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + return true; } - Bool VkTextureSamplerManager::UploadFallbackTexture() { + Bool VkTextureSamplerManager::EnsureTextureSynced(TextureResource& resource, + const MG_State::GLState::ITextureObject& texture) { + TextureUploadTarget level0Target = TextureUploadTarget::Unknown; + IntVec3 texelSize{0, 0, 0}; + SizeT byteSize = 0; + if (!ResolveLevel0(texture, level0Target, texelSize, byteSize)) { + return false; + } + + if (!EnsureTextureResource(resource, texture, level0Target, texelSize, byteSize)) { + return false; + } + + const auto* mipTexture = dynamic_cast(&texture); + if (!mipTexture) { + return false; + } + + if (!mipTexture->IsStorageDirty(level0Target, 0)) { + return true; + } + + if (!UploadLevel0(resource, *mipTexture, level0Target, byteSize)) { + return false; + } + + auto& mutableTexture = const_cast(*mipTexture); + mutableTexture.MarkStorageDirty(level0Target, 0, false); + return true; + } + + Bool VkTextureSamplerManager::EnsureTextureResource(TextureResource& resource, + const MG_State::GLState::ITextureObject& texture, + TextureUploadTarget level0Target, const IntVec3& texelSize, + SizeT byteSize) { + const VkFormat format = ResolveTextureFormat(texture.GetFormat()); + if (format == VK_FORMAT_UNDEFINED) { + return false; + } + if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) { + return false; + } + if (level0Target != TextureUploadTarget::Texture2D) { + return false; + } + + const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format && + resource.extent.width == static_cast(texelSize.x()) && + resource.extent.height == static_cast(texelSize.y()); + if (compatible) { + return true; + } + + DestroyTextureResource(resource); + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = static_cast(texelSize.x()); + imageInfo.extent.height = static_cast(texelSize.y()); + 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_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &resource.image), "vkCreateImage(texture)"); + + VkMemoryRequirements requirements{}; + vkGetImageMemoryRequirements(m_device, resource.image, &requirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = requirements.size; + allocInfo.memoryTypeIndex = FindMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &resource.memory), "vkAllocateMemory(texture)"); + VK_VERIFY(vkBindImageMemory(m_device, resource.image, resource.memory, 0), "vkBindImageMemory(texture)"); + + 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.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + 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(texture)"); + + resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; + resource.extent = {static_cast(texelSize.x()), static_cast(texelSize.y())}; + resource.format = format; + resource.textureExternalIndex = texture.GetExternalIndex(); + return true; + } + + Bool VkTextureSamplerManager::UploadLevel0(TextureResource& resource, + const MG_State::GLState::TextureObjectMipmap& mipmapTexture, + TextureUploadTarget level0Target, SizeT byteSize) { + auto& mutableTexture = const_cast(mipmapTexture); + const void* source = mutableTexture.MapMipmapData(level0Target, 0); + if (source == nullptr || byteSize == 0) { + return false; + } + + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VkDeviceMemory stagingMemory = VK_NULL_HANDLE; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = byteSize; + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer), "vkCreateBuffer(staging texture)"); + + VkMemoryRequirements requirements{}; + vkGetBufferMemoryRequirements(m_device, stagingBuffer, &requirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = requirements.size; + allocInfo.memoryTypeIndex = + FindMemoryType(requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &stagingMemory), "vkAllocateMemory(staging texture)"); + VK_VERIFY(vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0), "vkBindBufferMemory(staging texture)"); + + void* mapped = nullptr; + VK_VERIFY(vkMapMemory(m_device, stagingMemory, 0, byteSize, 0, &mapped), "vkMapMemory(staging texture)"); + std::memcpy(mapped, source, byteSize); + vkUnmapMemory(m_device, stagingMemory); + + const Bool ok = ExecuteImmediate([&](VkCommandBuffer commandBuffer) { + VkImageMemoryBarrier toTransferDst{}; + toTransferDst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toTransferDst.srcAccessMask = 0; + toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toTransferDst.oldLayout = resource.layout; + toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransferDst.image = resource.image; + toTransferDst.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toTransferDst.subresourceRange.baseMipLevel = 0; + toTransferDst.subresourceRange.levelCount = 1; + toTransferDst.subresourceRange.baseArrayLayer = 0; + toTransferDst.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, + nullptr, 0, nullptr, 1, &toTransferDst); + + VkBufferImageCopy copy{}; + copy.bufferOffset = 0; + copy.bufferRowLength = 0; + copy.bufferImageHeight = 0; + copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.imageSubresource.mipLevel = 0; + copy.imageSubresource.baseArrayLayer = 0; + copy.imageSubresource.layerCount = 1; + copy.imageOffset = {0, 0, 0}; + copy.imageExtent = {resource.extent.width, resource.extent.height, 1}; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, resource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ©); + + VkImageMemoryBarrier toSampled{}; + toSampled.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toSampled.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toSampled.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + toSampled.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toSampled.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + toSampled.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toSampled.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toSampled.image = resource.image; + toSampled.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toSampled.subresourceRange.baseMipLevel = 0; + toSampled.subresourceRange.levelCount = 1; + toSampled.subresourceRange.baseArrayLayer = 0; + toSampled.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, 0, nullptr, 1, &toSampled); + }); + + vkDestroyBuffer(m_device, stagingBuffer, nullptr); + vkFreeMemory(m_device, stagingMemory, nullptr); + + if (!ok) { + return false; + } + resource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + return true; + } + + Bool VkTextureSamplerManager::ExecuteImmediate(const std::function& recorder) const { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = m_commandPool; @@ -150,73 +335,310 @@ namespace MobileGL::MG_Backend::DirectVulkan { allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer = VK_NULL_HANDLE; - VK_VERIFY(vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer), - "VkTextureSamplerManager::UploadFallbackTexture, vkAllocateCommandBuffers"); + VK_VERIFY(vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer), "vkAllocateCommandBuffers(texture)"); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), - "VkTextureSamplerManager::UploadFallbackTexture, vkBeginCommandBuffer"); + VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture)"); - VkImageMemoryBarrier toTransfer{}; - toTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - toTransfer.srcAccessMask = 0; - toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - toTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - toTransfer.image = m_fallbackImage; - toTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - toTransfer.subresourceRange.baseMipLevel = 0; - toTransfer.subresourceRange.levelCount = 1; - toTransfer.subresourceRange.baseArrayLayer = 0; - toTransfer.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, - nullptr, 0, nullptr, 1, &toTransfer); + recorder(commandBuffer); - VkClearColorValue clearColor{}; - clearColor.float32[0] = 1.0f; - clearColor.float32[1] = 1.0f; - clearColor.float32[2] = 1.0f; - clearColor.float32[3] = 1.0f; - VkImageSubresourceRange clearRange{}; - clearRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - clearRange.baseMipLevel = 0; - clearRange.levelCount = 1; - clearRange.baseArrayLayer = 0; - clearRange.layerCount = 1; - vkCmdClearColorImage(commandBuffer, m_fallbackImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearColor, 1, - &clearRange); - - VkImageMemoryBarrier toShaderRead{}; - toShaderRead.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - toShaderRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - toShaderRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - toShaderRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - toShaderRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - toShaderRead.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - toShaderRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - toShaderRead.image = m_fallbackImage; - toShaderRead.subresourceRange = clearRange; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, - 0, nullptr, 0, nullptr, 1, &toShaderRead); - - VK_VERIFY(vkEndCommandBuffer(commandBuffer), - "VkTextureSamplerManager::UploadFallbackTexture, vkEndCommandBuffer"); + VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture)"); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; - VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE), - "VkTextureSamplerManager::UploadFallbackTexture, vkQueueSubmit"); - VK_VERIFY(vkQueueWaitIdle(m_graphicsQueue), - "VkTextureSamplerManager::UploadFallbackTexture, vkQueueWaitIdle"); + VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE), "vkQueueSubmit(texture)"); + VK_VERIFY(vkQueueWaitIdle(m_graphicsQueue), "vkQueueWaitIdle(texture)"); vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer); return true; } -} // namespace MobileGL::MG_Backend::DirectVulkan + void VkTextureSamplerManager::DestroyTextureResource(TextureResource& resource) const { + if (m_device != VK_NULL_HANDLE && resource.view != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, resource.view, nullptr); + } + if (m_device != VK_NULL_HANDLE && resource.image != VK_NULL_HANDLE) { + vkDestroyImage(m_device, resource.image, nullptr); + } + if (m_device != VK_NULL_HANDLE && resource.memory != VK_NULL_HANDLE) { + vkFreeMemory(m_device, resource.memory, nullptr); + } + resource.view = VK_NULL_HANDLE; + resource.image = VK_NULL_HANDLE; + resource.memory = VK_NULL_HANDLE; + resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; + resource.extent = {0, 0}; + resource.format = VK_FORMAT_UNDEFINED; + } + + Bool VkTextureSamplerManager::ResolveLevel0(const MG_State::GLState::ITextureObject& texture, + TextureUploadTarget& outTarget, IntVec3& outTexelSize, + SizeT& outByteSize) { + const auto* mipTexture = dynamic_cast(&texture); + if (!mipTexture) { + return false; + } + const auto& targets = texture.GetUploadTargets(); + if (targets.empty()) { + return false; + } + outTarget = targets.front(); + outTexelSize = mipTexture->GetMipmapTexelSize(outTarget, 0); + outByteSize = mipTexture->GetMipmapByteSize(outTarget, 0); + return outTexelSize.x() > 0 && outTexelSize.y() > 0 && outByteSize > 0; + } + + VkFormat VkTextureSamplerManager::ResolveTextureFormat(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::RGBA: + case TextureInternalFormat::RGBA8: + return VK_FORMAT_R8G8B8A8_UNORM; + case TextureInternalFormat::SRGB8Alpha8: + return VK_FORMAT_R8G8B8A8_SRGB; + default: + return VK_FORMAT_UNDEFINED; + } + } + + Uint32 VkTextureSamplerManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const { + VkPhysicalDeviceMemoryProperties memProperties{}; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties); + for (Uint32 i = 0; i < memProperties.memoryTypeCount; ++i) { + if ((typeFilter & (1u << i)) != 0 && + (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + MOBILEGL_ASSERT(false, "VkTextureSamplerManager::FindMemoryType failed"); + return 0; + } + + VkSampler VkTextureSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler) { + const Uint64 key = BuildSamplerKey(sampler.GetExternalIndex(), sampler.GetVersion()); + auto it = m_samplers.find(key); + if (it != m_samplers.end()) { + return it->second.handle; + } + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = ToVkFilter(sampler.GetMagFilter()); + samplerInfo.minFilter = ToVkFilter(sampler.GetMinFilter()); + samplerInfo.mipmapMode = ToVkMipmapMode(sampler.GetMipmapMode()); + samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS()); + samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT()); + samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR()); + samplerInfo.mipLodBias = sampler.GetLodBias(); + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE; + samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc()); + samplerInfo.minLod = sampler.GetMinLod(); + samplerInfo.maxLod = sampler.GetMaxLod(); + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + VkSampler vkSampler = VK_NULL_HANDLE; + VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &vkSampler), "vkCreateSampler(texture)"); + + SamplerCacheEntry entry{}; + entry.handle = vkSampler; + entry.externalIndex = sampler.GetExternalIndex(); + entry.version = sampler.GetVersion(); + m_samplers[key] = entry; + return vkSampler; + } + + VkFilter VkTextureSamplerManager::ToVkFilter(SamplerFilterMode mode) { + return mode == SamplerFilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; + } + + VkSamplerMipmapMode VkTextureSamplerManager::ToVkMipmapMode(SamplerMipmapMode mode) { + switch (mode) { + case SamplerMipmapMode::Nearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case SamplerMipmapMode::Linear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + case SamplerMipmapMode::None: + default: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + } + } + + VkSamplerAddressMode VkTextureSamplerManager::ToVkAddressMode(SamplerWrapMode mode) { + switch (mode) { + case SamplerWrapMode::ClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case SamplerWrapMode::MirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case SamplerWrapMode::Repeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case SamplerWrapMode::ClampToBorder: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + case SamplerWrapMode::MirrorClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + default: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } + } + + VkCompareOp VkTextureSamplerManager::ToVkCompareOp(SamplerCompareFunc func) { + switch (func) { + case SamplerCompareFunc::Never: + return VK_COMPARE_OP_NEVER; + case SamplerCompareFunc::Less: + return VK_COMPARE_OP_LESS; + case SamplerCompareFunc::Equal: + return VK_COMPARE_OP_EQUAL; + case SamplerCompareFunc::LessEqual: + return VK_COMPARE_OP_LESS_OR_EQUAL; + case SamplerCompareFunc::Greater: + return VK_COMPARE_OP_GREATER; + case SamplerCompareFunc::NotEqual: + return VK_COMPARE_OP_NOT_EQUAL; + case SamplerCompareFunc::GreaterEqual: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + case SamplerCompareFunc::Always: + default: + return VK_COMPARE_OP_ALWAYS; + } + } + + Bool VkTextureSamplerManager::UploadFallbackTexture() { + const Uint32 rgba = 0xFFFFFFFFu; + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent = {1, 1, 1}; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &m_fallbackImage), "vkCreateImage(fallback)"); + + VkMemoryRequirements imageMemReq{}; + vkGetImageMemoryRequirements(m_device, m_fallbackImage, &imageMemReq); + + VkMemoryAllocateInfo imageAllocInfo{}; + imageAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + imageAllocInfo.allocationSize = imageMemReq.size; + imageAllocInfo.memoryTypeIndex = FindMemoryType(imageMemReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_VERIFY(vkAllocateMemory(m_device, &imageAllocInfo, nullptr, &m_fallbackImageMemory), + "vkAllocateMemory(fallback)"); + VK_VERIFY(vkBindImageMemory(m_device, m_fallbackImage, m_fallbackImageMemory, 0), "vkBindImageMemory(fallback)"); + + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VkDeviceMemory stagingMemory = VK_NULL_HANDLE; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(rgba); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_VERIFY(vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer), "vkCreateBuffer(fallback)"); + + VkMemoryRequirements stagingMemReq{}; + vkGetBufferMemoryRequirements(m_device, stagingBuffer, &stagingMemReq); + + VkMemoryAllocateInfo stagingAllocInfo{}; + stagingAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + stagingAllocInfo.allocationSize = stagingMemReq.size; + stagingAllocInfo.memoryTypeIndex = + FindMemoryType(stagingMemReq.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_VERIFY(vkAllocateMemory(m_device, &stagingAllocInfo, nullptr, &stagingMemory), "vkAllocateMemory(fallback)"); + VK_VERIFY(vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0), "vkBindBufferMemory(fallback)"); + + void* mapped = nullptr; + VK_VERIFY(vkMapMemory(m_device, stagingMemory, 0, sizeof(rgba), 0, &mapped), "vkMapMemory(fallback)"); + std::memcpy(mapped, &rgba, sizeof(rgba)); + vkUnmapMemory(m_device, stagingMemory); + + const Bool uploadOk = ExecuteImmediate([&](VkCommandBuffer commandBuffer) { + VkImageMemoryBarrier toTransferDst{}; + toTransferDst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toTransferDst.srcAccessMask = 0; + toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toTransferDst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransferDst.image = m_fallbackImage; + toTransferDst.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toTransferDst.subresourceRange.baseMipLevel = 0; + toTransferDst.subresourceRange.levelCount = 1; + toTransferDst.subresourceRange.baseArrayLayer = 0; + toTransferDst.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, + nullptr, 0, nullptr, 1, &toTransferDst); + + VkBufferImageCopy copy{}; + copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.imageSubresource.mipLevel = 0; + copy.imageSubresource.baseArrayLayer = 0; + copy.imageSubresource.layerCount = 1; + copy.imageExtent = {1, 1, 1}; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, m_fallbackImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ©); + + VkImageMemoryBarrier toSampled{}; + toSampled.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toSampled.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toSampled.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + toSampled.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toSampled.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + toSampled.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toSampled.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toSampled.image = m_fallbackImage; + toSampled.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toSampled.subresourceRange.baseMipLevel = 0; + toSampled.subresourceRange.levelCount = 1; + toSampled.subresourceRange.baseArrayLayer = 0; + toSampled.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, 0, nullptr, 1, &toSampled); + }); + + vkDestroyBuffer(m_device, stagingBuffer, nullptr); + vkFreeMemory(m_device, stagingMemory, nullptr); + if (!uploadOk) { + return false; + } + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = m_fallbackImage; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &m_fallbackImageView), "vkCreateImageView(fallback)"); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_NEAREST; + samplerInfo.minFilter = VK_FILTER_NEAREST; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + samplerInfo.maxAnisotropy = 1.0f; + VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &m_fallbackSampler), "vkCreateSampler(fallback)"); + return true; + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.h index f44fff11..92d17ab5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureSamplerManager.h @@ -10,35 +10,79 @@ #include "../VkIncludes.h" #include +#include +#include + +namespace MobileGL::MG_State::GLState { +class ITextureObject; +class SamplerObject; +} namespace MobileGL::MG_Backend::DirectVulkan { - class VkTextureSamplerManager { - public: - struct InitInfo { - VkDevice device = VK_NULL_HANDLE; - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkCommandPool commandPool = VK_NULL_HANDLE; - VkQueue graphicsQueue = VK_NULL_HANDLE; - }; - - Bool Initialize(const InitInfo& initInfo); - void Shutdown(); - - Bool GetFallbackDescriptor(VkDescriptorImageInfo& outImageInfo) const; - - private: - Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const; - Bool UploadFallbackTexture(); - - VkDevice m_device = VK_NULL_HANDLE; - VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; - VkCommandPool m_commandPool = VK_NULL_HANDLE; - VkQueue m_graphicsQueue = VK_NULL_HANDLE; - - VkImage m_fallbackImage = VK_NULL_HANDLE; - VkDeviceMemory m_fallbackImageMemory = VK_NULL_HANDLE; - VkImageView m_fallbackImageView = VK_NULL_HANDLE; - VkSampler m_fallbackSampler = VK_NULL_HANDLE; +class VkTextureSamplerManager { +public: + struct InitInfo { + VkDevice device = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkQueue graphicsQueue = VK_NULL_HANDLE; }; -} // namespace MobileGL::MG_Backend::DirectVulkan + Bool Initialize(const InitInfo& initInfo); + void Shutdown(); + + Bool GetFallbackDescriptor(VkDescriptorImageInfo& outImageInfo) const; + Bool SyncTextureAndGetDescriptor(const MG_State::GLState::ITextureObject& texture, + const MG_State::GLState::SamplerObject* samplerOverride, + VkDescriptorImageInfo& outImageInfo); + +private: + struct TextureResource { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkExtent2D extent = {0, 0}; + VkFormat format = VK_FORMAT_UNDEFINED; + Uint textureExternalIndex = 0; + }; + + struct SamplerCacheEntry { + VkSampler handle = VK_NULL_HANDLE; + Uint externalIndex = 0; + Uint16 version = 0; + }; + + Bool EnsureTextureSynced(TextureResource& resource, const MG_State::GLState::ITextureObject& texture); + Bool EnsureTextureResource(TextureResource& resource, const MG_State::GLState::ITextureObject& texture, + TextureUploadTarget level0Target, const IntVec3& texelSize, SizeT byteSize); + Bool UploadLevel0(TextureResource& resource, const MG_State::GLState::TextureObjectMipmap& mipmapTexture, + TextureUploadTarget level0Target, SizeT byteSize); + Bool ExecuteImmediate(const std::function& recorder) const; + void DestroyTextureResource(TextureResource& resource) const; + static Bool ResolveLevel0(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget& outTarget, + IntVec3& outTexelSize, SizeT& outByteSize); + static VkFormat ResolveTextureFormat(TextureInternalFormat format); + Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const; + + VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler); + static VkFilter ToVkFilter(SamplerFilterMode mode); + static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode); + static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode); + static VkCompareOp ToVkCompareOp(SamplerCompareFunc func); + Bool UploadFallbackTexture(); + + VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VkCommandPool m_commandPool = VK_NULL_HANDLE; + VkQueue m_graphicsQueue = VK_NULL_HANDLE; + + UnorderedMap m_textureResources; + UnorderedMap m_samplers; + + VkImage m_fallbackImage = VK_NULL_HANDLE; + VkDeviceMemory m_fallbackImageMemory = VK_NULL_HANDLE; + VkImageView m_fallbackImageView = VK_NULL_HANDLE; + VkSampler m_fallbackSampler = VK_NULL_HANDLE; +}; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2223e027..e7d7c878 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -121,20 +121,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_E("VkTextureSamplerManager initialization failed. Sampler/texture descriptors will fallback."); m_textureSamplerManager.reset(); } - m_uniformDescriptorBinder = MakeUnique(); - if (!m_uniformDescriptorBinder->Initialize(m_device, m_allocator, - m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, - m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024, - m_textureSamplerManager.get())) { - MGLOG_E("UniformDescriptorBinder initialization failed. UBO sync on Vulkan backend is disabled."); - m_uniformDescriptorBinder.reset(); - } - m_vertexInputStateFactory = MakeUnique(m_config); m_framebufferManager = MakeUnique(); if (!m_framebufferManager->Initialize({m_device, m_physicalDevice.handle})) { MGLOG_E("VkFramebufferManager initialization failed. Offscreen FBO clear path is disabled."); m_framebufferManager.reset(); } + m_uniformDescriptorBinder = MakeUnique(); + if (!m_uniformDescriptorBinder->Initialize(m_device, m_allocator, + m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, + m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024, + m_textureSamplerManager.get(), + m_framebufferManager.get())) { + MGLOG_E("UniformDescriptorBinder initialization failed. UBO sync on Vulkan backend is disabled."); + m_uniformDescriptorBinder.reset(); + } + m_vertexInputStateFactory = MakeUnique(m_config); PrepareDemoPipeline(); CreateFrameContexts(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index c1dad4eb..7f64751d 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -35,7 +35,7 @@ namespace MobileGL { Int GetUniformMaxLength() const { return m_uniformNameMaxLength; } Uint GetUniformCount() { return m_activeUniformCount; } Uint GetMaxUniformLocation() const { return m_maxUniformLocation; } - Int GetUniformLocation(const String& name) { + Int GetUniformLocation(const String& name) const { const auto it = m_uniformLocations.find(name); if (it == m_uniformLocations.end()) return -1; return (Int)it->second;