[Feat] (MG_Backend/DirectVulkan): implement multisample texture backend

This commit is contained in:
BZLZHH
2026-06-10 01:56:37 +08:00
parent 5c1fd733da
commit 6051588458
7 changed files with 140 additions and 17 deletions
@@ -33,6 +33,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
@@ -134,7 +135,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
raster.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
ms.rasterizationSamples = payload.rasterizationSamples;
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
@@ -26,6 +26,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkRenderPass renderPass = VK_NULL_HANDLE;
Uint32 colorAttachmentCount = 1;
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
Uint32 subpass = 0;
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT;
@@ -172,6 +172,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
if (resource != nullptr) {
imageIdentity = reinterpret_cast<Uint64>(resource->image);
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)));
}
@@ -289,6 +293,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
textureResources.reserve(colorAttachmentSlotCount + 1);
Vector<VkImageView> attachmentViews;
attachmentViews.reserve(colorAttachmentSlotCount + 1);
VkSampleCountFlagBits renderPassSampleCount = VK_SAMPLE_COUNT_1_BIT;
Bool hasRenderPassSampleCount = false;
const auto adoptRenderPassSampleCount = [&](VkSampleCountFlagBits sampleCount,
const char* attachmentKind,
Int attachmentId) {
if (!hasRenderPassSampleCount) {
renderPassSampleCount = sampleCount;
hasRenderPassSampleCount = true;
return;
}
MOBILEGL_ASSERT(renderPassSampleCount == sampleCount,
"GetOrCreateRenderPass: mismatched sample count %d on %s attachment %d (expected %d)",
static_cast<Int>(sampleCount), attachmentKind, attachmentId,
static_cast<Int>(renderPassSampleCount));
};
// This should automatically work on default & offscreen FBO
// assuming default FBO has the right param
for (Uint32 i = 0; i < colorAttachmentSlotCount; ++i) {
@@ -306,15 +325,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Color attachment description
VkAttachmentDescription& desc = attachmentDescriptions.back();
switch (textureTarget) {
case TextureTarget::Texture2D: {
auto* texture2d =
static_cast<MG_State::GLState::TextureObject2D*>(texture);
case TextureTarget::Texture2D:
case TextureTarget::Texture2DMultisample: {
desc.flags = 0;
desc.format = isDefaultFbo ?
m_swapchainObject.GetSurfaceFormat().format :
MG_Util::ConvertTextureInternalFormatToVkEnum(
texture2d->GetFormat());
desc.samples = VK_SAMPLE_COUNT_1_BIT;
texture->GetFormat());
VkSampleCountFlagBits attachmentSampleCount = VK_SAMPLE_COUNT_1_BIT;
ClearAttachmentPayload clearPayload{};
Bool hasClear = m_clearManager.GetPendingClear(att, clearPayload);
VkImageLayout trackedColorLayout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -348,6 +366,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.swapchainImageIndex = swapchainImageIndex,
.finalLayout = desc.finalLayout,
});
attachmentSampleCount = VK_SAMPLE_COUNT_1_BIT;
textureResources.emplace_back(nullptr);
attachmentViews.emplace_back(swapchainViews[swapchainImageIndex]);
} else {
@@ -356,6 +375,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i);
textureResources.emplace_back(textureResource);
desc.format = textureResource->format;
attachmentSampleCount = textureResource->sampleCount;
trackedColorLayout = textureResource->layout;
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
.target = TrackedAttachmentTarget::Texture,
@@ -372,6 +392,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE,
"GetOrCreateRenderPass: GetOrCreateAttachmentView failed at color attachment %d", i);
}
desc.samples = attachmentSampleCount;
adoptRenderPassSampleCount(attachmentSampleCount, "color", texture->GetExternalIndex());
if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
MGLOG_W("GetOrCreateRenderPass: color attachment textureId=%d starts with undefined layout and no clear; "
@@ -439,10 +461,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
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;
}
depthAttachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachmentDescription.samples = depthAttachmentSampleCount;
adoptRenderPassSampleCount(depthAttachmentSampleCount, "depth/stencil", texture.GetExternalIndex());
const auto loadInfo =
ResolveDepthStencilAttachmentLoadInfo(trackedDepthLayout, clearDepth, clearStencil);
depthAttachmentDescription.loadOp = loadInfo.depthLoadOp;
@@ -556,13 +581,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static_cast<Uint32>(attachmentViews.size()),
static_cast<Uint32>(colorAttachmentRefs.size()),
hasDepthStencilAttachment,
renderPassSampleCount,
extent,
1 };
MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u extent=%dx%d",
MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u samples=%d extent=%dx%d",
static_cast<unsigned long long>(hash),
static_cast<unsigned long long>(compatibilityHash),
renderPassEntry.attachmentCount,
renderPassEntry.colorAttachmentCount,
static_cast<Int>(renderPassEntry.sampleCount),
extent.x(),
extent.y());
auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry));
@@ -58,6 +58,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 attachmentCount = 0;
Uint32 colorAttachmentCount = 0;
Bool hasDepthStencilAttachment = false;
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
IntVec2 extent = {0, 0};
Uint32 subpass = 0;
@@ -73,6 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
std::swap(attachmentCount, that.attachmentCount);
std::swap(colorAttachmentCount, that.colorAttachmentCount);
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
std::swap(sampleCount, that.sampleCount);
std::swap(extent, that.extent);
std::swap(subpass, that.subpass);
}
@@ -86,6 +88,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 attachmentCount,
Uint32 colorAttachmentCount,
Bool hasDepthStencilAttachment,
VkSampleCountFlagBits sampleCount,
IntVec2 extent, int subpass):
hash(hash),
renderPass(renderpass),
@@ -96,6 +99,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
attachmentCount(attachmentCount),
colorAttachmentCount(colorAttachmentCount),
hasDepthStencilAttachment(hasDepthStencilAttachment),
sampleCount(sampleCount),
extent(extent),
subpass(subpass)
{}
@@ -46,6 +46,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 arrayLayers = 1;
};
static Bool IsMultisampleTextureUploadTarget(TextureUploadTarget target) {
return target == TextureUploadTarget::Texture2DMultisample ||
target == TextureUploadTarget::ProxyTexture2DMultisample ||
target == TextureUploadTarget::Texture2DMultisampleArray ||
target == TextureUploadTarget::ProxyTexture2DMultisampleArray;
}
static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) {
switch (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 Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
return target >= TextureUploadTarget::CubeMapPositiveX &&
target <= TextureUploadTarget::CubeMapNegativeZ;
@@ -468,6 +503,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
case TextureUploadTarget::ProxyTextureRectangle:
outShape = {};
return true;
case TextureUploadTarget::Texture2DMultisample:
case TextureUploadTarget::ProxyTexture2DMultisample:
outShape = {};
return true;
case TextureUploadTarget::Texture2DMultisampleArray:
case TextureUploadTarget::ProxyTexture2DMultisampleArray:
MOBILEGL_ASSERT(texelSize.z() > 0,
"TryResolveTextureShapeInfo: invalid 2D multisample array depth=%d for textureId=%d",
texelSize.z(), texture.GetExternalIndex());
outShape.imageType = VK_IMAGE_TYPE_2D;
outShape.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
outShape.depth = 1;
outShape.arrayLayers = static_cast<Uint32>(texelSize.z());
return true;
case TextureUploadTarget::Texture3D:
case TextureUploadTarget::ProxyTexture3D:
MOBILEGL_ASSERT(texelSize.z() > 0,
@@ -806,6 +855,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (resource == nullptr) {
return false;
}
if (resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
MGLOG_D("TransitionTextureForStorageImage: multisample textureId=%d is not exposed as a storage image",
texture.GetExternalIndex());
return false;
}
if (resource->layout == VK_IMAGE_LAYOUT_GENERAL) {
return true;
}
@@ -958,7 +1012,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_D("%s: no mip levels", __func__);
return false;
}
const Uint32 backingMipLevels = std::max(mipLevels, ComputeFullMipLevelCount(texelSize));
const Bool isMultisampleTexture = IsMultisampleTextureUploadTarget(uploadTarget);
const Uint32 backingMipLevels =
isMultisampleTexture ? 1u : std::max(mipLevels, ComputeFullMipLevelCount(texelSize));
TextureShapeInfo shapeInfo{};
const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo);
MOBILEGL_ASSERT(supportedShape,
@@ -972,6 +1028,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_D("%s: not Texture2D, unsupported", __func__);
return false;
}
VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT;
if (isMultisampleTexture &&
!TryResolveSampleCountFlagBits(texture.GetSamples(), resolvedSampleCount)) {
MGLOG_D("%s: unsupported multisample count=%d for textureId=%d target=%s", __func__,
texture.GetSamples(), texture.GetExternalIndex(),
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str());
return false;
}
const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format &&
resource.extent.width == static_cast<Uint32>(texelSize.x()) &&
@@ -979,6 +1043,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.depth == shapeInfo.depth &&
resource.arrayLayers == shapeInfo.arrayLayers &&
resource.viewType == shapeInfo.viewType &&
resource.sampleCount == resolvedSampleCount &&
resource.mipLevels == backingMipLevels;
if (compatible) {
if (resource.perMipViews.size() != backingMipLevels) {
@@ -998,6 +1063,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.depth == shapeInfo.depth &&
resource.arrayLayers == shapeInfo.arrayLayers &&
resource.viewType == shapeInfo.viewType &&
resource.sampleCount == resolvedSampleCount &&
resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT &&
resource.mipLevels < backingMipLevels &&
resource.layout != VK_IMAGE_LAYOUT_UNDEFINED;
@@ -1025,16 +1092,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFormatProperties formatProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
const Bool supportsStorageImage =
!isMultisampleTexture &&
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
imageInfo.usage =
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
(supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) |
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ?
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
((aspect & VK_IMAGE_ASPECT_DEPTH_BIT || aspect & VK_IMAGE_ASPECT_STENCIL_BIT) ?
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 0);
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
(supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) |
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
(((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) || (aspect & VK_IMAGE_ASPECT_STENCIL_BIT)) ?
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT :
0);
if (!isMultisampleTexture) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
imageInfo.samples = resolvedSampleCount;
if (isMultisampleTexture) {
VkImageFormatProperties imageFormatProperties{};
const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
imageInfo.flags, &imageFormatProperties);
if (imageFormatResult != VK_SUCCESS ||
(imageFormatProperties.sampleCounts & resolvedSampleCount) == 0) {
MGLOG_D("%s: sampleCount=%d is unsupported for textureId=%d target=%s format=%d usage=0x%x",
__func__, texture.GetSamples(), texture.GetExternalIndex(),
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
static_cast<Int>(format), static_cast<Uint32>(imageInfo.usage));
return false;
}
}
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocationInfo{};
allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
@@ -1054,6 +1138,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource.format = format;
resource.aspect = aspect;
resource.viewType = shapeInfo.viewType;
resource.sampleCount = resolvedSampleCount;
resource.syncedTextureParamsVersion = 0;
if (preservedResource) {
@@ -72,6 +72,7 @@ public:
VkFormat format = VK_FORMAT_UNDEFINED;
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE;
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
Uint16 syncedTextureParamsVersion = 0;
TextureResource() = default;
@@ -94,6 +95,7 @@ public:
std::swap(this->format, that.format);
std::swap(this->aspect, that.aspect);
std::swap(this->viewType, that.viewType);
std::swap(this->sampleCount, that.sampleCount);
std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion);
}
@@ -139,6 +141,7 @@ public:
format = VK_FORMAT_UNDEFINED;
aspect = VK_IMAGE_ASPECT_NONE;
viewType = VK_IMAGE_VIEW_TYPE_2D;
sampleCount = VK_SAMPLE_COUNT_1_BIT;
syncedTextureParamsVersion = 0;
}
@@ -2167,6 +2167,7 @@ void main() {
.pipelineLayout = programObj.pipelineLayout,
.renderPass = renderPassEntry.renderPass,
.colorAttachmentCount = renderPassEntry.colorAttachmentCount,
.rasterizationSamples = renderPassEntry.sampleCount,
.subpass = 0,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.cullMode = VK_CULL_MODE_NONE,
@@ -2633,6 +2634,7 @@ void main() {
.pipelineLayout = programObj.pipelineLayout,
.renderPass = renderPassEntry.renderPass,
.colorAttachmentCount = renderPassEntry.colorAttachmentCount,
.rasterizationSamples = renderPassEntry.sampleCount,
.subpass = 0,
.topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode),
.cullMode = cullFaceEnabled