mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix] (DirectVulkan): give an unbound multisample sampler a placeholder, clear a multisample texture through a load-op pass, and plumb glSampleMaski into the pipeline
This commit is contained in:
@@ -203,6 +203,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.sampleShadingEnable, sizeof(payload.sampleShadingEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.minSampleShading, sizeof(payload.minSampleShading)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.sampleMask, sizeof(payload.sampleMask)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||
XXHASH_VERIFY(
|
||||
@@ -443,6 +444,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Ignored by Vulkan unless sampleShadingEnable is set, but written unconditionally so the
|
||||
// struct's bytes match the hash the payload was keyed by.
|
||||
ms.minSampleShading = payload.minSampleShading;
|
||||
// GL_SAMPLE_MASK / glSampleMaski. Left at nullptr - which Vulkan reads as all-ones - until
|
||||
// now, so glSampleMaski was a silent no-op on this backend while DirectGLES forwarded it.
|
||||
// The pointer has to outlive the vkCreateGraphicsPipelines call, which the payload does.
|
||||
ms.pSampleMask = &payload.sampleMask;
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
@@ -44,6 +44,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// (VUID-VkPipelineMultisampleStateCreateInfo-sampleShadingEnable-00784).
|
||||
Bool sampleShadingEnable = false;
|
||||
Float minSampleShading = 0.0f;
|
||||
// glEnable(GL_SAMPLE_MASK) + glSampleMaski, the fixed-function coverage mask. One
|
||||
// word is the whole mask: GL_MAX_SAMPLE_MASK_WORDS is 1 on both backends, and Vulkan
|
||||
// reads ceil(rasterizationSamples / 32) words. Pipeline state like the two above -
|
||||
// Vulkan has no dynamic sample mask before VK_EXT_extended_dynamic_state3 - so it is
|
||||
// hashed with them, and 0xffffffff (the GL default, and what a null pSampleMask
|
||||
// means) has to keep producing the pipeline it always did.
|
||||
Uint32 sampleMask = 0xffffffffu;
|
||||
Uint32 subpass = 0;
|
||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
Bool primitiveRestartEnable = false;
|
||||
|
||||
@@ -37,6 +37,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// glGenTextures ever hands this out, and nothing looks a placeholder up by name - so the
|
||||
// id only has to stay clear of the application's, exactly like the sampled fallback's.
|
||||
constexpr Uint kUnboundStorageImageExternalIndex = 0xFFFFFF01u;
|
||||
// The multisample sampled fallbacks. Separate ids for the same reason as the two above:
|
||||
// they must not collide with anything glGenTextures can hand out.
|
||||
constexpr Uint kFallbackTexture2DMultisampleExternalIndex = 0xFFFFFF02u;
|
||||
constexpr Uint kFallbackTexture2DMultisampleArrayExternalIndex = 0xFFFFFF03u;
|
||||
|
||||
// MobileGL's own stand-in textures, by the reserved ids above. Nothing an application can
|
||||
// do reaches one, so anything keyed on the GL object an application bound - image-unit
|
||||
@@ -44,7 +48,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool IsPlaceholderTexture(const MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) return false;
|
||||
const Uint index = static_cast<Uint>(texture->GetExternalIndex());
|
||||
return index == kFallbackTexture2DExternalIndex || index == kUnboundStorageImageExternalIndex;
|
||||
return index == kFallbackTexture2DExternalIndex || index == kUnboundStorageImageExternalIndex ||
|
||||
index == kFallbackTexture2DMultisampleExternalIndex ||
|
||||
index == kFallbackTexture2DMultisampleArrayExternalIndex;
|
||||
}
|
||||
|
||||
// The R32 member of each numeric class. Every one of the three is a MANDATORY-support
|
||||
@@ -369,6 +375,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_textureManager = nullptr;
|
||||
m_samplerManager = nullptr;
|
||||
m_fallbackTexture2D.reset();
|
||||
m_fallbackTexture2DMultisample.reset();
|
||||
m_fallbackTexture2DMultisampleArray.reset();
|
||||
}
|
||||
|
||||
void UniformManager::BeginFrame(Uint32 frameIndex) {
|
||||
@@ -1377,11 +1385,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackTexture(TextureTarget target) const {
|
||||
// The fallback is a single-sampled 2D image, so it can only stand in for a sampler that
|
||||
// would accept one. A multisample sampler in particular cannot: its descriptor demands a
|
||||
// multisample view, and handing it this one is invalid Vulkan, not a degraded picture.
|
||||
// Report that there is no fallback and let the caller decline the draw - aborting the
|
||||
// process over an unbound sampler is never the right answer.
|
||||
// A multisample sampler cannot be served by the single-sampled 2D image below - its
|
||||
// descriptor demands a multisample view - so it gets its own placeholder rather than no
|
||||
// placeholder at all. Without one, ResolveSamplerDescriptor declined and
|
||||
// BindProgramUniformBuffers dropped the WHOLE draw, which is how every
|
||||
// sample_variables.*.samples_0 body failed: the CTS's resolve program declares both a
|
||||
// sampler2D and a sampler2DMS and deliberately points the unused one at an empty texture
|
||||
// unit, and at samples_0 the unused one is the sampler2DMS. GL says sampling an
|
||||
// incomplete texture is undefined, not fatal, so the draw has to happen.
|
||||
if (target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray) {
|
||||
return GetFallbackMultisampleTexture(target);
|
||||
}
|
||||
if (target != TextureTarget::Texture2D && target != TextureTarget::TextureRectangle) {
|
||||
MGLOG_E_ONCE("UniformManager::GetFallbackTexture: no fallback exists for target=%d",
|
||||
static_cast<Int>(target));
|
||||
@@ -1405,6 +1420,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return m_fallbackTexture2D;
|
||||
}
|
||||
|
||||
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackMultisampleTexture(
|
||||
TextureTarget target) const {
|
||||
const Bool arrayed = target == TextureTarget::Texture2DMultisampleArray;
|
||||
auto& slot = arrayed ? m_fallbackTexture2DMultisampleArray : m_fallbackTexture2DMultisample;
|
||||
if (slot != nullptr) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
const TextureUploadTarget uploadTarget = arrayed ? TextureUploadTarget::Texture2DMultisampleArray
|
||||
: TextureUploadTarget::Texture2DMultisample;
|
||||
SharedPtr<MG_State::GLState::TextureObjectMipmap> texture;
|
||||
if (arrayed) {
|
||||
texture = MakeShared<MG_State::GLState::TextureObject2DMultisampleArray>(
|
||||
kFallbackTexture2DMultisampleArrayExternalIndex);
|
||||
} else {
|
||||
texture = MakeShared<MG_State::GLState::TextureObject2DMultisample>(
|
||||
kFallbackTexture2DMultisampleExternalIndex);
|
||||
}
|
||||
texture->SetInternalFormat(TextureInternalFormat::RGBA8);
|
||||
// TWO samples, never one. VUID-RuntimeSpirv-samples-08726 forbids an OpTypeImage with
|
||||
// MS = 1 from reading a VK_SAMPLE_COUNT_1_BIT image, which is exactly the hazard
|
||||
// VkTextureManager::SyncTextureResource's one-sample floor exists to avoid; a placeholder
|
||||
// that re-created it would be worse than none.
|
||||
texture->SetSamples(2);
|
||||
texture->SetFixedSampleLocations(true);
|
||||
// No upload, and MarkStorageDirty(dirty = false) to say so: a multisample image cannot be
|
||||
// written by a transfer at all - it deliberately carries no TRANSFER_DST usage - so unlike
|
||||
// the 2D fallback this one cannot be given (0, 0, 0, 1) content. Its texels are undefined,
|
||||
// which is precisely what GL 4.6 core 8.17 promises for a texelFetch on a multisample
|
||||
// texture that is not complete. The point of the placeholder is that the DRAW happens.
|
||||
texture->AllocateStorage(uploadTarget, 0, {.texelSize = {1, 1, 1}, .byteSize = 0});
|
||||
texture->TruncateMipmapLevels(uploadTarget, 1);
|
||||
texture->MarkStorageDirty(uploadTarget, 0, false);
|
||||
slot = texture;
|
||||
MGLOG_D("UniformManager::GetFallbackMultisampleTexture: created placeholder target=%d",
|
||||
static_cast<Int>(target));
|
||||
return slot;
|
||||
}
|
||||
|
||||
VkBufferView UniformManager::AcquireUnboundTexelBufferView(VkFormat declaredFormat,
|
||||
SamplerNumericDomain numericDomain, Bool storage) {
|
||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "AcquireUnboundTexelBufferView: buffer manager is null");
|
||||
@@ -1589,11 +1643,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// ResolveSamplerDescriptor will substitute the fallback texture for this binding;
|
||||
// include it in the sampled set so the pre-render-pass sync/transition pass covers
|
||||
// its first use instead of leaving that work to happen inside an active pass.
|
||||
if (preferredTarget != TextureTarget::Texture2D &&
|
||||
preferredTarget != TextureTarget::TextureRectangle) {
|
||||
// Ask GetFallbackTexture rather than re-listing the targets it serves: that list grew
|
||||
// a multisample arm and the two must not drift apart.
|
||||
texture = GetFallbackTexture(preferredTarget).get();
|
||||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
texture = GetFallbackTexture(preferredTarget).get();
|
||||
// The substitution changed the texture, so the "no override" arm of the effective
|
||||
// sampler has to follow it to the fallback's own.
|
||||
if (!samplerOverride) {
|
||||
|
||||
@@ -180,6 +180,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const MG_State::GLState::ProgramObject& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element);
|
||||
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
||||
// The multisample arm of GetFallbackTexture. Separate object per target and no upload
|
||||
// path: a multisample image cannot be written by a transfer, so its texels stay undefined
|
||||
// - which is what GL promises for a texelFetch on an incomplete multisample texture.
|
||||
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackMultisampleTexture(TextureTarget target) const;
|
||||
// ---- placeholders for UNBOUND image-backed descriptors -------------------------
|
||||
// GL lets a program declare `samplerBuffer`, `imageBuffer` or `image2D` and bind nothing
|
||||
// to the unit it names: the fetch is then undefined (GL 4.6 core 8.9 for an incomplete
|
||||
@@ -293,6 +297,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkTextureManager* m_textureManager = nullptr;
|
||||
VkSamplerManager* m_samplerManager = nullptr;
|
||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2DMultisample;
|
||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2DMultisampleArray;
|
||||
// See AcquireUnboundTexelBufferView / GetUnboundStorageImageTexture. Both are lazily
|
||||
// populated, never evicted (a program's declared formats are a fixed, tiny set) and torn
|
||||
// down with the manager. The texel views are keyed by format AND by storage-vs-sampled
|
||||
|
||||
@@ -4767,9 +4767,9 @@ void main() {
|
||||
// build in GetOrCreatePipeline - any new GL-state read there must be added here:
|
||||
// - capability bits: CullFace, DepthTest, PolygonOffsetFill (mode gating rides
|
||||
// the memo's mode key), RasterizerDiscard, ColorLogicOp, StencilTest,
|
||||
// PrimitiveRestart(+FixedIndex), SampleShading, plus the depth write mask
|
||||
// PrimitiveRestart(+FixedIndex), SampleShading, SampleMask, plus the depth write mask
|
||||
// - patch vertices, polygon mode, cull face mode, depth func, logic op,
|
||||
// min sample shading
|
||||
// min sample shading, the glSampleMaski word
|
||||
// - front/back stencil ops + compare funcs (ref/mask are dynamic state)
|
||||
// - per draw buffer up to the render pass's colour span: indexed blend enable,
|
||||
// blend factors/equations, indexed colour write mask (broadcast from index 0
|
||||
@@ -4795,6 +4795,7 @@ void main() {
|
||||
capabilityBits |= p.PrimitiveRestartFixedIndexEnabled ? 1ull << 7 : 0;
|
||||
capabilityBits |= p.DepthMask ? 1ull << 8 : 0;
|
||||
capabilityBits |= p.SampleShadingEnabled ? 1ull << 9 : 0;
|
||||
capabilityBits |= p.SampleMaskEnabled ? 1ull << 10 : 0;
|
||||
Uint64 hash = CombinePipelineStateWord(0x243F6A8885A308D3ull, capabilityBits);
|
||||
// glMinSampleShading. Hashed by BITS, not by value: this memo compares hashes rather than
|
||||
// versions, so an unhashed float would let a pipeline built at one rate be handed back
|
||||
@@ -4804,6 +4805,13 @@ void main() {
|
||||
std::memcpy(&minSampleShadingBits, &p.MinSampleShadingValue, sizeof(minSampleShadingBits));
|
||||
hash = CombinePipelineStateWord(hash, static_cast<Uint64>(minSampleShadingBits));
|
||||
}
|
||||
// glSampleMaski's word, for the same reason glMinSampleShading's bits are hashed above:
|
||||
// this memo compares hashes, not versions, so a mask that moved between two otherwise
|
||||
// identical draws has to key a different pipeline. Hashed unconditionally rather than only
|
||||
// while GL_SAMPLE_MASK is enabled - the enable bit is already in capabilityBits, and
|
||||
// folding one more word costs nothing on a path that only recomputes when the
|
||||
// pipeline-state version moved.
|
||||
hash = CombinePipelineStateWord(hash, static_cast<Uint64>(p.SampleMaskValue));
|
||||
hash = CombinePipelineStateWord(hash, static_cast<Uint64>(p.PatchVertices));
|
||||
// The default tessellation levels belong here for the same reason PatchVertices does:
|
||||
// when a program has an evaluation stage and no control stage, both are compiled into the
|
||||
@@ -5199,6 +5207,12 @@ void main() {
|
||||
.sampleShadingEnable = m_sampleRateShadingFeatureEnabled &&
|
||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleShading),
|
||||
.minSampleShading = MG_State::pGLContext->GetMinSampleShadingValue(),
|
||||
// GL_SAMPLE_MASK off means full coverage, which is what an all-ones mask says and what
|
||||
// a null pSampleMask used to say by omission. One word: MaxSampleMaskWords is clamped
|
||||
// to 1 on both backends, so glSampleMaski only ever writes index 0.
|
||||
.sampleMask = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleMask)
|
||||
? MG_State::pGLContext->GetRenderStateParameters().SampleMaskValue
|
||||
: 0xffffffffu,
|
||||
.subpass = 0,
|
||||
.topology = vkTopology,
|
||||
.primitiveRestartEnable = primitiveRestartEnabled,
|
||||
@@ -7610,7 +7624,8 @@ void main() {
|
||||
|
||||
Bool VulkanRenderer::ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 depthSlice, const VkClearValue& clearValue) {
|
||||
Uint32 depthSlice, const VkClearValue& clearValue,
|
||||
VkImageLayout finalLayout) {
|
||||
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;
|
||||
@@ -7623,7 +7638,11 @@ void main() {
|
||||
|
||||
VkAttachmentDescription colorAttachment{};
|
||||
colorAttachment.format = resource->format;
|
||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
// The image's own count, not a hardcoded one: a render-pass attachment must match the
|
||||
// image it is given (VUID-VkFramebufferCreateInfo-pAttachments-00880), and this helper is
|
||||
// now also the multisample path - a multisample image carries no TRANSFER_DST usage, so a
|
||||
// load-op clear is the only legal way to clear it at all.
|
||||
colorAttachment.samples = resource->sampleCount;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
@@ -7631,7 +7650,7 @@ void main() {
|
||||
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;
|
||||
colorAttachment.finalLayout = finalLayout;
|
||||
|
||||
VkAttachmentReference colorRef{};
|
||||
colorRef.attachment = 0;
|
||||
@@ -7699,9 +7718,26 @@ void main() {
|
||||
"MaterializePendingClearForTexture requires no active render pass on the target buffer");
|
||||
|
||||
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture);
|
||||
MOBILEGL_ASSERT(resource != nullptr,
|
||||
"MaterializePendingClearForTexture: SyncTextureAndGetDescriptor failed for textureId=%d",
|
||||
texture.GetExternalIndex());
|
||||
if (resource == nullptr) {
|
||||
// Declined sync (an incomplete texture, say). Nothing to clear into, and every line
|
||||
// below dereferences this - the assert that used to stand here is compiled out of
|
||||
// every build past DEBUG.
|
||||
MGLOG_E_ONCE("MaterializePendingClearForTexture: no texture resource for textureId=%d; the queued clears "
|
||||
"stay queued",
|
||||
texture.GetExternalIndex());
|
||||
return false;
|
||||
}
|
||||
|
||||
// A multisample image is not a transfer target: SyncTextureResource deliberately withholds
|
||||
// TRANSFER_DST/TRANSFER_SRC from every one of them, so the vkCmdClearColorImage below -
|
||||
// and the TRANSFER_DST transition ahead of it - are invalid usage
|
||||
// (VUID-vkCmdClearColorImage-image-00002) on exactly the shape a
|
||||
// glClearBufferfv-then-sample sequence produces. Clear it the one way that is legal at
|
||||
// any sample count instead: a throwaway render pass whose whole content is its load-op
|
||||
// clear, which is also what the 3D-slice case below already does.
|
||||
if (resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
|
||||
return MaterializeMultisamplePendingClear(commandBuffer, texture, *resource, pendingClears);
|
||||
}
|
||||
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags srcAccessMask = 0;
|
||||
@@ -7841,6 +7877,77 @@ void main() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::MaterializeMultisamplePendingClear(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture,
|
||||
VkTextureManager::TextureResource& resource,
|
||||
const Vector<PendingClearEntry>& pendingClears) {
|
||||
// Colour only. GL can queue a depth/stencil clear on a multisample texture too, and the
|
||||
// load-op idiom would serve it just as well, but this helper attaches its view as a
|
||||
// COLOUR attachment; declining is honest and leaves the queue intact for a later path.
|
||||
if ((resource.aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) {
|
||||
MGLOG_E_ONCE("MaterializeMultisamplePendingClear: textureId=%d is a multisample depth/stencil texture; "
|
||||
"its queued clear cannot be materialised out of a render pass yet",
|
||||
texture.GetExternalIndex());
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool allCleared = true;
|
||||
for (const auto& pendingClear : pendingClears) {
|
||||
if (pendingClear.key.mipLevel >= resource.mipLevels) {
|
||||
MGLOG_E_ONCE("MaterializeMultisamplePendingClear: textureId=%d pending clear mip=%u out of range %u",
|
||||
texture.GetExternalIndex(), pendingClear.key.mipLevel, resource.mipLevels);
|
||||
allCleared = false;
|
||||
continue;
|
||||
}
|
||||
auto clearPayload = pendingClear.payload;
|
||||
PreCompensateSrgbClearColor(clearPayload, resource.format);
|
||||
VkClearValue clearValue{};
|
||||
clearValue.color = MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(&texture));
|
||||
|
||||
// A multisample texture has exactly one level and, for the 2D target, one layer; the
|
||||
// array target's layers are cleared one at a time, which is what this helper's
|
||||
// per-layer view gives us.
|
||||
const Uint32 firstLayer = pendingClear.key.baseArrayLayer;
|
||||
const Uint32 layerCount = std::max(pendingClear.key.layerCount, 1u);
|
||||
for (Uint32 layer = firstLayer; layer < firstLayer + layerCount; ++layer) {
|
||||
if (layer >= resource.arrayLayers) break;
|
||||
// COLOR_ATTACHMENT_OPTIMAL, not TRANSFER_DST: the image never has transfer usage,
|
||||
// and a render target is where it came from and where it is going.
|
||||
if (!ClearDepthSliceWithRenderPass(commandBuffer, texture, pendingClear.key.mipLevel, layer,
|
||||
clearValue, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)) {
|
||||
MGLOG_E_ONCE("MaterializeMultisamplePendingClear: textureId=%d layer %u could not be cleared",
|
||||
texture.GetExternalIndex(), layer);
|
||||
allCleared = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!allCleared) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The load-op clear left every touched layer in COLOR_ATTACHMENT_OPTIMAL (each pass's
|
||||
// finalLayout), so that - not the tracked layout on entry - is what the closing barrier
|
||||
// has to start from.
|
||||
// TransitionImageLayout takes the tracked layout by reference and updates it, so seeding
|
||||
// it is both how the barrier learns its source and how resource->layout ends up right.
|
||||
resource.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
const Bool ok = VkTextureManager::TransitionImageLayout(
|
||||
commandBuffer, resource.image, resource.layout,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||
resource.aspect, 0, resource.mipLevels);
|
||||
if (!ok) {
|
||||
MGLOG_E_ONCE("MaterializeMultisamplePendingClear: failed to transition textureId=%d to the sampled layout",
|
||||
texture.GetExternalIndex());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_clearManager->PopPendingClear(&texture);
|
||||
MGLOG_D("MaterializeMultisamplePendingClear: textureId=%d pending clear materialised through a load-op pass",
|
||||
texture.GetExternalIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::MaterializePendingClearForRenderbuffer(
|
||||
VkCommandBuffer commandBuffer, const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer) {
|
||||
if (renderbuffer == nullptr) {
|
||||
|
||||
@@ -1279,13 +1279,25 @@ 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.
|
||||
// Clears one layer of a colour image through a throwaway render pass whose entire content
|
||||
// is its LOAD_OP_CLEAR. Two callers, both of which a transfer clear cannot serve: a z
|
||||
// slice of a VK_IMAGE_TYPE_3D image (vkCmdClearColorImage cannot name one), and a
|
||||
// MULTISAMPLE image (which carries no TRANSFER_DST usage at all). `finalLayout` is the
|
||||
// layout the caller already tracks for the whole image, so this never has to touch
|
||||
// resource->layout.
|
||||
Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 depthSlice, const VkClearValue& clearValue);
|
||||
Uint32 depthSlice, const VkClearValue& clearValue,
|
||||
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
// The multisample arm of the above. Split out rather than branched inline because it
|
||||
// shares none of the transfer path: a multisample image carries no TRANSFER_DST usage, so
|
||||
// neither the TRANSFER_DST transition nor vkCmdClearColorImage is legal on one.
|
||||
Bool MaterializeMultisamplePendingClear(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture,
|
||||
VkTextureManager::TextureResource& resource,
|
||||
const Vector<PendingClearEntry>& pendingClears);
|
||||
Bool MaterializePendingClearForRenderbuffer(
|
||||
VkCommandBuffer commandBuffer,
|
||||
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||
|
||||
Reference in New Issue
Block a user