diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index e4ba9d7e..1413c064 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -670,6 +670,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdSetLineWidth(commandBuffer, lineWidth); } + // Whether the GL scissor test clips viewport index `index`. The scissor rectangles start + // all-zero, and all-zero there means "never written" (RenderState's constructor), not an + // empty box: GL's initial box is the whole window, so until the application's first + // glScissor an enabled test clips nothing. Read as a box it would clip EVERYTHING, so every + // reader of the box asks this first. The resolution matches the viewport's (ComputeGLViewport + // resolves a never-written viewport to the current framebuffer's extent). + static Bool GLScissorTestClips(Uint32 index = 0) { + const RenderStateParameters& parameters = MG_Pipe::gPipeInputs.GetRenderStateParameters(); + const Uint32 bit = 1u << index; + return (parameters.ScissorTestEnabledMask & bit) != 0 && (parameters.ScissorBoxWrittenMask & bit) != 0; + } + static VkRect2D MakeClampedScissorRect(const IntVec4& scissorBox, const IntVec2& framebufferExtent) { const Int x0 = std::max(0, scissorBox.x()); const Int y0 = std::max(0, scissorBox.y()); @@ -3393,7 +3405,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFbo) const { const auto& parameters = MG_Pipe::gPipeInputs.GetRenderStateParameters(); - if ((parameters.ScissorTestEnabledMask & (1u << index)) == 0) { + if (!GLScissorTestClips(index)) { VkRect2D full{}; full.offset = {0, 0}; full.extent = {static_cast(extent.x()), static_cast(extent.y())}; @@ -3515,7 +3527,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { key.stencilWriteMask[face] = p.StencilStates[face].WriteMask; key.stencilRef[face] = p.StencilStates[face].Ref; } - key.scissorEnabled = (p.ScissorTestEnabledMask & 1u) != 0; + key.scissorEnabled = GLScissorTestClips(0); key.scissorBox[0] = p.ScissorBoxes[0].x(); key.scissorBox[1] = p.ScissorBoxes[0].y(); key.scissorBox[2] = p.ScissorBoxes[0].z(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc index 0270e49e..0e29085e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc @@ -1046,7 +1046,7 @@ if (count == 0) return remaining & ~handled; VkRect2D area{{0, 0}, {key.width, key.height}}; - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { + if (GLScissorTestClips()) { const auto& box = MG_Pipe::gPipeInputs.GetScissorBox(); const Int32 x0 = std::max(box.x(), 0), y0 = std::max(box.y(), 0); const Int32 x1 = std::min(box.x() + box.z(), static_cast(key.width)); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index 3bc16b0d..97bc53a6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -369,7 +369,7 @@ const VkCommandBuffer passBuffer = active->commandBuffer; const Uint64 passHash = active->hash; VkRect2D area{{0, 0}, {key.width, key.height}}; - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { + if (GLScissorTestClips()) { const auto& box = MG_Pipe::gPipeInputs.GetScissorBox(); const Int32 x0 = std::max(box.x(), 0), y0 = std::max(box.y(), 0); const Int32 x1 = std::min(box.x() + box.z(), static_cast(key.width)); @@ -469,7 +469,7 @@ const ClearAttachmentPayload& payload, GLint drawbuffer) { GLbitfield remaining = payload.mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (!remaining || fbo.IsDefault || drawbuffer >= 0 || m_wirePreparationDepth != 0) return remaining; - const Bool scissor = MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest); + const Bool scissor = GLScissorTestClips(); const auto wholeImage = [&](const WireImage& image) { if (!scissor) return true; const auto& box = MG_Pipe::gPipeInputs.GetScissorBox(); @@ -614,7 +614,7 @@ if (!image.image) return; m_textureManager->FlushPendingUploads(); VkRect2D area{{0, 0}, image.extent}; - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { + if (GLScissorTestClips()) { const auto& box = MG_Pipe::gPipeInputs.GetScissorBox(); const Int32 x0 = std::max(box.x(), 0), y0 = std::max(box.y(), 0); const Int32 x1 = std::min(box.x() + box.z(), static_cast(image.extent.width)); @@ -1263,7 +1263,7 @@ RetireWireDrawPass(); // The scissor test clips blit writes: intersect the destination rectangle with // the scissor box and shrink the source proportionally. - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { + if (GLScissorTestClips()) { const IntVec4& scissor = MG_Pipe::gPipeInputs.GetScissorBox(); const auto clipAxis = [](GLint& d0, GLint& d1, GLint& s0, GLint& s1, GLint clipLo, GLint clipHi) -> Bool { const Bool dstFlipped = d1 < d0; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp index bd7473d9..ad06ea59 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp @@ -13,7 +13,9 @@ // keeps the surface size in a memo that a swap, a change of draw surface, or a surface's creation // or destruction invalidates. This case makes a second, larger surface current on the same // context and clears it with the scissor test on: the clear must reach the larger surface's far -// corner, which a size remembered from the first surface would cut off. +// corner, which a size remembered from the first surface would cut off. DirectVulkan resolves a +// never-written box to the whole framebuffer it draws into, so the same clear reaches the corner +// there too (it used to read the all-zero box as an empty one and clear nothing). #include @@ -36,7 +38,6 @@ class DefaultScissorFollowsSurfaceScenario : public ScenarioTest {}; TEST_F(DefaultScissorFollowsSurfaceScenario, AClearOnALargerSurfaceMadeCurrentReachesItsFarCorner) { if (!Ready() || IsSkipped()) return; - if (Gl().BackendName() != "DirectGLES") GTEST_SKIP() << "the substituted scissor box is DirectGLES's"; const EGLDisplay display = eglGetCurrentDisplay(); const EGLSurface first = eglGetCurrentSurface(EGL_DRAW); const EGLContext context = eglGetCurrentContext();