[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:
2026-08-27 12:52:19 -04:00
parent 28c5badf8f
commit 9dee53337f
6 changed files with 212 additions and 20 deletions
@@ -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);