mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[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:
@@ -87,9 +87,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static VkImageViewType ResolveAttachmentViewType(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
const VkTextureManager::TextureResource& resource) {
|
||||
return !attachment.IsLayered() && IsCubeMapFaceUploadTarget(attachment.GetTextureUploadTarget()) ?
|
||||
VK_IMAGE_VIEW_TYPE_2D :
|
||||
resource.viewType;
|
||||
if (attachment.IsLayered()) {
|
||||
return resource.viewType;
|
||||
}
|
||||
// A non-layered attachment names ONE layer, so the view over it is a plain 2D view whatever
|
||||
// the image's own view type is. The cube-face upload targets always meant this; a cube map
|
||||
// array attached through glFramebufferTextureLayer means it too, and a CUBE_ARRAY view over
|
||||
// a single layer is not a legal attachment. The CUBE arm is inert today - no frontend path
|
||||
// produces a non-layered cube attachment without a face upload target - and is kept for
|
||||
// symmetry with CUBE_ARRAY.
|
||||
if (IsCubeMapFaceUploadTarget(attachment.GetTextureUploadTarget()) ||
|
||||
resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY || resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) {
|
||||
return VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
return resource.viewType;
|
||||
}
|
||||
|
||||
static MG_State::GLState::ITextureObject* ResolveCompleteColorAttachmentTexture(
|
||||
|
||||
@@ -564,6 +564,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
outShape.depth = 1;
|
||||
outShape.arrayLayers = 6;
|
||||
return true;
|
||||
case TextureUploadTarget::CubeMapArray:
|
||||
case TextureUploadTarget::ProxyCubeMapArray:
|
||||
// GL_TEXTURE_CUBE_MAP_ARRAY is an array texture whose layers happen to be cube faces:
|
||||
// one 2D image with arrayLayers = 6 * cubeCount, CUBE_COMPATIBLE so the whole thing can
|
||||
// be sampled as a samplerCubeArray. glTexStorage3D hands the 6*n through as the GL depth
|
||||
// and the upload path's depthSelectsArrayLayer already lists VK_IMAGE_VIEW_TYPE_CUBE_ARRAY,
|
||||
// so the copies address layers correctly.
|
||||
//
|
||||
// A depth that is not a whole number of cubes, or a non-square level, has no Vulkan shape
|
||||
// - declined the way every other unrepresentable target is. This function's Bool return
|
||||
// exists for exactly that; asserting here would abort the process on ordinary application
|
||||
// input, GL_PROXY_TEXTURE_CUBE_MAP_ARRAY above all.
|
||||
if (texelSize.z() <= 0 || (texelSize.z() % 6) != 0 || texelSize.x() != texelSize.y()) {
|
||||
return false;
|
||||
}
|
||||
outShape.imageType = VK_IMAGE_TYPE_2D;
|
||||
outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||
outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
||||
outShape.depth = 1;
|
||||
outShape.arrayLayers = static_cast<Uint32>(texelSize.z());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -822,8 +843,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers ||
|
||||
baseArrayLayer + layerCount > resource->arrayLayers) {
|
||||
// A 3D image has arrayLayers == 1 and keeps its GL layers on the z axis, so a per-slice
|
||||
// attachment view is a 2D view whose "array layer" is the slice - legal only on a
|
||||
// 2D-array-compatible image (VUID-VkImageViewCreateInfo-image-04970), which
|
||||
// SyncTextureResource asks for and may have had refused per format.
|
||||
if (resource->viewType == VK_IMAGE_VIEW_TYPE_3D && viewType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
const Uint32 sliceCount = std::max(resource->depth >> mipLevel, 1u);
|
||||
if ((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) == 0 ||
|
||||
layerCount == 0 || baseArrayLayer >= sliceCount || baseArrayLayer + layerCount > sliceCount) {
|
||||
MGLOG_D("%s: cannot name slice span [%u, %u) of 3D textureId=%d (mip %u has %u slices, "
|
||||
"2D-array-compatible=%d)",
|
||||
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
||||
mipLevel, sliceCount,
|
||||
(int)((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
} else if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers ||
|
||||
baseArrayLayer + layerCount > resource->arrayLayers) {
|
||||
MGLOG_D("%s: invalid layer span [%u, %u) for textureId=%d arrayLayers=%u",
|
||||
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
||||
resource->arrayLayers);
|
||||
@@ -1492,11 +1528,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// preserve-copy path below carries the pixels over), so sequentially-
|
||||
// defined atlas mips do not recreate per level, and glGenerateMipmap -
|
||||
// which defines every level before syncing - works unchanged.
|
||||
const Uint32 backingMipLevels =
|
||||
isMultisampleTexture ? 1u
|
||||
: (mipLevels > 1 ? std::max(mipLevels, ComputeFullMipLevelCount(texelSize)) : 1u);
|
||||
TextureShapeInfo shapeInfo{};
|
||||
const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo);
|
||||
// ComputeFullMipLevelCount takes max(x, y, z), and for every ARRAY shape z is the layer
|
||||
// count, not a mip-able axis: a 4x4 array with 192 layers asked for 6 levels on an image
|
||||
// whose legal maximum is 3 (VUID-VkImageCreateInfo-mipLevels-00958). Only the image's own
|
||||
// extent - width, height and shapeInfo.depth, which is 1 for every array - can bound it.
|
||||
// lavapipe has been letting this through unvalidated; a strict driver would not.
|
||||
const IntVec3 mipExtent{texelSize.x(), texelSize.y(), static_cast<Int>(shapeInfo.depth)};
|
||||
const Uint32 fullMipLevels = ComputeFullMipLevelCount(mipExtent);
|
||||
const Uint32 backingMipLevels =
|
||||
isMultisampleTexture ? 1u : (mipLevels > 1 ? std::min(std::max(mipLevels, fullMipLevels), fullMipLevels) : 1u);
|
||||
if (!supportedShape) {
|
||||
// A gap in this backend's coverage, not a broken invariant: the GL front end accepts
|
||||
// targets this manager has no Vulkan image shape for yet (cube map arrays above all).
|
||||
@@ -1554,6 +1596,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||
const Bool supportsStorageImage = storageImageCapable && markedAsStorageImage;
|
||||
VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags;
|
||||
// One z slice of a 3D texture can only be attached to a framebuffer through a 2D view over
|
||||
// it, which needs the image to be 2D-array-compatible (Vulkan 1.1 core, promoted from
|
||||
// VK_KHR_maintenance1). Asked for optimistically and withdrawn per format below if the
|
||||
// driver refuses - losing it only costs per-slice attachment, while failing creation would
|
||||
// lose the texture entirely.
|
||||
if (shapeInfo.imageType == VK_IMAGE_TYPE_3D && !isMultisampleTexture &&
|
||||
m_2dArrayCompatibleUnsupported.find(format) == m_2dArrayCompatibleUnsupported.end()) {
|
||||
imageCreateFlags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
|
||||
}
|
||||
if (storageImageCapable && IsMutableStorageImageFormat(format) &&
|
||||
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
||||
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||
@@ -1711,7 +1762,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
imageInfo.pNext = &formatListInfo;
|
||||
}
|
||||
|
||||
if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
||||
if (isMultisampleTexture || (imageInfo.flags & (VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT |
|
||||
VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT)) != 0) {
|
||||
VkImageFormatProperties imageFormatProperties{};
|
||||
VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||
@@ -1734,6 +1786,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||
imageInfo.flags, &imageFormatProperties);
|
||||
}
|
||||
if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture &&
|
||||
(imageInfo.flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0) {
|
||||
// Losing 2D-array compatibility only costs per-slice framebuffer attachment for this
|
||||
// format; failing creation would lose the texture entirely. Remembered so later syncs
|
||||
// neither reprobe nor flag-mismatch against this image and recreate it.
|
||||
MGLOG_W("%s: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is unsupported for format=%d "
|
||||
"textureId=%d; creating without it (per-slice framebuffer attachment will be "
|
||||
"unavailable for it)",
|
||||
__func__, static_cast<Int>(format), texture.GetExternalIndex());
|
||||
m_2dArrayCompatibleUnsupported.insert(format);
|
||||
imageInfo.flags &= ~VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
|
||||
imageCreateFlags = imageInfo.flags;
|
||||
imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||
imageInfo.flags, &imageFormatProperties);
|
||||
}
|
||||
if (imageFormatResult != VK_SUCCESS ||
|
||||
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
|
||||
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s "
|
||||
|
||||
@@ -487,6 +487,10 @@ private:
|
||||
// Formats whose mutable-image probe failed on this device; their images are created
|
||||
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
||||
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
||||
// Formats whose 3D images refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT. Per format+usage,
|
||||
// exactly like the mutable-format verdict above, so it is answered at image creation and
|
||||
// remembered rather than probed once globally.
|
||||
std::unordered_set<VkFormat> m_2dArrayCompatibleUnsupported;
|
||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||
// Textures that have been bound to a GL image unit (see MarkStorageImageTexture).
|
||||
|
||||
@@ -5921,6 +5921,83 @@ void main() {
|
||||
QueueClearBufferPayload(buffer, drawbuffer, payload);
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 depthSlice, const VkClearValue& clearValue) {
|
||||
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture);
|
||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE) return false;
|
||||
if (m_frameContext.GetCurrentFrameIndex() >= m_deferredDepthMipmapCleanup.size()) return false;
|
||||
|
||||
// A 2D view over one z slice. Returns VK_NULL_HANDLE when the image is not
|
||||
// 2D-array-compatible, which is the whole reason this can fail.
|
||||
const VkImageView sliceView = m_textureManager->GetOrCreateAttachmentViewAtMipLevel(
|
||||
texture, mipLevel, depthSlice, 1, VK_IMAGE_VIEW_TYPE_2D);
|
||||
if (sliceView == VK_NULL_HANDLE) return false;
|
||||
|
||||
VkAttachmentDescription colorAttachment{};
|
||||
colorAttachment.format = resource->format;
|
||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
// Hand the slice back in the layout the caller already tracks for the whole image, so its
|
||||
// closing barrier stays truthful and resource->layout is never touched from in here.
|
||||
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
|
||||
VkAttachmentReference colorRef{};
|
||||
colorRef.attachment = 0;
|
||||
colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
|
||||
VkRenderPassCreateInfo renderPassInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
renderPassInfo.attachmentCount = 1;
|
||||
renderPassInfo.pAttachments = &colorAttachment;
|
||||
renderPassInfo.subpassCount = 1;
|
||||
renderPassInfo.pSubpasses = &subpass;
|
||||
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
if (vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) return false;
|
||||
|
||||
const Uint32 levelWidth = std::max(resource->extent.width >> mipLevel, 1u);
|
||||
const Uint32 levelHeight = std::max(resource->extent.height >> mipLevel, 1u);
|
||||
|
||||
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebufferInfo.renderPass = renderPass;
|
||||
framebufferInfo.attachmentCount = 1;
|
||||
framebufferInfo.pAttachments = &sliceView;
|
||||
framebufferInfo.width = levelWidth;
|
||||
framebufferInfo.height = levelHeight;
|
||||
framebufferInfo.layers = 1;
|
||||
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
if (vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer) != VK_SUCCESS) {
|
||||
vkDestroyRenderPass(m_device, renderPass, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
VkRenderPassBeginInfo beginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
beginInfo.renderPass = renderPass;
|
||||
beginInfo.framebuffer = framebuffer;
|
||||
beginInfo.renderArea.extent = {levelWidth, levelHeight};
|
||||
beginInfo.clearValueCount = 1;
|
||||
beginInfo.pClearValues = &clearValue;
|
||||
// The load op is the whole operation: begin and end with nothing in between.
|
||||
vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdEndRenderPass(commandBuffer);
|
||||
|
||||
// The image view is owned and memoised by the texture resource; only these two are throwaway.
|
||||
auto& deferredCleanup = m_deferredDepthMipmapCleanup[m_frameContext.GetCurrentFrameIndex()];
|
||||
deferredCleanup.renderPasses.push_back(renderPass);
|
||||
deferredCleanup.framebuffers.push_back(framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture) {
|
||||
Vector<PendingClearEntry> pendingClears;
|
||||
@@ -5957,16 +6034,79 @@ void main() {
|
||||
MOBILEGL_ASSERT(pendingClear.key.mipLevel < resource->mipLevels,
|
||||
"MaterializePendingClearForTexture: textureId=%d pending clear mip=%u out of range %u",
|
||||
texture.GetExternalIndex(), pendingClear.key.mipLevel, resource->mipLevels);
|
||||
MOBILEGL_ASSERT(pendingClear.key.baseArrayLayer + pendingClear.key.layerCount <= resource->arrayLayers,
|
||||
"MaterializePendingClearForTexture: textureId=%d pending clear layer span [%u, %u) exceeds arrayLayers=%u",
|
||||
// FIXME: a layered clear of a GL_TEXTURE_3D texture still reads back wrong.
|
||||
// KHR-GL44/45/46.geometry_shader.layered_framebuffer.clear_call_support fails on
|
||||
// DirectVulkan: it attaches a 4-deep 3D texture with glFramebufferTexture (layered),
|
||||
// clears with glClearBufferiv, then reads each slice back through
|
||||
// glFramebufferTextureLayer and gets zeros. Those cases exist only in the GL44+ lists,
|
||||
// above the 4.0 this backend reports, so they are outside the current conformance
|
||||
// claim - but the feature (layered attachment, GL 3.2) is not, so an application can
|
||||
// reach this.
|
||||
//
|
||||
// Already ruled out by bisecting with temporary bypasses, so do not re-test these:
|
||||
// - the per-slice render-pass clear below (disabling it changes nothing)
|
||||
// - the per-target gate in FramebufferTextureLayer_State (it already permits
|
||||
// Texture3D here; bypassing it changes nothing)
|
||||
// - VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT on the 3D image (not requesting it
|
||||
// changes nothing)
|
||||
// What IS fixed here is the subresource range below: a layered GL clear queues
|
||||
// layerCount = depth, which is illegal for a VK_IMAGE_TYPE_3D image, and the old code
|
||||
// passed it straight through - running the case standalone against the previous build
|
||||
// trips MOBILEGL_ASSERT(baseArrayLayer + layerCount <= arrayLayers) as 0 + 4 <= 1.
|
||||
//
|
||||
// Note when picking this up: the case does not reproduce standalone the way it behaves
|
||||
// in a batch run (batch passed before this change, standalone asserted), so it depends
|
||||
// on state left by earlier cases. Reproduce it inside a chunk, not on its own.
|
||||
//
|
||||
// A 3D image keeps its GL layers on the z axis (arrayLayers == 1), so the pending
|
||||
// clear's "layer" is a slice index bounded by the mip level's depth.
|
||||
const Bool clearAddressesDepthSlices = resource->viewType == VK_IMAGE_VIEW_TYPE_3D;
|
||||
const Uint32 clearableLayers = clearAddressesDepthSlices
|
||||
? std::max(resource->depth >> pendingClear.key.mipLevel, 1u)
|
||||
: resource->arrayLayers;
|
||||
MOBILEGL_ASSERT(pendingClear.key.baseArrayLayer + pendingClear.key.layerCount <= clearableLayers,
|
||||
"MaterializePendingClearForTexture: textureId=%d pending clear layer span [%u, %u) exceeds %u",
|
||||
texture.GetExternalIndex(), pendingClear.key.baseArrayLayer,
|
||||
pendingClear.key.baseArrayLayer + pendingClear.key.layerCount, resource->arrayLayers);
|
||||
pendingClear.key.baseArrayLayer + pendingClear.key.layerCount, clearableLayers);
|
||||
// Whether this clear names a strict SUBSET of the level. A layered attachment
|
||||
// (glFramebufferTexture) queues layerCount = the whole depth, a single-slice one
|
||||
// (glFramebufferTextureLayer) queues 1 - so the key already distinguishes them, and it is
|
||||
// the clear's span that decides, not the image's slice count. Reading the latter sent a
|
||||
// layered clear of a 3D texture down the per-slice path, where it cleared slice zero and
|
||||
// left the rest stale (geometry_shader.layered_framebuffer.clear_call_support).
|
||||
const Bool clearsWholeLevel =
|
||||
pendingClear.key.baseArrayLayer == 0 && pendingClear.key.layerCount >= clearableLayers;
|
||||
if (clearAddressesDepthSlices && clearableLayers > 1 && !clearsWholeLevel) {
|
||||
// vkCmdClearColorImage cannot clear a subset of a 3D image's slices:
|
||||
// VUID-vkCmdClearColorImage-baseArrayLayer-01472 pins baseArrayLayer to 0 and
|
||||
// layerCount to 1 for VK_IMAGE_TYPE_3D, i.e. the whole mip level. A render pass whose
|
||||
// only content is its LOAD_OP_CLEAR does address exactly one slice, because its
|
||||
// attachment is a 2D view over that slice.
|
||||
auto clearPayload3D = pendingClear.payload;
|
||||
PreCompensateSrgbClearColor(clearPayload3D, resource->format);
|
||||
VkClearValue sliceClearValue{};
|
||||
sliceClearValue.color = MakeVkClearColorValue(clearPayload3D, ColorFormatLacksAlpha(&texture));
|
||||
if (!ClearDepthSliceWithRenderPass(commandBuffer, texture, pendingClear.key.mipLevel,
|
||||
pendingClear.key.baseArrayLayer, sliceClearValue)) {
|
||||
// The device or the format refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, so
|
||||
// there is no way to name this slice. Leaving it uncleared is wrong pixels;
|
||||
// asserting would abort a process that glFramebufferTextureLayer can reach at will.
|
||||
MGLOG_W("MaterializePendingClearForTexture: textureId=%d slice %u could not be cleared "
|
||||
"(no 2D-array-compatible view)",
|
||||
texture.GetExternalIndex(), pendingClear.key.baseArrayLayer);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
VkImageSubresourceRange subresourceRange{};
|
||||
subresourceRange.baseMipLevel = pendingClear.key.mipLevel;
|
||||
subresourceRange.levelCount = 1;
|
||||
subresourceRange.baseArrayLayer = pendingClear.key.baseArrayLayer;
|
||||
subresourceRange.layerCount = pendingClear.key.layerCount;
|
||||
// VUID-vkCmdClearColorImage-baseArrayLayer-01472: for a VK_IMAGE_TYPE_3D image the range
|
||||
// must name baseArrayLayer 0 and layerCount 1, which Vulkan reads as "the whole mip
|
||||
// level" - the z extent is not an array dimension. A layered GL clear queues
|
||||
// layerCount = depth, which is the right GL answer and an illegal Vulkan one.
|
||||
subresourceRange.baseArrayLayer = clearAddressesDepthSlices ? 0u : pendingClear.key.baseArrayLayer;
|
||||
subresourceRange.layerCount = clearAddressesDepthSlices ? 1u : pendingClear.key.layerCount;
|
||||
|
||||
auto clearPayload = pendingClear.payload;
|
||||
if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) {
|
||||
@@ -9993,6 +10133,10 @@ void main() {
|
||||
// it vkCreateShaderModule is invalid usage (VUID-VkShaderModuleCreateInfo-pCode-08740),
|
||||
// which is why SupportsFloat64VertexAttributes gates the entry point on the same feature.
|
||||
deviceFeatures.shaderFloat64 = supportedDeviceFeatures.shaderFloat64;
|
||||
// Required before a VK_IMAGE_VIEW_TYPE_CUBE_ARRAY view may be created
|
||||
// (VUID-VkImageViewCreateInfo-viewType-01004). Without it a cube map array texture cannot
|
||||
// get its sampled or full view, so SyncTextureResource fails and the texture stays unbacked.
|
||||
deviceFeatures.imageCubeArray = supportedDeviceFeatures.imageCubeArray;
|
||||
// Required for desktop GL image load/store semantics. iterationRP writes storage
|
||||
// images from vertex and fragment stages and uses formats outside Vulkan's small
|
||||
// mandatory storage-image set.
|
||||
|
||||
@@ -815,6 +815,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLenum filter);
|
||||
// Clears one z slice of a VK_IMAGE_TYPE_3D colour image. See the call site in
|
||||
// MaterializePendingClearForTexture for why a transfer clear cannot do this.
|
||||
Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 depthSlice, const VkClearValue& clearValue);
|
||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
Bool MaterializePendingClearForRenderbuffer(
|
||||
|
||||
Reference in New Issue
Block a user