[Feat] (MG_State/RenderState, MG_Backend/DirectVulkan): make Distant

Horizon work
- Implement BlendEquation/CullFaceMode/PointSize/PolygonMode
- Implement GetFramebufferAttachmentParameter*
- Downgrade some color attachment resolve failure
- Downgrade some overly-strict shader stage linkage check (don't check
  on unused input var)
This commit is contained in:
2026-05-12 12:38:19 +08:00
parent 60199c0323
commit 633b3d3b0a
22 changed files with 489 additions and 126 deletions
@@ -13,6 +13,7 @@
#include <cstring>
#include <spirv-tools/libspirv.h>
#include <spirv-tools/optimizer.hpp>
#include <source/opt/build_module.h>
#include <source/opt/constants.h>
#include <source/opt/instruction.h>
#include <source/opt/ir_builder.h>
@@ -290,12 +291,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return occupiedSlotCount;
}
void ValidateTransformedSpirv(const Vector<Uint>& spirv, ShaderStage shaderStage, Uint programExternalIndex) {
if (spirv.empty()) {
return;
}
spv_const_binary_t binary = {spirv.data(), spirv.size()};
spv_target_env GetSpirvTargetEnv(const Vector<Uint>& spirv) {
spv_target_env targetEnv = SPV_ENV_VULKAN_1_0;
if (spirv.size() > 1) {
const Uint32 versionWord = spirv[1];
@@ -311,6 +307,53 @@ namespace MobileGL::MG_Backend::DirectVulkan {
targetEnv = SPV_ENV_VULKAN_1_1;
}
}
return targetEnv;
}
Bool IsInterfaceVariableStaticallyUsed(const Vector<Uint>& spirv, Uint32 spirvId) {
if (spirv.empty() || spirvId == 0) {
return false;
}
auto context = spvtools::BuildModule(
GetSpirvTargetEnv(spirv),
[](spv_message_level_t, const char*, const spv_position_t&, const char*) {},
spirv.data(),
spirv.size());
if (!context) {
return true;
}
auto* variable = context->get_def_use_mgr()->GetDef(spirvId);
if (variable == nullptr) {
return false;
}
Bool used = false;
context->get_def_use_mgr()->ForEachUser(variable, [&used](spvtools::opt::Instruction* user) {
switch (user->opcode()) {
case spv::Op::OpName:
case spv::Op::OpMemberName:
case spv::Op::OpDecorate:
case spv::Op::OpMemberDecorate:
case spv::Op::OpDecorateId:
case spv::Op::OpEntryPoint:
return;
default:
used = true;
return;
}
});
return used;
}
void ValidateTransformedSpirv(const Vector<Uint>& spirv, ShaderStage shaderStage, Uint programExternalIndex) {
if (spirv.empty()) {
return;
}
spv_const_binary_t binary = {spirv.data(), spirv.size()};
const spv_target_env targetEnv = GetSpirvTargetEnv(spirv);
spv_context context = spvContextCreate(targetEnv);
MOBILEGL_ASSERT(context != nullptr,
@@ -589,6 +632,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (variable == nullptr) {
continue;
}
if (reflectInputs && !IsInterfaceVariableStaticallyUsed(module, variable->spirv_id)) {
continue;
}
ReflectStageInterfaceVariable(*variable, reflectInputs, outSummary, programExternalIndex,
stageLabel, stageCursor);
}
@@ -10,9 +10,49 @@
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
#include "MG_State/GLState/TextureState/TextureObject2D.h"
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
namespace MobileGL::MG_Backend::DirectVulkan {
static MG_State::GLState::ITextureObject* ResolveCompleteColorAttachmentTexture(
const MG_State::GLState::FramebufferObject& fbo,
FramebufferAttachmentType attachmentType,
Uint32 drawBufferIndex) {
if (attachmentType == FramebufferAttachmentType::None) {
return nullptr;
}
const auto& attachment = fbo.GetAttachment(attachmentType);
if (!attachment.IsTexture()) {
if (attachment.IsEmpty()) {
MGLOG_D("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u has no bound color attachment; using VK_ATTACHMENT_UNUSED",
drawBufferIndex,
MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(),
fbo.GetExternalIndex());
}
return nullptr;
}
if (!attachment.IsComplete()) {
MGLOG_W("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u has an incomplete texture attachment; using VK_ATTACHMENT_UNUSED",
drawBufferIndex,
MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(),
fbo.GetExternalIndex());
return nullptr;
}
auto* texture = attachment.GetTexture().get();
if (texture == nullptr) {
MGLOG_W("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u resolved to a null texture; using VK_ATTACHMENT_UNUSED",
drawBufferIndex,
MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(),
fbo.GetExternalIndex());
return nullptr;
}
return texture;
}
VkRenderPassManager::VkRenderPassManager(VkDevice device,
const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager,
SwapchainObject& swapchainObject):
@@ -195,12 +235,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// assuming default FBO has the right param
for (Uint32 i = 0; i < colorAttachmentSlotCount; ++i) {
auto drawbuf = drawbufs[i];
if (drawbuf == FramebufferAttachmentType::None ||
fbo.GetAttachment(drawbuf).IsRenderbuffer())
auto* texture = ResolveCompleteColorAttachmentTexture(fbo, drawbuf, i);
if (texture == nullptr)
continue;
auto& att = fbo.GetAttachment(drawbuf);
auto* texture = att.GetTexture().get();
const Uint32 attachmentMipLevel = static_cast<Uint32>(std::max(att.GetTextureLevel(), 0));
const auto textureTarget = texture->GetTarget();
const Uint32 attachmentIndex = static_cast<Uint32>(attachmentDescriptions.size());
@@ -27,17 +27,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool blendEnable,
VkBlendFactor srcColorBlendFactor,
VkBlendFactor dstColorBlendFactor,
VkBlendOp colorBlendOp,
VkBlendFactor srcAlphaBlendFactor,
VkBlendFactor dstAlphaBlendFactor,
VkBlendOp alphaBlendOp,
VkColorComponentFlags colorWriteMask) {
VkPipelineColorBlendAttachmentState attachment{};
attachment.blendEnable = blendEnable ? VK_TRUE : VK_FALSE;
attachment.srcColorBlendFactor = srcColorBlendFactor;
attachment.dstColorBlendFactor = dstColorBlendFactor;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.colorBlendOp = colorBlendOp;
attachment.srcAlphaBlendFactor = srcAlphaBlendFactor;
attachment.dstAlphaBlendFactor = dstAlphaBlendFactor;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
attachment.alphaBlendOp = alphaBlendOp;
attachment.colorWriteMask = colorWriteMask;
return attachment;
}
@@ -1720,8 +1722,10 @@ void main() {
false,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
kColorWriteMask);
}
return m_pipelineFactory->GetOrCreatePipeline(payload);
@@ -2173,14 +2177,15 @@ void main() {
.vertexInputState = pipelineVertexInputState
};
const Bool hasDepthStencilAttachment = renderPassEntry.hasDepthStencilAttachment;
MOBILEGL_ASSERT(
hasDepthStencilAttachment || (!payload.depthTestEnable && !payload.depthWriteEnable),
"GetOrCreatePipeline: render pass has no depth attachment but depthTestEnable=%d depthWriteEnable=%d program=%u attachmentCount=%u colorAttachmentCount=%u",
payload.depthTestEnable ? 1 : 0,
payload.depthWriteEnable ? 1 : 0,
program.GetExternalIndex(),
renderPassEntry.attachmentCount,
renderPassEntry.colorAttachmentCount);
if (!hasDepthStencilAttachment && (payload.depthTestEnable || payload.depthWriteEnable)) {
MGLOG_D("GetOrCreatePipeline: disabling depth test/write for program=%u because render pass has no depth attachment (attachmentCount=%u colorAttachmentCount=%u)",
program.GetExternalIndex(),
renderPassEntry.attachmentCount,
renderPassEntry.colorAttachmentCount);
payload.depthTestEnable = false;
payload.depthWriteEnable = false;
payload.depthCompareOp = VK_COMPARE_OP_ALWAYS;
}
const Uint32 fragmentOutputMask = programObj.activeFragmentOutputLocationMask;
MOBILEGL_ASSERT(
(fragmentOutputMask >> payload.colorAttachmentCount) == 0,
@@ -2197,92 +2202,109 @@ void main() {
const Bool isDefaultDrawFbo =
drawFboBinding.get() == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get();
const auto& drawBuffers = drawFboBinding->GetDrawBuffers();
auto resolveCompleteColorAttachmentTexture = [&](Uint32 drawBufferIndex) -> MG_State::GLState::ITextureObject* {
if (isDefaultDrawFbo || drawBufferIndex >= drawBuffers.size()) {
return nullptr;
}
const auto drawBuffer = drawBuffers[drawBufferIndex];
if (drawBuffer == FramebufferAttachmentType::None) {
return nullptr;
}
const auto& attachment = drawFboBinding->GetAttachment(drawBuffer);
if (!attachment.IsTexture() || !attachment.IsComplete()) {
return nullptr;
}
return attachment.GetTexture().get();
};
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
BlendFactor srcRGB = BlendFactor::One;
BlendFactor dstRGB = BlendFactor::Zero;
BlendFactor srcAlpha = BlendFactor::One;
BlendFactor dstAlpha = BlendFactor::Zero;
BlendEquation colorEquation = BlendEquation::Add;
BlendEquation alphaEquation = BlendEquation::Add;
MG_State::pGLContext->GetBlendFuncIndexed(i, srcRGB, dstRGB, srcAlpha, dstAlpha);
MG_State::pGLContext->GetBlendEquationIndexed(i, colorEquation, alphaEquation);
const Bool blendEnabled = MG_State::pGLContext->IsCapabilityEnabledIndexed(CapabilityInput::Blend, i);
VkColorComponentFlags attachmentColorWriteMask = colorWriteMask;
Bool effectiveBlendEnabled = blendEnabled;
MG_State::GLState::ITextureObject* colorAttachmentTexture = nullptr;
if (!isDefaultDrawFbo && i < drawBuffers.size()) {
const auto drawBuffer = drawBuffers[i];
if (drawBuffer == FramebufferAttachmentType::None) {
colorAttachmentTexture = resolveCompleteColorAttachmentTexture(i);
if (drawBuffer == FramebufferAttachmentType::None || colorAttachmentTexture == nullptr) {
// GL ignores writes and per-target blend state for GL_NONE draw buffer slots.
// Depth-only or otherwise unattached draw buffers should also discard color writes.
attachmentColorWriteMask = 0;
effectiveBlendEnabled = false;
}
if (drawBuffer != FramebufferAttachmentType::None) {
const auto& attachment = drawFboBinding->GetAttachment(drawBuffer);
if (attachment.IsTexture()) {
auto* texture = attachment.GetTexture().get();
MOBILEGL_ASSERT(texture != nullptr,
"GetOrCreatePipeline: color attachment %u texture is null",
i);
if (colorAttachmentTexture != nullptr) {
auto* texture = colorAttachmentTexture;
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
MOBILEGL_ASSERT(textureResource != nullptr,
"GetOrCreatePipeline: failed to sync color attachment textureId=%d",
texture->GetExternalIndex());
VkFormatProperties attachmentFormatProperties{};
vkGetPhysicalDeviceFormatProperties(
m_physicalDevice.handle,
textureResource->format,
&attachmentFormatProperties);
const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
MOBILEGL_ASSERT(textureResource != nullptr,
"GetOrCreatePipeline: failed to sync color attachment textureId=%d",
texture->GetExternalIndex());
VkFormatProperties attachmentFormatProperties{};
vkGetPhysicalDeviceFormatProperties(
m_physicalDevice.handle,
textureResource->format,
&attachmentFormatProperties);
MOBILEGL_ASSERT(
(attachmentFormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0,
"GetOrCreatePipeline: color attachment %u format=%d textureId=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT (program=%u)",
i,
static_cast<Int>(textureResource->format),
texture->GetExternalIndex(),
program.GetExternalIndex());
#endif
const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat());
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
const NumericDomain attachmentNumericDomain =
GetNumericDomainForTextureInternalFormat(texture->GetFormat());
for (Uint32 outputLocation = 0;
outputLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations;
++outputLocation) {
if ((programObj.activeFragmentOutputLocationMask & (1u << outputLocation)) == 0 ||
outputLocation != i) {
continue;
}
const GLenum fragmentOutputType = programObj.fragmentOutputTypes[outputLocation];
const NumericDomain fragmentOutputDomain =
GetNumericDomainForShaderValueType(fragmentOutputType);
// GL allows fragment outputs with more components than the bound color attachment;
// excess components are discarded during conversion to the attachment format.
MOBILEGL_ASSERT(
(attachmentFormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0,
"GetOrCreatePipeline: color attachment %u format=%d textureId=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT (program=%u)",
attachmentNumericDomain == NumericDomain::Unknown ||
fragmentOutputDomain == NumericDomain::Unknown ||
attachmentNumericDomain == fragmentOutputDomain,
"GetOrCreatePipeline: fragment output location=%d type=%u mismatches color attachment %u internalFormat=%d textureId=%d program=%u",
static_cast<Int>(outputLocation),
static_cast<Uint32>(fragmentOutputType),
i,
static_cast<Int>(textureResource->format),
static_cast<Int>(texture->GetFormat()),
texture->GetExternalIndex(),
program.GetExternalIndex());
}
#endif
const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat());
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
const NumericDomain attachmentNumericDomain =
GetNumericDomainForTextureInternalFormat(texture->GetFormat());
for (Uint32 outputLocation = 0;
outputLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations;
++outputLocation) {
if ((programObj.activeFragmentOutputLocationMask & (1u << outputLocation)) == 0 ||
outputLocation != i) {
continue;
}
const GLenum fragmentOutputType = programObj.fragmentOutputTypes[outputLocation];
const NumericDomain fragmentOutputDomain =
GetNumericDomainForShaderValueType(fragmentOutputType);
// GL allows fragment outputs with more components than the bound color attachment;
// excess components are discarded during conversion to the attachment format.
MOBILEGL_ASSERT(
attachmentNumericDomain == NumericDomain::Unknown ||
fragmentOutputDomain == NumericDomain::Unknown ||
attachmentNumericDomain == fragmentOutputDomain,
"GetOrCreatePipeline: fragment output location=%d type=%u mismatches color attachment %u internalFormat=%d textureId=%d program=%u",
static_cast<Int>(outputLocation),
static_cast<Uint32>(fragmentOutputType),
i,
static_cast<Int>(texture->GetFormat()),
texture->GetExternalIndex(),
program.GetExternalIndex());
}
#endif
const VkColorComponentFlags supportedColorWriteMask =
GetSupportedColorWriteMaskForComponentCount(componentCount);
if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) {
MGLOG_W(
"GetOrCreatePipeline: clamping colorWriteMask=0x%x to 0x%x on color attachment %u (componentCount=%zu textureId=%d internalFormat=%d program=%u blendEnabled=%d)",
static_cast<Uint32>(attachmentColorWriteMask),
static_cast<Uint32>(attachmentColorWriteMask & supportedColorWriteMask),
i,
componentCount,
texture->GetExternalIndex(),
static_cast<Int>(texture->GetFormat()),
program.GetExternalIndex(),
effectiveBlendEnabled ? 1 : 0);
attachmentColorWriteMask &= supportedColorWriteMask;
}
const VkColorComponentFlags supportedColorWriteMask =
GetSupportedColorWriteMaskForComponentCount(componentCount);
if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) {
MGLOG_W(
"GetOrCreatePipeline: clamping colorWriteMask=0x%x to 0x%x on color attachment %u (componentCount=%zu textureId=%d internalFormat=%d program=%u blendEnabled=%d)",
static_cast<Uint32>(attachmentColorWriteMask),
static_cast<Uint32>(attachmentColorWriteMask & supportedColorWriteMask),
i,
componentCount,
texture->GetExternalIndex(),
static_cast<Int>(texture->GetFormat()),
program.GetExternalIndex(),
effectiveBlendEnabled ? 1 : 0);
attachmentColorWriteMask &= supportedColorWriteMask;
}
}
}
@@ -2296,16 +2318,9 @@ void main() {
if (isDefaultDrawFbo) {
colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format;
} else {
const auto drawBuffer = drawBuffers[i];
MOBILEGL_ASSERT(drawBuffer != FramebufferAttachmentType::None,
"GetOrCreatePipeline: blend is enabled on draw buffer %u but the attachment is None",
i);
const auto& attachment = drawFboBinding->GetAttachment(drawBuffer);
MOBILEGL_ASSERT(attachment.IsTexture(),
"GetOrCreatePipeline: blend validation currently expects texture color attachments only");
auto* texture = attachment.GetTexture().get();
auto* texture = colorAttachmentTexture;
MOBILEGL_ASSERT(texture != nullptr,
"GetOrCreatePipeline: color attachment %u texture is null",
"GetOrCreatePipeline: blend is enabled on draw buffer %u but no complete texture attachment is bound",
i);
textureExternalIndex = texture->GetExternalIndex();
colorAttachmentFormat = MG_Util::ConvertTextureInternalFormatToVkEnum(texture->GetFormat());
@@ -2327,8 +2342,10 @@ void main() {
effectiveBlendEnabled,
MG_Util::ConvertBlendFactorToVkEnum(srcRGB),
MG_Util::ConvertBlendFactorToVkEnum(dstRGB),
MG_Util::ConvertBlendEquationToVkEnum(colorEquation),
MG_Util::ConvertBlendFactorToVkEnum(srcAlpha),
MG_Util::ConvertBlendFactorToVkEnum(dstAlpha),
MG_Util::ConvertBlendEquationToVkEnum(alphaEquation),
attachmentColorWriteMask);
}
return m_pipelineFactory->GetOrCreatePipeline(payload);
@@ -2431,6 +2448,14 @@ void main() {
activeRenderPass = nullptr;
renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired);
}
if (renderPassEntry->attachmentCount == 0 || renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) {
MGLOG_D("SetupDraw skipped: drawFbo=%u resolved to an empty render pass (attachmentCount=%u extent=%dx%d)",
drawFbo->GetExternalIndex(),
renderPassEntry->attachmentCount,
renderPassEntry->extent.x(),
renderPassEntry->extent.y());
return false;
}
auto pipeline = GetOrCreatePipeline(mode, program, programObj, transformFlags, vao, *renderPassEntry);
activeRenderPass = VkRenderPassManager::GetActiveRenderPass();