mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
Merge origin/dev (efd7b473) into readback overhaul - unify DirectGLES 2D-array target support under MapToBackendTextureTarget, keep canonical UNorm8 shadows for RGBA4/RGB565 (supersedes packed-word transfer types; GL_RGB565 aliases RGB5), keep upstream GLSL 330 normalization, anisotropy params, error-count semantics and VK clear/scissor fixes
This commit is contained in:
@@ -2169,6 +2169,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_LOD, samplerParams.maxLod);
|
||||
m_cacheSamplerParameters.maxLod = samplerParams.maxLod;
|
||||
}
|
||||
if (m_cacheSamplerParameters.maxAnisotropy != samplerParams.maxAnisotropy) {
|
||||
if (g_GLESCapabilities.SupportsTextureFilterAnisotropy) {
|
||||
g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
samplerParams.maxAnisotropy);
|
||||
}
|
||||
// Unsupported GLES backends intentionally treat anisotropy as a
|
||||
// frontend-only no-op; remember the observed value so the cache
|
||||
// remains coherent without issuing an illegal enum every sync.
|
||||
m_cacheSamplerParameters.maxAnisotropy = samplerParams.maxAnisotropy;
|
||||
}
|
||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
|
||||
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
@@ -3060,6 +3070,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MAX_LOD, samplerParams.maxLod);
|
||||
m_cacheSamplerParameters.maxLod = samplerParams.maxLod;
|
||||
}
|
||||
if (m_cacheSamplerParameters.maxAnisotropy != samplerParams.maxAnisotropy) {
|
||||
if (g_GLESCapabilities.SupportsTextureFilterAnisotropy) {
|
||||
g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
samplerParams.maxAnisotropy);
|
||||
}
|
||||
m_cacheSamplerParameters.maxAnisotropy = samplerParams.maxAnisotropy;
|
||||
}
|
||||
#undef SYNC_SAMPLER_PARAM_IF_CHANGED
|
||||
m_isInitialized = true;
|
||||
}
|
||||
|
||||
@@ -197,16 +197,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return static_cast<VkBufferResource*>(bufferObject.GetBackendResource().get());
|
||||
}
|
||||
|
||||
SharedPtr<VkBufferResource> VkBufferManager::GetOrCreateResource(
|
||||
VkBufferResource* VkBufferManager::GetOrCreateResource(
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
|
||||
auto existing = std::static_pointer_cast<VkBufferResource>(bufferObject->GetBackendResource());
|
||||
// Return by raw pointer: the resource is owned for its whole lifetime by the BufferObject's
|
||||
// backend-resource SharedPtr (already set, or set below), so callers that only dereference
|
||||
// it avoid a static_pointer_cast + SharedPtr refcount inc/dec on every per-draw buffer bind.
|
||||
const auto& existing = bufferObject->GetBackendResource();
|
||||
if (existing) {
|
||||
return existing;
|
||||
return static_cast<VkBufferResource*>(existing.get());
|
||||
}
|
||||
auto resource = MakeShared<VkBufferResource>();
|
||||
VkBufferResource* raw = resource.get();
|
||||
bufferObject->SetBackendResource(resource);
|
||||
TrackLiveResource(resource);
|
||||
return resource;
|
||||
return raw;
|
||||
}
|
||||
|
||||
void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
private:
|
||||
Bool InitializeTransientArenas();
|
||||
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
||||
SharedPtr<VkBufferResource> GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
VkBufferResource* GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject);
|
||||
Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
VkMemoryPropertyFlags requiredFlags = 0);
|
||||
|
||||
@@ -718,6 +718,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (hasClear) {
|
||||
pendingClearAttachments.emplace_back(PendingClearAttachmentInfo {
|
||||
.attachmentIndex = attachmentIndex,
|
||||
.colorAttachmentSlot = i,
|
||||
.key = VkClearManager::MakePendingClearKey(att)
|
||||
});
|
||||
}
|
||||
@@ -1015,7 +1016,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
hasDepthStencilAttachment,
|
||||
renderPassSampleCount,
|
||||
extent,
|
||||
static_cast<Int>(framebufferLayers) };
|
||||
framebufferLayers };
|
||||
MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u samples=%d extent=%dx%d",
|
||||
static_cast<unsigned long long>(hash),
|
||||
static_cast<unsigned long long>(compatibilityHash),
|
||||
|
||||
@@ -27,7 +27,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
};
|
||||
|
||||
struct PendingClearAttachmentInfo {
|
||||
// Index into the render pass attachment descriptions (VkRenderPassBeginInfo::pClearValues space).
|
||||
Uint32 attachmentIndex = 0;
|
||||
// Index into the subpass pColorAttachments (VkClearAttachment::colorAttachment space) — the GL
|
||||
// draw-buffer slot. Differs from attachmentIndex when earlier slots are GL_NONE/incomplete.
|
||||
// Only meaningful for color clears.
|
||||
Uint32 colorAttachmentSlot = 0;
|
||||
PendingClearKey key{};
|
||||
MG_State::GLState::RenderbufferObject* renderbuffer = nullptr;
|
||||
Bool hasInlinePayload = false;
|
||||
@@ -68,7 +73,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool hasDepthStencilAttachment = false;
|
||||
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
IntVec2 extent = {0, 0};
|
||||
Uint32 subpass = 0;
|
||||
// VkFramebufferCreateInfo::layers of the entry's framebuffer (>1 for layered GL attachments).
|
||||
Uint32 layers = 1;
|
||||
|
||||
RenderPassEntry() = default;
|
||||
RenderPassEntry(const RenderPassEntry&) = delete;
|
||||
@@ -84,7 +90,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
||||
std::swap(sampleCount, that.sampleCount);
|
||||
std::swap(extent, that.extent);
|
||||
std::swap(subpass, that.subpass);
|
||||
std::swap(layers, that.layers);
|
||||
}
|
||||
RenderPassEntry(
|
||||
Uint64 hash,
|
||||
@@ -97,7 +103,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 colorAttachmentCount,
|
||||
Bool hasDepthStencilAttachment,
|
||||
VkSampleCountFlagBits sampleCount,
|
||||
IntVec2 extent, int subpass):
|
||||
IntVec2 extent, Uint32 layers):
|
||||
hash(hash),
|
||||
renderPass(renderpass),
|
||||
framebuffer(framebuffer),
|
||||
@@ -109,7 +115,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
hasDepthStencilAttachment(hasDepthStencilAttachment),
|
||||
sampleCount(sampleCount),
|
||||
extent(extent),
|
||||
subpass(subpass)
|
||||
layers(layers)
|
||||
{}
|
||||
|
||||
~RenderPassEntry() {
|
||||
|
||||
@@ -99,6 +99,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||
const auto lodBias = sampler.GetLodBias();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
||||
// Anisotropy is currently an accepted frontend-only state on DirectVulkan.
|
||||
// Keep it out of the key so changing this no-op does not manufacture duplicate
|
||||
// VkSamplers while sampler versioning still exposes the new frontend value.
|
||||
const auto compareMode = sampler.GetCompareMode();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
||||
@@ -125,6 +128,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||
samplerInfo.mipLodBias = sampler.GetLodBias();
|
||||
// DirectVulkan does not yet plumb samplerAnisotropy feature/limit discovery;
|
||||
// preserve the accepted frontend state without requesting an unsupported feature.
|
||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||
#include "MG_Util/Metrics/TextureMetrics.h"
|
||||
#include <Config.h>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
@@ -2150,8 +2151,8 @@ void main() {
|
||||
if (!supported) {
|
||||
// SetupDraw's pre-flight should have rejected this already; never upload a null payload.
|
||||
MGLOG_E("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: "
|
||||
"location=%u type=0x%x",
|
||||
location, glType);
|
||||
"programHash=%llu location=%u type=0x%x",
|
||||
static_cast<unsigned long long>(programObj.hash), location, glType);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3584,8 +3585,73 @@ void main() {
|
||||
1, &memoryBarrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
VulkanRenderer::ScissoredClearPrep VulkanRenderer::PrepareScissoredClear(
|
||||
const MG_State::GLState::FramebufferObject& framebuffer, VkClearRect& outClearRect) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) {
|
||||
m_frameContext.BeginCommandRecording();
|
||||
}
|
||||
|
||||
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
||||
auto* renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(framebuffer, m_imageIndexAcquired);
|
||||
if (renderPassEntry->attachmentCount == 0 ||
|
||||
renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) {
|
||||
return ScissoredClearPrep::NoOp;
|
||||
}
|
||||
|
||||
VkClearRect clearRect{};
|
||||
clearRect.rect = framebuffer.IsDefaultFramebuffer()
|
||||
? MakeDefaultFramebufferScissorRect(MG_State::pGLContext->GetScissorBox(),
|
||||
renderPassEntry->extent,
|
||||
m_swapchainObject.GetPreTransform())
|
||||
: MakeClampedScissorRect(MG_State::pGLContext->GetScissorBox(), renderPassEntry->extent);
|
||||
clearRect.baseArrayLayer = 0;
|
||||
// GL 3.3 §4.4.7: clearing a layered framebuffer clears every layer.
|
||||
clearRect.layerCount = renderPassEntry->layers;
|
||||
if (clearRect.rect.extent.width == 0 || clearRect.rect.extent.height == 0) {
|
||||
return ScissoredClearPrep::NoOp;
|
||||
}
|
||||
// A scissor that covers the whole target is a whole-surface clear; the deferred loadOp
|
||||
// path is equivalent and cheaper (no render pass churn, loadOp=CLEAR on tilers).
|
||||
if (clearRect.rect.offset.x == 0 && clearRect.rect.offset.y == 0 &&
|
||||
clearRect.rect.extent.width == static_cast<Uint32>(renderPassEntry->extent.x()) &&
|
||||
clearRect.rect.extent.height == static_cast<Uint32>(renderPassEntry->extent.y())) {
|
||||
return ScissoredClearPrep::NotNeeded;
|
||||
}
|
||||
|
||||
if (activeRenderPass && !activeRenderPass->CompatibleWith(*renderPassEntry)) {
|
||||
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
activeRenderPass = nullptr;
|
||||
// Re-resolve: ending the pass updates tracked attachment layouts, which feed the
|
||||
// entry's load ops and initial layouts.
|
||||
renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(framebuffer, m_imageIndexAcquired);
|
||||
}
|
||||
// A still-active pass is necessarily compatible here: the block above ended any
|
||||
// incompatible one and nothing since can change the active pass.
|
||||
if (activeRenderPass) {
|
||||
// Materialize any older whole-attachment clear before applying this
|
||||
// ordered, scissored clear.
|
||||
ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, *renderPassEntry);
|
||||
} else {
|
||||
const Bool began = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, *renderPassEntry);
|
||||
MOBILEGL_ASSERT(began, "%s: BeginRenderPass failed", __func__);
|
||||
if (!began) {
|
||||
return ScissoredClearPrep::NoOp;
|
||||
}
|
||||
}
|
||||
outClearRect = clearRect;
|
||||
return ScissoredClearPrep::Ready;
|
||||
}
|
||||
|
||||
void VulkanRenderer::Clear(GLbitfield mask) {
|
||||
m_clearManager->CollectGarbage();
|
||||
if ((mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) == 0) {
|
||||
return;
|
||||
}
|
||||
// GL 3.3 §3.1: when RASTERIZER_DISCARD is enabled, Clear and ClearBuffer* are ignored.
|
||||
if (MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) {
|
||||
return;
|
||||
}
|
||||
auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get();
|
||||
MOBILEGL_ASSERT(fbo, "VulkanRenderer::Clear: draw framebuffer not found (fbo == nullptr)");
|
||||
if (IsUnsupportedFramebufferForDirectVulkan(*fbo)) {
|
||||
@@ -3604,109 +3670,89 @@ void main() {
|
||||
// GuiItemAtlas: animated items clear only their atlas slot before being
|
||||
// redrawn. Queueing that clear as a loadOp erases every cached static item.
|
||||
if (MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ScissorTest)) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) {
|
||||
m_frameContext.BeginCommandRecording();
|
||||
}
|
||||
|
||||
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
||||
auto* renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(*fbo, m_imageIndexAcquired);
|
||||
if (activeRenderPass && !activeRenderPass->CompatibleWith(*renderPassEntry)) {
|
||||
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
activeRenderPass = nullptr;
|
||||
renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(*fbo, m_imageIndexAcquired);
|
||||
}
|
||||
if (renderPassEntry->attachmentCount == 0 ||
|
||||
renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeRenderPass && activeRenderPass->CompatibleWith(*renderPassEntry)) {
|
||||
// Materialize any older whole-attachment clear before applying this
|
||||
// ordered, scissored clear.
|
||||
ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, *renderPassEntry);
|
||||
} else {
|
||||
const Bool began = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, *renderPassEntry);
|
||||
MOBILEGL_ASSERT(began, "%s: BeginRenderPass failed", __func__);
|
||||
}
|
||||
|
||||
VkClearRect clearRect{};
|
||||
clearRect.rect = fbo->IsDefaultFramebuffer()
|
||||
? MakeDefaultFramebufferScissorRect(MG_State::pGLContext->GetScissorBox(),
|
||||
renderPassEntry->extent,
|
||||
m_swapchainObject.GetPreTransform())
|
||||
: MakeClampedScissorRect(MG_State::pGLContext->GetScissorBox(), renderPassEntry->extent);
|
||||
clearRect.baseArrayLayer = 0;
|
||||
clearRect.layerCount = 1;
|
||||
if (clearRect.rect.extent.width == 0 || clearRect.rect.extent.height == 0) {
|
||||
switch (PrepareScissoredClear(*fbo, clearRect)) {
|
||||
case ScissoredClearPrep::NoOp:
|
||||
return;
|
||||
case ScissoredClearPrep::NotNeeded:
|
||||
break; // full-coverage scissor: the deferred whole-surface path below is equivalent
|
||||
case ScissoredClearPrep::Ready: {
|
||||
VkClearAttachment clearAttachments[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS + 1];
|
||||
Uint32 clearAttachmentCount = 0;
|
||||
|
||||
if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
const auto& drawBuffers = fbo->GetDrawBuffers();
|
||||
for (Uint32 drawBufferIndex = 0; drawBufferIndex < drawBuffers.size(); ++drawBufferIndex) {
|
||||
const auto attachmentType = drawBuffers[drawBufferIndex];
|
||||
if (attachmentType == FramebufferAttachmentType::None) {
|
||||
continue;
|
||||
}
|
||||
const auto& attachment = fbo->GetAttachment(attachmentType);
|
||||
if (!attachment.IsComplete()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const BoolVec4 colorMask = MG_State::pGLContext->GetColorMaskIndexed(drawBufferIndex);
|
||||
if (!colorMask.r() && !colorMask.g() && !colorMask.b() && !colorMask.a()) {
|
||||
continue;
|
||||
}
|
||||
if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) {
|
||||
MGLOG_W("DirectVulkan: scissored glClear with a partial color mask is not supported");
|
||||
continue;
|
||||
}
|
||||
|
||||
MG_State::GLState::ITextureObject* colorTexture = nullptr;
|
||||
if (attachment.IsTexture()) {
|
||||
colorTexture = attachment.GetTexture().get();
|
||||
}
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = drawBufferIndex;
|
||||
clearAttachment.clearValue.color = {
|
||||
payload.color.x(), payload.color.y(), payload.color.z(),
|
||||
ResolveColorClearAlpha(colorTexture, payload.color.w())
|
||||
};
|
||||
clearAttachments[clearAttachmentCount++] = clearAttachment;
|
||||
}
|
||||
}
|
||||
|
||||
VkImageAspectFlags depthStencilAspects = 0;
|
||||
if ((mask & GL_DEPTH_BUFFER_BIT) != 0 && MG_State::pGLContext->GetDepthMask()) {
|
||||
const auto& depthAttachment = fbo->GetAttachment(FramebufferAttachmentType::Depth);
|
||||
if (depthAttachment.IsComplete()) {
|
||||
depthStencilAspects |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
}
|
||||
if ((mask & GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
const auto& stencilAttachment = fbo->GetAttachment(FramebufferAttachmentType::Stencil);
|
||||
if (stencilAttachment.IsComplete()) {
|
||||
// GL 3.3 §4.2.3: the clear is masked by the front stencil write mask.
|
||||
// vkCmdClearAttachments writes every bit, so only a full (8-bit stencil) or
|
||||
// zero mask can be expressed; treat a partial mask like a partial color mask.
|
||||
const Uint32 stencilWriteMask =
|
||||
MG_State::pGLContext->GetStencilState(StencilFace::Front).WriteMask;
|
||||
if ((stencilWriteMask & 0xFFu) == 0xFFu) {
|
||||
depthStencilAspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
} else if (stencilWriteMask != 0) {
|
||||
MGLOG_W("DirectVulkan: scissored glClear with a partial stencil write mask is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (depthStencilAspects != 0) {
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = depthStencilAspects;
|
||||
clearAttachment.clearValue.depthStencil = {payload.depth, payload.stencil};
|
||||
clearAttachments[clearAttachmentCount++] = clearAttachment;
|
||||
}
|
||||
|
||||
if (clearAttachmentCount != 0) {
|
||||
vkCmdClearAttachments(m_frameContext.GetCurrent().commandBuffer,
|
||||
clearAttachmentCount, clearAttachments,
|
||||
1, &clearRect);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<VkClearAttachment> clearAttachments;
|
||||
clearAttachments.reserve(fbo->GetDrawBuffers().size() + 1);
|
||||
|
||||
if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
const auto& drawBuffers = fbo->GetDrawBuffers();
|
||||
for (Uint32 drawBufferIndex = 0; drawBufferIndex < drawBuffers.size(); ++drawBufferIndex) {
|
||||
const auto attachmentType = drawBuffers[drawBufferIndex];
|
||||
if (attachmentType == FramebufferAttachmentType::None) {
|
||||
continue;
|
||||
}
|
||||
const auto& attachment = fbo->GetAttachment(attachmentType);
|
||||
if (!attachment.IsComplete()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const BoolVec4 colorMask = MG_State::pGLContext->GetColorMaskIndexed(drawBufferIndex);
|
||||
if (!colorMask.r() && !colorMask.g() && !colorMask.b() && !colorMask.a()) {
|
||||
continue;
|
||||
}
|
||||
if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) {
|
||||
MGLOG_W("DirectVulkan: scissored glClear with a partial color mask is not supported");
|
||||
continue;
|
||||
}
|
||||
|
||||
MG_State::GLState::ITextureObject* colorTexture = nullptr;
|
||||
if (attachment.IsTexture()) {
|
||||
colorTexture = attachment.GetTexture().get();
|
||||
}
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = drawBufferIndex;
|
||||
clearAttachment.clearValue.color = {
|
||||
payload.color.x(), payload.color.y(), payload.color.z(),
|
||||
ResolveColorClearAlpha(colorTexture, payload.color.w())
|
||||
};
|
||||
clearAttachments.push_back(clearAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
VkImageAspectFlags depthStencilAspects = 0;
|
||||
if ((mask & GL_DEPTH_BUFFER_BIT) != 0 && MG_State::pGLContext->GetDepthMask()) {
|
||||
const auto& depthAttachment = fbo->GetAttachment(FramebufferAttachmentType::Depth);
|
||||
if (depthAttachment.IsComplete()) {
|
||||
depthStencilAspects |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
}
|
||||
if ((mask & GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
const auto& stencilAttachment = fbo->GetAttachment(FramebufferAttachmentType::Stencil);
|
||||
if (stencilAttachment.IsComplete()) {
|
||||
depthStencilAspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
}
|
||||
if (depthStencilAspects != 0) {
|
||||
VkClearAttachment clearAttachment{};
|
||||
clearAttachment.aspectMask = depthStencilAspects;
|
||||
clearAttachment.clearValue.depthStencil = {payload.depth, payload.stencil};
|
||||
clearAttachments.push_back(clearAttachment);
|
||||
}
|
||||
|
||||
if (!clearAttachments.empty()) {
|
||||
vkCmdClearAttachments(frame.commandBuffer,
|
||||
static_cast<Uint32>(clearAttachments.size()), clearAttachments.data(),
|
||||
1, &clearRect);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_clearManager->QueueClear(mask, payload, *fbo);
|
||||
@@ -3717,11 +3763,62 @@ void main() {
|
||||
const MG_State::GLState::FramebufferObject& framebuffer, GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload) {
|
||||
m_clearManager->CollectGarbage();
|
||||
// GL 3.3 §3.1: when RASTERIZER_DISCARD is enabled, Clear and ClearBuffer* are ignored.
|
||||
if (MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) {
|
||||
return;
|
||||
}
|
||||
if (IsUnsupportedFramebufferForDirectVulkan(framebuffer)) {
|
||||
RecordUnsupportedFramebufferError(__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate (buffer, drawbuffer) up front so GL errors fire regardless of which clear
|
||||
// path is taken below.
|
||||
switch (buffer) {
|
||||
case GL_COLOR:
|
||||
if (drawbuffer < 0 ||
|
||||
drawbuffer >= static_cast<GLint>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS)) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "color drawbuffer index is out of range");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case GL_DEPTH:
|
||||
if (drawbuffer != 0) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth clear requires drawbuffer 0");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case GL_STENCIL:
|
||||
if (drawbuffer != 0) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "stencil clear requires drawbuffer 0");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case GL_DEPTH_STENCIL:
|
||||
if (drawbuffer != 0) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth/stencil clear requires drawbuffer 0");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidEnum, "unsupported clear buffer target");
|
||||
return;
|
||||
}
|
||||
|
||||
// GL 3.3 §4.2.3: ClearBuffer* is clipped by GL_SCISSOR_TEST exactly like Clear.
|
||||
if (MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ScissorTest)) {
|
||||
VkClearRect clearRect{};
|
||||
switch (PrepareScissoredClear(framebuffer, clearRect)) {
|
||||
case ScissoredClearPrep::NoOp:
|
||||
return;
|
||||
case ScissoredClearPrep::NotNeeded:
|
||||
break; // full-coverage scissor: the deferred whole-surface path below is equivalent
|
||||
case ScissoredClearPrep::Ready:
|
||||
RecordScissoredClearBuffer(framebuffer, buffer, drawbuffer, clearPayload, clearRect);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto queueAttachmentClear = [&](FramebufferAttachmentType attachmentType) {
|
||||
if (attachmentType == FramebufferAttachmentType::None) {
|
||||
return;
|
||||
@@ -3738,43 +3835,84 @@ void main() {
|
||||
};
|
||||
|
||||
switch (buffer) {
|
||||
case GL_COLOR: {
|
||||
if (drawbuffer < 0 ||
|
||||
drawbuffer >= static_cast<GLint>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS)) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "color drawbuffer index is out of range");
|
||||
return;
|
||||
}
|
||||
case GL_COLOR:
|
||||
queueAttachmentClear(framebuffer.GetDrawBuffers()[drawbuffer]);
|
||||
return;
|
||||
}
|
||||
case GL_DEPTH:
|
||||
if (drawbuffer != 0) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth clear requires drawbuffer 0");
|
||||
return;
|
||||
}
|
||||
queueAttachmentClear(FramebufferAttachmentType::Depth);
|
||||
return;
|
||||
case GL_STENCIL:
|
||||
if (drawbuffer != 0) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "stencil clear requires drawbuffer 0");
|
||||
return;
|
||||
}
|
||||
queueAttachmentClear(FramebufferAttachmentType::Stencil);
|
||||
return;
|
||||
case GL_DEPTH_STENCIL:
|
||||
if (drawbuffer != 0) {
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth/stencil clear requires drawbuffer 0");
|
||||
return;
|
||||
}
|
||||
queueAttachmentClear(FramebufferAttachmentType::Depth);
|
||||
queueAttachmentClear(FramebufferAttachmentType::Stencil);
|
||||
return;
|
||||
default:
|
||||
RecordClearBufferError(__func__, ErrorCode::InvalidEnum, "unsupported clear buffer target");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload,
|
||||
const VkClearRect& clearRect) {
|
||||
VkClearAttachment clearAttachment{};
|
||||
|
||||
if (buffer == GL_COLOR) {
|
||||
const auto attachmentType = framebuffer.GetDrawBuffers()[drawbuffer];
|
||||
if (attachmentType == FramebufferAttachmentType::None) {
|
||||
return;
|
||||
}
|
||||
const auto& attachment = framebuffer.GetAttachment(attachmentType);
|
||||
if (!attachment.IsComplete()) {
|
||||
return;
|
||||
}
|
||||
const BoolVec4 colorMask = MG_State::pGLContext->GetColorMaskIndexed(static_cast<Uint>(drawbuffer));
|
||||
if (!colorMask.r() && !colorMask.g() && !colorMask.b() && !colorMask.a()) {
|
||||
return;
|
||||
}
|
||||
if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) {
|
||||
MGLOG_W("DirectVulkan: scissored glClearBuffer with a partial color mask is not supported");
|
||||
return;
|
||||
}
|
||||
MG_State::GLState::ITextureObject* colorTexture = nullptr;
|
||||
if (attachment.IsTexture()) {
|
||||
colorTexture = attachment.GetTexture().get();
|
||||
}
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = static_cast<Uint32>(drawbuffer);
|
||||
clearAttachment.clearValue.color = {
|
||||
clearPayload.color.x(), clearPayload.color.y(), clearPayload.color.z(),
|
||||
ResolveColorClearAlpha(colorTexture, clearPayload.color.w())
|
||||
};
|
||||
} else {
|
||||
VkImageAspectFlags aspects = 0;
|
||||
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0 && MG_State::pGLContext->GetDepthMask() &&
|
||||
framebuffer.GetAttachment(FramebufferAttachmentType::Depth).IsComplete()) {
|
||||
aspects |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0 &&
|
||||
framebuffer.GetAttachment(FramebufferAttachmentType::Stencil).IsComplete()) {
|
||||
// GL 3.3 §4.2.3: the clear is masked by the front stencil write mask (see Clear).
|
||||
const Uint32 stencilWriteMask =
|
||||
MG_State::pGLContext->GetStencilState(StencilFace::Front).WriteMask;
|
||||
if ((stencilWriteMask & 0xFFu) == 0xFFu) {
|
||||
aspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
} else if (stencilWriteMask != 0) {
|
||||
MGLOG_W("DirectVulkan: scissored glClearBuffer with a partial stencil write mask is not supported");
|
||||
}
|
||||
}
|
||||
if (aspects == 0) {
|
||||
return;
|
||||
}
|
||||
clearAttachment.aspectMask = aspects;
|
||||
clearAttachment.clearValue.depthStencil = {clearPayload.depth, clearPayload.stencil};
|
||||
}
|
||||
|
||||
vkCmdClearAttachments(m_frameContext.GetCurrent().commandBuffer, 1, &clearAttachment, 1, &clearRect);
|
||||
}
|
||||
|
||||
void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload) {
|
||||
auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get();
|
||||
@@ -3787,7 +3925,8 @@ void main() {
|
||||
void VulkanRenderer::ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
|
||||
ClearAttachmentPayload payload{};
|
||||
payload.mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
payload.depth = depth;
|
||||
// Vulkan clear values require depth in [0,1] (VUID-VkClearDepthStencilValue-depth-00022).
|
||||
payload.depth = std::clamp(depth, 0.0f, 1.0f);
|
||||
payload.stencil = static_cast<Uint32>(stencil);
|
||||
QueueClearBufferPayload(buffer, drawbuffer, payload);
|
||||
}
|
||||
@@ -3804,7 +3943,7 @@ void main() {
|
||||
break;
|
||||
case GL_DEPTH:
|
||||
payload.mask = GL_DEPTH_BUFFER_BIT;
|
||||
payload.depth = value[0];
|
||||
payload.depth = std::clamp(value[0], 0.0f, 1.0f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -3826,7 +3965,7 @@ void main() {
|
||||
break;
|
||||
case GL_DEPTH:
|
||||
payload.mask = GL_DEPTH_BUFFER_BIT;
|
||||
payload.depth = value[0];
|
||||
payload.depth = std::clamp(value[0], 0.0f, 1.0f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -3842,7 +3981,8 @@ void main() {
|
||||
}
|
||||
ClearAttachmentPayload payload{};
|
||||
payload.mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
payload.depth = depth;
|
||||
// Vulkan clear values require depth in [0,1] (VUID-VkClearDepthStencilValue-depth-00022).
|
||||
payload.depth = std::clamp(depth, 0.0f, 1.0f);
|
||||
payload.stencil = static_cast<Uint32>(stencil);
|
||||
QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload);
|
||||
}
|
||||
@@ -6863,7 +7003,8 @@ void main() {
|
||||
static_cast<Uint32>(activeRenderPass->extent.y())
|
||||
};
|
||||
clearRect.baseArrayLayer = 0;
|
||||
clearRect.layerCount = 1;
|
||||
// Compatible entries share the framebuffer layer count; layered attachments clear every layer.
|
||||
clearRect.layerCount = compatibleRenderPassEntry.layers;
|
||||
|
||||
for (const auto& pending : compatibleRenderPassEntry.pendingClearAttachments) {
|
||||
if (!pending.hasInlinePayload && pending.key.texture == nullptr) {
|
||||
@@ -6884,7 +7025,9 @@ void main() {
|
||||
clearAttachment.clearValue.depthStencil = {1.0f, 0};
|
||||
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clearAttachment.colorAttachment = pending.attachmentIndex;
|
||||
// VkClearAttachment::colorAttachment indexes the subpass pColorAttachments (draw-buffer
|
||||
// slot space, with UNUSED holes), not the compacted attachment descriptions.
|
||||
clearAttachment.colorAttachment = pending.colorAttachmentSlot;
|
||||
clearAttachment.clearValue.color = {
|
||||
clearPayload.color.x(),
|
||||
clearPayload.color.y(),
|
||||
|
||||
@@ -131,6 +131,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
||||
const RenderPassEntry& compatibleRenderPassEntry);
|
||||
|
||||
enum class ScissoredClearPrep {
|
||||
NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead
|
||||
NoOp, // nothing to clear (degenerate target or empty scissor rect)
|
||||
Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect
|
||||
};
|
||||
ScissoredClearPrep PrepareScissoredClear(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
VkClearRect& outClearRect);
|
||||
|
||||
void Clear(GLbitfield mask);
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
@@ -275,6 +283,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload);
|
||||
void RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload,
|
||||
const VkClearRect& clearRect);
|
||||
|
||||
// ---- Submission fence tracking (GL sync objects) ----
|
||||
// One record per vkQueueSubmit still in flight, in ascending submit
|
||||
|
||||
Reference in New Issue
Block a user