[Feat] (MG_Backend, MG_Impl, MG_Util): attach one layer of any layered texture on DirectVulkan

Whether a backend can attach a single layer of a texture to a framebuffer was one
Bool, so it could only give the most conservative answer any target needed.
DirectVulkan therefore declined every layer of every target and
direct_state_access.framebuffers_texture_layer_attachment failed with 542
messages across four targets.

The three ways a GL layer maps onto Vulkan are independent capabilities, so the
flag becomes a per-TextureTarget mask. A 2D or 2D multisample array layer IS a
VkImage array layer and needed nothing but the gate opened. A cube map array is
one 2D image with arrayLayers = 6 * cubeCount and CUBE_COMPATIBLE, which is a
shape VkTextureManager simply did not have - it is declined softly when the depth
is not a whole number of cubes or the level is not square, because that function's
Bool return exists for unrepresentable shapes and asserting there would abort on
ordinary input, GL_PROXY_TEXTURE_CUBE_MAP_ARRAY above all. A 3D texture's layer is
a z slice, which needs a 2D-array-compatible image and a per-slice clear, because
vkCmdClearColorImage cannot address a subset of a 3D image's slices - a render
pass whose only content is its LOAD_OP_CLEAR can, since its attachment is a 2D
view over that one slice.

VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is asked for per format and withdrawn per
format, mirroring the MUTABLE_FORMAT pattern already in this file: the capability
is per format+usage, so a single global probe answers a different question than
the one the frontend goes on to ask. Losing it costs per-slice attachment for that
format; failing creation would lose the texture.

Three things found on the way that are not the headline:

glFramebufferTextureLayer, the non-DSA twin, had no gate at all and additionally
refused cube map arrays that GL 4.5 requires it to accept. GL 4.6 core 9.2.8 makes
the two entry points equivalent, so they now decline in the same places - leaving
one ungated is what let an unrepresentable attachment reach the renderer.

ComputeFullMipLevelCount takes max(x, y, z), and for every array shape z is the
layer count rather than a mip-able axis, so a 4x4 array with 192 layers asked for
six mip levels on an image whose legal maximum is three
(VUID-VkImageCreateInfo-mipLevels-00958). Only the image's own extent can bound
it. lavapipe had been letting that through.

A layered GL clear queues layerCount = depth, which is illegal for a
VK_IMAGE_TYPE_3D image (VUID-vkCmdClearColorImage-baseArrayLayer-01472 pins it to
0/1, read as the whole mip level) and the old code passed it straight through.

Takes framebuffers_texture_layer_attachment green on DirectVulkan, so the whole
direct_state_access suite is 371/371 there; Espryt stays 370/371, the remaining
case being the fp64 one it declines by design.

Known and deliberately not fixed here, with a FIXME at the site:
KHR-GL44/45/46.geometry_shader.layered_framebuffer.clear_call_support now fails on
DirectVulkan - a layered clear of a 3D texture reads back zeros. Those cases exist
only in the GL44+ lists, above the 4.0 this backend reports. An A/B of a 6935-case
subset (cube map array, texture storage, framebuffer, 3D, the full DSA suite and
the GL33 texture group) is otherwise clean on both backends: 16 cases fixed and
none broken on Espryt, 15 fixed and those 2 broken on Magma, and zero difference
anywhere at GL 4.0 or below. The FIXME records which causes were already ruled out
by bisection so the next reader does not repeat them.
This commit is contained in:
BZLZHH
2026-08-05 12:07:04 -04:00
parent c8c7b19579
commit ba81ee114e
12 changed files with 416 additions and 24 deletions
@@ -1069,11 +1069,36 @@ namespace MobileGL::MG_Impl::GLImpl {
case TextureTarget::Texture2DMultisampleArray:
textureUploadTarget = TextureUploadTarget::Texture2DMultisampleArray;
break;
case TextureTarget::Texture1DArray:
textureUploadTarget = TextureUploadTarget::Texture1DArray;
break;
case TextureTarget::TextureCubeMapArray:
textureUploadTarget = TextureUploadTarget::CubeMapArray;
break;
default:
RecordUnsupportedFramebufferTextureAttachmentError(
__func__, "FramebufferTextureLayer requires a 3D, 2D array or 2D multisample array texture.");
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"FramebufferTextureLayer requires a 3D, array, 2D multisample "
"array, or cube map array texture."));
return;
}
// The same backend question the DSA twin asks. GL 4.6 core 9.2.8 makes the two entry points
// equivalent, so they have to decline in the same places - leaving this one ungated is what
// let an unrepresentable attachment reach the renderer, and it also refused cube map arrays
// that GL requires it to accept.
{
const auto& layerLimits = MG_Backend::pActiveBackendObject
? MG_Backend::pActiveBackendObject->GetDynamicParameters()
: MG_Backend::DynamicBackendParameters{};
const TextureTarget layeredTarget = textureObject->GetTarget();
if ((layer != 0 || layeredTarget == TextureTarget::TextureCubeMapArray) &&
!layerLimits.SupportsPerLayerFramebufferAttachment(layeredTarget)) {
RecordUnsupportedFramebufferTextureAttachmentError(
__func__, "This backend does not resolve a framebuffer attachment's layer onto its image.");
return;
}
}
AttachFramebufferTextureLayer(__func__, target, attachment, texture, level, layer, textureUploadTarget);
}
@@ -1433,11 +1458,19 @@ namespace MobileGL::MG_Impl::GLImpl {
// so a slice lands outside the image and the renderer asserts on the clear. Letting it
// through there would only move the failure downstream, so it is declined instead - layer
// zero always works, being the plain first-slice attachment.
const Bool backsLayeredAttachment = limits.SupportsPerLayerFramebufferAttachment;
// A cube map array additionally has no image shape at all in VkTextureManager, so on that
// backend it cannot be an attachment whatever the layer is.
const Bool isCubeMapArray = textureObject->GetTarget() == TextureTarget::TextureCubeMapArray;
if ((layer != 0 && !backsLayeredAttachment) || (isCubeMapArray && !backsLayeredAttachment)) {
// ...and it is a DIFFERENT question per target: a 2D/2D-multisample array layer is a Vulkan
// array layer, a 3D layer is a z slice, and a cube map array needs a cube-compatible image
// before it has any layer to name. Ask the backend about this texture's target rather than
// guessing from one blanket flag.
const TextureTarget layeredTextureTarget = textureObject->GetTarget();
const Bool backsThisTargetsLayers = limits.SupportsPerLayerFramebufferAttachment(layeredTextureTarget);
// Layer zero of a 3D or array texture is the plain first-slice attachment every backend can
// already express, so it stays legal even where per-layer selection is not backed. A cube map
// array has no such fallback: layer zero is still one face of one cube inside a
// cube-compatible image, so it needs the same support layer 5 does.
const Bool needsPerLayerSupport =
layer != 0 || layeredTextureTarget == TextureTarget::TextureCubeMapArray;
if (needsPerLayerSupport && !backsThisTargetsLayers) {
RecordUnsupportedFramebufferTextureAttachmentError(
__func__, "This backend does not resolve a framebuffer attachment's layer onto its image.");
return;