mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +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.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.sampleShadingEnable, sizeof(payload.sampleShadingEnable)));
|
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.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.subpass, sizeof(payload.subpass)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
@@ -443,6 +444,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Ignored by Vulkan unless sampleShadingEnable is set, but written unconditionally so the
|
// Ignored by Vulkan unless sampleShadingEnable is set, but written unconditionally so the
|
||||||
// struct's bytes match the hash the payload was keyed by.
|
// struct's bytes match the hash the payload was keyed by.
|
||||||
ms.minSampleShading = payload.minSampleShading;
|
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};
|
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||||
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE;
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// (VUID-VkPipelineMultisampleStateCreateInfo-sampleShadingEnable-00784).
|
// (VUID-VkPipelineMultisampleStateCreateInfo-sampleShadingEnable-00784).
|
||||||
Bool sampleShadingEnable = false;
|
Bool sampleShadingEnable = false;
|
||||||
Float minSampleShading = 0.0f;
|
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;
|
Uint32 subpass = 0;
|
||||||
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
Bool primitiveRestartEnable = false;
|
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
|
// 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.
|
// id only has to stay clear of the application's, exactly like the sampled fallback's.
|
||||||
constexpr Uint kUnboundStorageImageExternalIndex = 0xFFFFFF01u;
|
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
|
// 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
|
// 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) {
|
Bool IsPlaceholderTexture(const MG_State::GLState::ITextureObject* texture) {
|
||||||
if (texture == nullptr) return false;
|
if (texture == nullptr) return false;
|
||||||
const Uint index = static_cast<Uint>(texture->GetExternalIndex());
|
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
|
// 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_textureManager = nullptr;
|
||||||
m_samplerManager = nullptr;
|
m_samplerManager = nullptr;
|
||||||
m_fallbackTexture2D.reset();
|
m_fallbackTexture2D.reset();
|
||||||
|
m_fallbackTexture2DMultisample.reset();
|
||||||
|
m_fallbackTexture2DMultisampleArray.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformManager::BeginFrame(Uint32 frameIndex) {
|
void UniformManager::BeginFrame(Uint32 frameIndex) {
|
||||||
@@ -1377,11 +1385,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> UniformManager::GetFallbackTexture(TextureTarget target) const {
|
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
|
// A multisample sampler cannot be served by the single-sampled 2D image below - its
|
||||||
// would accept one. A multisample sampler in particular cannot: its descriptor demands a
|
// descriptor demands a multisample view - so it gets its own placeholder rather than no
|
||||||
// multisample view, and handing it this one is invalid Vulkan, not a degraded picture.
|
// placeholder at all. Without one, ResolveSamplerDescriptor declined and
|
||||||
// Report that there is no fallback and let the caller decline the draw - aborting the
|
// BindProgramUniformBuffers dropped the WHOLE draw, which is how every
|
||||||
// process over an unbound sampler is never the right answer.
|
// 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) {
|
if (target != TextureTarget::Texture2D && target != TextureTarget::TextureRectangle) {
|
||||||
MGLOG_E_ONCE("UniformManager::GetFallbackTexture: no fallback exists for target=%d",
|
MGLOG_E_ONCE("UniformManager::GetFallbackTexture: no fallback exists for target=%d",
|
||||||
static_cast<Int>(target));
|
static_cast<Int>(target));
|
||||||
@@ -1405,6 +1420,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return m_fallbackTexture2D;
|
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,
|
VkBufferView UniformManager::AcquireUnboundTexelBufferView(VkFormat declaredFormat,
|
||||||
SamplerNumericDomain numericDomain, Bool storage) {
|
SamplerNumericDomain numericDomain, Bool storage) {
|
||||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "AcquireUnboundTexelBufferView: buffer manager is null");
|
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;
|
// ResolveSamplerDescriptor will substitute the fallback texture for this binding;
|
||||||
// include it in the sampled set so the pre-render-pass sync/transition pass covers
|
// 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.
|
// its first use instead of leaving that work to happen inside an active pass.
|
||||||
if (preferredTarget != TextureTarget::Texture2D &&
|
// Ask GetFallbackTexture rather than re-listing the targets it serves: that list grew
|
||||||
preferredTarget != TextureTarget::TextureRectangle) {
|
// a multisample arm and the two must not drift apart.
|
||||||
|
texture = GetFallbackTexture(preferredTarget).get();
|
||||||
|
if (texture == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
texture = GetFallbackTexture(preferredTarget).get();
|
|
||||||
// The substitution changed the texture, so the "no override" arm of the effective
|
// The substitution changed the texture, so the "no override" arm of the effective
|
||||||
// sampler has to follow it to the fallback's own.
|
// sampler has to follow it to the fallback's own.
|
||||||
if (!samplerOverride) {
|
if (!samplerOverride) {
|
||||||
|
|||||||
@@ -180,6 +180,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const MG_State::GLState::ProgramObject& program,
|
const MG_State::GLState::ProgramObject& program,
|
||||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element);
|
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element);
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> GetFallbackTexture(TextureTarget target) const;
|
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 -------------------------
|
// ---- placeholders for UNBOUND image-backed descriptors -------------------------
|
||||||
// GL lets a program declare `samplerBuffer`, `imageBuffer` or `image2D` and bind nothing
|
// 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
|
// 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;
|
VkTextureManager* m_textureManager = nullptr;
|
||||||
VkSamplerManager* m_samplerManager = nullptr;
|
VkSamplerManager* m_samplerManager = nullptr;
|
||||||
mutable SharedPtr<MG_State::GLState::ITextureObject> m_fallbackTexture2D;
|
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
|
// See AcquireUnboundTexelBufferView / GetUnboundStorageImageTexture. Both are lazily
|
||||||
// populated, never evicted (a program's declared formats are a fixed, tiny set) and torn
|
// 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
|
// 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:
|
// build in GetOrCreatePipeline - any new GL-state read there must be added here:
|
||||||
// - capability bits: CullFace, DepthTest, PolygonOffsetFill (mode gating rides
|
// - capability bits: CullFace, DepthTest, PolygonOffsetFill (mode gating rides
|
||||||
// the memo's mode key), RasterizerDiscard, ColorLogicOp, StencilTest,
|
// 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,
|
// - 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)
|
// - 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,
|
// - 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
|
// 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.PrimitiveRestartFixedIndexEnabled ? 1ull << 7 : 0;
|
||||||
capabilityBits |= p.DepthMask ? 1ull << 8 : 0;
|
capabilityBits |= p.DepthMask ? 1ull << 8 : 0;
|
||||||
capabilityBits |= p.SampleShadingEnabled ? 1ull << 9 : 0;
|
capabilityBits |= p.SampleShadingEnabled ? 1ull << 9 : 0;
|
||||||
|
capabilityBits |= p.SampleMaskEnabled ? 1ull << 10 : 0;
|
||||||
Uint64 hash = CombinePipelineStateWord(0x243F6A8885A308D3ull, capabilityBits);
|
Uint64 hash = CombinePipelineStateWord(0x243F6A8885A308D3ull, capabilityBits);
|
||||||
// glMinSampleShading. Hashed by BITS, not by value: this memo compares hashes rather than
|
// 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
|
// 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));
|
std::memcpy(&minSampleShadingBits, &p.MinSampleShadingValue, sizeof(minSampleShadingBits));
|
||||||
hash = CombinePipelineStateWord(hash, static_cast<Uint64>(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));
|
hash = CombinePipelineStateWord(hash, static_cast<Uint64>(p.PatchVertices));
|
||||||
// The default tessellation levels belong here for the same reason PatchVertices does:
|
// 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
|
// 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 &&
|
.sampleShadingEnable = m_sampleRateShadingFeatureEnabled &&
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleShading),
|
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleShading),
|
||||||
.minSampleShading = MG_State::pGLContext->GetMinSampleShadingValue(),
|
.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,
|
.subpass = 0,
|
||||||
.topology = vkTopology,
|
.topology = vkTopology,
|
||||||
.primitiveRestartEnable = primitiveRestartEnabled,
|
.primitiveRestartEnable = primitiveRestartEnabled,
|
||||||
@@ -7610,7 +7624,8 @@ void main() {
|
|||||||
|
|
||||||
Bool VulkanRenderer::ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
Bool VulkanRenderer::ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
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);
|
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture);
|
||||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE) return false;
|
if (resource == nullptr || resource->image == VK_NULL_HANDLE) return false;
|
||||||
if (m_frameContext.GetCurrentFrameIndex() >= m_deferredDepthMipmapCleanup.size()) return false;
|
if (m_frameContext.GetCurrentFrameIndex() >= m_deferredDepthMipmapCleanup.size()) return false;
|
||||||
@@ -7623,7 +7638,11 @@ void main() {
|
|||||||
|
|
||||||
VkAttachmentDescription colorAttachment{};
|
VkAttachmentDescription colorAttachment{};
|
||||||
colorAttachment.format = resource->format;
|
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.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
@@ -7631,7 +7650,7 @@ void main() {
|
|||||||
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
// Hand the slice back in the layout the caller already tracks for the whole image, so its
|
// 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.
|
// 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{};
|
VkAttachmentReference colorRef{};
|
||||||
colorRef.attachment = 0;
|
colorRef.attachment = 0;
|
||||||
@@ -7699,9 +7718,26 @@ void main() {
|
|||||||
"MaterializePendingClearForTexture requires no active render pass on the target buffer");
|
"MaterializePendingClearForTexture requires no active render pass on the target buffer");
|
||||||
|
|
||||||
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture);
|
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture);
|
||||||
MOBILEGL_ASSERT(resource != nullptr,
|
if (resource == nullptr) {
|
||||||
"MaterializePendingClearForTexture: SyncTextureAndGetDescriptor failed for textureId=%d",
|
// Declined sync (an incomplete texture, say). Nothing to clear into, and every line
|
||||||
texture.GetExternalIndex());
|
// 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;
|
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
VkAccessFlags srcAccessMask = 0;
|
VkAccessFlags srcAccessMask = 0;
|
||||||
@@ -7841,6 +7877,77 @@ void main() {
|
|||||||
return true;
|
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(
|
Bool VulkanRenderer::MaterializePendingClearForRenderbuffer(
|
||||||
VkCommandBuffer commandBuffer, const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer) {
|
VkCommandBuffer commandBuffer, const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer) {
|
||||||
if (renderbuffer == nullptr) {
|
if (renderbuffer == nullptr) {
|
||||||
|
|||||||
@@ -1279,13 +1279,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||||
GLenum filter);
|
GLenum filter);
|
||||||
// Clears one z slice of a VK_IMAGE_TYPE_3D colour image. See the call site in
|
// Clears one layer of a colour image through a throwaway render pass whose entire content
|
||||||
// MaterializePendingClearForTexture for why a transfer clear cannot do this.
|
// 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,
|
Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
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,
|
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||||
MG_State::GLState::ITextureObject& texture);
|
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(
|
Bool MaterializePendingClearForRenderbuffer(
|
||||||
VkCommandBuffer commandBuffer,
|
VkCommandBuffer commandBuffer,
|
||||||
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||||
|
|||||||
Reference in New Issue
Block a user