mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Backend): apply framebuffer no-attachment defaults on both backends
This commit is contained in:
@@ -9686,6 +9686,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// force. 0 is never a live hash, so a zeroed memo is a guaranteed miss.
|
||||
m_syncedRecordHashes = {0};
|
||||
m_syncedAttachmentRecordHash = 0;
|
||||
m_syncedNoAttachmentDefaultsValid = false;
|
||||
}
|
||||
|
||||
static Bool IsSnormFormat(TextureInternalFormat format) {
|
||||
@@ -10465,6 +10466,39 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
NoteFramebufferTextureAttachments(fbo, record);
|
||||
}
|
||||
|
||||
// 4. ARB_framebuffer_no_attachments. With no attachment at all the record's geometry
|
||||
// IS the framebuffer's default width/height/layers/samples (FillGeometry), and the
|
||||
// driver FBO needs them too: without a default size an attachment-less ES
|
||||
// framebuffer is incomplete and every draw into it is dropped. With attachments the
|
||||
// defaults are ignored by GL, so they are only pushed for the empty shape.
|
||||
Bool hasAnyAttachment = !(record.Depth.Kind == MG_Pipe::kMGPipeSurfaceKindNone ||
|
||||
MG_Pipe::MGPipeHandleIsNull(record.Depth.Res)) ||
|
||||
!(record.Stencil.Kind == MG_Pipe::kMGPipeSurfaceKindNone ||
|
||||
MG_Pipe::MGPipeHandleIsNull(record.Stencil.Res));
|
||||
for (Uint i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments && !hasAnyAttachment; ++i) {
|
||||
hasAnyAttachment = !(record.Color[i].Kind == MG_Pipe::kMGPipeSurfaceKindNone ||
|
||||
MG_Pipe::MGPipeHandleIsNull(record.Color[i].Res));
|
||||
}
|
||||
if (!hasAnyAttachment) {
|
||||
const Array<Int, 5> defaults = {
|
||||
static_cast<Int>(record.Width), static_cast<Int>(record.Height),
|
||||
static_cast<Int>(record.Layers),
|
||||
std::min<Int>(static_cast<Int>(record.Samples), g_GLESCapabilities.MaxFramebufferSamples),
|
||||
static_cast<Int>(record.FixedSampleLocations != 0 ? GL_TRUE : GL_FALSE)};
|
||||
if (!m_syncedNoAttachmentDefaultsValid || m_syncedNoAttachmentDefaults != defaults) {
|
||||
g_GLESFuncs.glFramebufferParameteri(glFBOTarget, GL_FRAMEBUFFER_DEFAULT_WIDTH, defaults[0]);
|
||||
g_GLESFuncs.glFramebufferParameteri(glFBOTarget, GL_FRAMEBUFFER_DEFAULT_HEIGHT, defaults[1]);
|
||||
if (g_GLESCapabilities.SupportsFramebufferDefaultLayers) {
|
||||
g_GLESFuncs.glFramebufferParameteri(glFBOTarget, GL_FRAMEBUFFER_DEFAULT_LAYERS, defaults[2]);
|
||||
}
|
||||
g_GLESFuncs.glFramebufferParameteri(glFBOTarget, GL_FRAMEBUFFER_DEFAULT_SAMPLES, defaults[3]);
|
||||
g_GLESFuncs.glFramebufferParameteri(glFBOTarget, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS,
|
||||
defaults[4]);
|
||||
m_syncedNoAttachmentDefaults = defaults;
|
||||
m_syncedNoAttachmentDefaultsValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The walk itself can re-mint a driver id (the storage sync behind a texture
|
||||
// surface), invalidating points this walk already attached. Re-enter until the
|
||||
// generation is quiescent, exactly as the object form does - every pass syncs each
|
||||
|
||||
@@ -2258,6 +2258,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// INVALID_OPERATION. Keying on the object is what makes "this hash is applied" mean
|
||||
// "these points are on the driver now".
|
||||
Uint64 m_syncedAttachmentRecordHash = 0;
|
||||
// The ARB_framebuffer_no_attachments defaults (width, height, layers, samples,
|
||||
// fixed sample locations) last pushed to the driver FBO with glFramebufferParameteri.
|
||||
// Like the attachments they belong to the driver OBJECT, so one memo, not per target.
|
||||
Array<Int, 5> m_syncedNoAttachmentDefaults = {0, 0, 0, 0, 0};
|
||||
Bool m_syncedNoAttachmentDefaultsValid = false;
|
||||
// P5e (fb): the read-buffer decision, taken from the record alone. Both
|
||||
// SyncReadBufferToBackend overloads funnel through it, so the rule lives in one
|
||||
// place and the object form is visibly the half that only finds the handle.
|
||||
|
||||
@@ -842,6 +842,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<Uint32> colorReferences;
|
||||
Uint32 depthReference = VK_ATTACHMENT_UNUSED;
|
||||
Uint32 width = 0, height = 0, layers = 1;
|
||||
// The rasterization sample count of a pass with no attachments (0 otherwise), where
|
||||
// nothing else in the key would tell two such passes apart.
|
||||
Uint32 noAttachmentSamples = 0;
|
||||
Uint64 textureImageEpoch = 0, resourceEraseEpoch = 0;
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
Uint32 swapchainImageIndex = 0;
|
||||
|
||||
@@ -380,8 +380,36 @@
|
||||
else if (fbo.Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
||||
depth.attachment = add(fbo.Stencil, VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
if (attachments.empty()) {
|
||||
MGL_WIRE_DECLINE_AT(NoAttachments, "the draw framebuffer resolved to no attachments");
|
||||
return false;
|
||||
// ARB_framebuffer_no_attachments (GL 4.6 core 9.2): a framebuffer with no image
|
||||
// attached rasterizes at its default width/height/layers/samples, which the record
|
||||
// carries in that case (FillGeometry). Vulkan expresses it directly as a render pass
|
||||
// with no attachments over a framebuffer of that extent.
|
||||
const Bool hasAnyAttachment = fbo.Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone ||
|
||||
fbo.Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone ||
|
||||
std::any_of(std::begin(fbo.Color), std::end(fbo.Color), [](const MG_Pipe::MGPSurface& surface) {
|
||||
return surface.Kind != MG_Pipe::kMGPipeSurfaceKindNone;
|
||||
});
|
||||
if (fbo.IsDefault || hasAnyAttachment || fbo.Width == 0 || fbo.Height == 0) {
|
||||
MGL_WIRE_DECLINE_AT(NoAttachments, "the draw framebuffer resolved to no attachments");
|
||||
return false;
|
||||
}
|
||||
const auto& limits = m_physicalDevice.properties.limits;
|
||||
extent.width = std::min<Uint32>(fbo.Width, limits.maxFramebufferWidth);
|
||||
extent.height = std::min<Uint32>(fbo.Height, limits.maxFramebufferHeight);
|
||||
layers = std::clamp<Uint32>(fbo.Layers, 1u, std::max<Uint32>(limits.maxFramebufferLayers, 1u));
|
||||
// The smallest count the device rasterizes attachment-less passes at that covers the
|
||||
// request (GL lets the effective count exceed the requested one), else its largest.
|
||||
samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
const VkSampleCountFlags supported = limits.framebufferNoAttachmentsSampleCounts;
|
||||
if (fbo.Samples > 1) {
|
||||
for (Uint32 bit = VK_SAMPLE_COUNT_2_BIT; bit <= VK_SAMPLE_COUNT_64_BIT; bit <<= 1) {
|
||||
if ((supported & bit) == 0) continue;
|
||||
samples = static_cast<VkSampleCountFlagBits>(bit);
|
||||
if (bit >= fbo.Samples) break;
|
||||
}
|
||||
}
|
||||
passKey.noAttachmentSamples = static_cast<Uint32>(samples);
|
||||
passHash = XXH64(&samples, sizeof(samples), passHash);
|
||||
}
|
||||
passKey.width = extent.width;
|
||||
passKey.height = extent.height;
|
||||
|
||||
@@ -1128,6 +1128,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
Bool hasTessellationShaderExtension = false;
|
||||
Bool hasClipControlExtension = false;
|
||||
Bool hasPolygonOffsetClampExtension = false;
|
||||
Bool hasGeometryShaderExtension = false;
|
||||
for (GLint i = 0; i < extCount; ++i) {
|
||||
const char* extension = (const char*)glesFuncs.glGetStringi(GL_EXTENSIONS, i);
|
||||
if (extension) {
|
||||
@@ -1194,6 +1195,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_KHR_parallel_shader_compile") == 0) {
|
||||
caps.SupportsParallelShaderCompile = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_geometry_shader") == 0 ||
|
||||
std::strcmp(extension, "GL_OES_geometry_shader") == 0) {
|
||||
hasGeometryShaderExtension = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_polygon_offset_clamp") == 0) {
|
||||
hasPolygonOffsetClampExtension = true;
|
||||
}
|
||||
@@ -1293,6 +1298,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2);
|
||||
const Bool esAtLeast31 = caps.GLESVersion.Major > 3 ||
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1);
|
||||
caps.SupportsFramebufferDefaultLayers = esAtLeast32 || hasGeometryShaderExtension;
|
||||
caps.SupportsDrawIndirect = esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr &&
|
||||
glesFuncs.glDrawElementsIndirect != nullptr;
|
||||
caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) &&
|
||||
|
||||
@@ -1223,6 +1223,10 @@ namespace MobileGL {
|
||||
// it the GL_POLYGON_OFFSET_CLAMP value cannot be applied and only glPolygonOffset's
|
||||
// unclamped offset reaches the driver.
|
||||
Bool SupportsPolygonOffsetClamp = false;
|
||||
// GL_FRAMEBUFFER_DEFAULT_LAYERS is a legal glFramebufferParameteri pname: ES 3.2 core,
|
||||
// or GL_EXT/OES_geometry_shader before it. The other four no-attachment defaults are
|
||||
// ES 3.1 core and need no flag.
|
||||
Bool SupportsFramebufferDefaultLayers = false;
|
||||
// GL_NV_shader_noperspective_interpolation is present: the driver accepts the
|
||||
// `noperspective` interpolation qualifier in ESSL. GLES core has none, so without this
|
||||
// SPIRV-Cross's `#extension ... : require` would fail to compile and MobileGL falls back
|
||||
|
||||
Reference in New Issue
Block a user