diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4f099fa..028c2576 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1454,6 +1454,8 @@ jobs: "F1WireScenario.UniformBufferRangeAndRebindPixels", "F1WireScenario.ShortUniformBufferRangePadsMissingBytes", "F1WireScenario.ComputeWrittenVertexAndIndexBuffersDrawAndReadBack", + "F1WireScenario.ColorBlitToDefaultIgnoresViewportAndPreservesOrientation", + "F1WireScenario.ColorBlitToDefaultHonorsScissorAndReversedRect", "IndexedDrawFamilyScenario.AnElementBufferDrawElementsOffsetSelectsTheRightQuad", "IndexedDrawFamilyScenario.DrawElementsBaseVertexMovesTheSameIndicesToTheOtherQuad", "IndexedDrawFamilyScenario.DrawRangeElementsDrawsTheRangedRun", diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6e45da15..e9399334 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -3431,6 +3431,7 @@ void main() { } #if MOBILEGL_BUILD_DISAGGREGATED DestroyWireDrawPass(); + DestroyWireColorBlitResources(); #endif OnSubmitsCompletedUpTo(m_submitCounter); DestroySubmitFencePool(); @@ -7728,6 +7729,7 @@ void main() { } #include "WireFramebuffer.inc" + #include "WireColorBlit.inc" #include "WireDraw.inc" void VulkanRenderer::Clear(GLbitfield mask) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 0ac544c2..36f30774 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -458,6 +458,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { const ClearAttachmentPayload& payload, GLint drawbuffer = -1); void BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLbitfield mask, GLenum filter); + Bool BlitWireColorToDefault(WireImage source, WireImage destination, + GLint sx0, GLint sy0, GLint sx1, GLint sy1, + GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter); + struct WireColorBlitResources; + WireColorBlitResources* m_wireColorBlitResources = nullptr; + void DestroyWireColorBlitResources(); void ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); void GenerateWireMipmap(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.frag b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.frag new file mode 100644 index 00000000..ddefb453 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.frag @@ -0,0 +1,13 @@ +#version 450 +layout(set = 0, binding = 0) uniform sampler2D sourceImage; +layout(location = 0) in vec2 texCoord; +layout(location = 0) out vec4 outColor; + +void main() { + // Source framebuffer clipping preserves the original scale, rather than + // extending the edge texels into pixels outside the source framebuffer. + if (any(lessThan(texCoord, vec2(0.0))) || any(greaterThan(texCoord, vec2(1.0)))) discard; + // Explicit LOD is required on Adreno 650: implicit LOD through a rotated + // single-mip UBWC render target can read outside its allocation. + outColor = textureLod(sourceImage, texCoord, 0.0); +} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc new file mode 100644 index 00000000..742aad0b --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc @@ -0,0 +1,261 @@ +// Included by VulkanRenderer.cpp inside the backend namespace, after WireFramebuffer.inc. +// The native blit owns no frontend object and never invokes the GL shader compiler. +#if MOBILEGL_BUILD_DISAGGREGATED +#include "WireColorBlitSpirv.h" + + struct VulkanRenderer::WireColorBlitResources { + struct Target { + VkFormat format = VK_FORMAT_UNDEFINED; + VkRenderPass renderPass = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + }; + VkShaderModule vertex = VK_NULL_HANDLE, fragment = VK_NULL_HANDLE; + VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkSampler nearest = VK_NULL_HANDLE, linear = VK_NULL_HANDLE; + Vector targets; + }; + + void VulkanRenderer::DestroyWireColorBlitResources() { + if (!m_wireColorBlitResources) return; + // Shutdown calls this after vkDeviceWaitIdle; every individual blit also + // waits before recycling its descriptor set or destroying temporary views. + auto& cache = *m_wireColorBlitResources; + for (const auto& target : cache.targets) { + vkDestroyPipeline(m_device, target.pipeline, nullptr); + vkDestroyRenderPass(m_device, target.renderPass, nullptr); + } + vkDestroySampler(m_device, cache.nearest, nullptr); + vkDestroySampler(m_device, cache.linear, nullptr); + vkDestroyDescriptorPool(m_device, cache.descriptorPool, nullptr); + vkDestroyPipelineLayout(m_device, cache.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(m_device, cache.descriptorLayout, nullptr); + vkDestroyShaderModule(m_device, cache.vertex, nullptr); + vkDestroyShaderModule(m_device, cache.fragment, nullptr); + delete m_wireColorBlitResources; + m_wireColorBlitResources = nullptr; + } + + Bool VulkanRenderer::BlitWireColorToDefault(WireImage source, WireImage destination, + GLint sx0, GLint sy0, GLint sx1, GLint sy1, + GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter) { + if (!destination.isDefault || source.isDefault || source.image == destination.image || + source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT || + source.is3D || (source.viewType != VK_IMAGE_VIEW_TYPE_2D && + source.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) || + !vkuFormatIsSampledFloat(source.format)) return false; + if (sx0 == sx1 || sy0 == sy1 || dx0 == dx1 || dy0 == dy1) return true; + if (!source.extent.width || !source.extent.height || + !destination.extent.width || !destination.extent.height) return false; + const auto transform = m_swapchainObject.GetPreTransform(); + if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR && + transform != VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR && + transform != VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR && + transform != VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) return false; + VkFormatProperties sourceProperties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, source.format, &sourceProperties); + const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + (filter == GL_LINEAR ? VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT : 0); + if ((sourceProperties.optimalTilingFeatures & required) != required) return false; + + struct Constants { + float srcRect[4]; + float dstRect[4]; + Int32 surfaceTransform; + }; + static_assert(sizeof(Constants) == 36); + if (!m_wireColorBlitResources) { + m_wireColorBlitResources = new WireColorBlitResources; + auto& cache = *m_wireColorBlitResources; + const auto shader = [&](const Uint32* code, SizeT bytes, VkShaderModule& module) { + VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + info.codeSize = bytes; + info.pCode = code; + VK_VERIFY(vkCreateShaderModule(m_device, &info, nullptr, &module)); + }; + shader(kWireColorBlitVertexSpirv, sizeof(kWireColorBlitVertexSpirv), cache.vertex); + shader(kWireColorBlitFragmentSpirv, sizeof(kWireColorBlitFragmentSpirv), cache.fragment); + const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; + VkDescriptorSetLayoutCreateInfo layout{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + layout.bindingCount = 1; + layout.pBindings = &binding; + VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &layout, nullptr, &cache.descriptorLayout)); + const VkPushConstantRange push{VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(Constants)}; + VkPipelineLayoutCreateInfo pipelineLayout{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipelineLayout.setLayoutCount = 1; + pipelineLayout.pSetLayouts = &cache.descriptorLayout; + pipelineLayout.pushConstantRangeCount = 1; + pipelineLayout.pPushConstantRanges = &push; + VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayout, nullptr, &cache.pipelineLayout)); + const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}; + VkDescriptorPoolCreateInfo pool{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + pool.maxSets = 1; + pool.poolSizeCount = 1; + pool.pPoolSizes = &poolSize; + VK_VERIFY(vkCreateDescriptorPool(m_device, &pool, nullptr, &cache.descriptorPool)); + VkDescriptorSetAllocateInfo alloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + alloc.descriptorPool = cache.descriptorPool; + alloc.descriptorSetCount = 1; + alloc.pSetLayouts = &cache.descriptorLayout; + VK_VERIFY(vkAllocateDescriptorSets(m_device, &alloc, &cache.descriptorSet)); + VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + sampler.addressModeU = sampler.addressModeV = sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler.magFilter = sampler.minFilter = VK_FILTER_NEAREST; + sampler.maxLod = 0.0f; + VK_VERIFY(vkCreateSampler(m_device, &sampler, nullptr, &cache.nearest)); + sampler.magFilter = sampler.minFilter = VK_FILTER_LINEAR; + VK_VERIFY(vkCreateSampler(m_device, &sampler, nullptr, &cache.linear)); + } + auto& cache = *m_wireColorBlitResources; + // Honor the same sRGB write policy as wire draw attachments. + destination.format = ResolveSrgbAttachmentWriteFormat(destination.format, + MGB_CTX->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb)); + WireColorBlitResources::Target* target = nullptr; + for (auto& entry : cache.targets) { + if (entry.format == destination.format) { target = &entry; break; } + } + if (!target) { + cache.targets.emplace_back(); + target = &cache.targets.back(); + target->format = destination.format; + VkAttachmentDescription attachment{}; + attachment.format = destination.format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + const VkAttachmentReference color{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color; + VkRenderPassCreateInfo pass{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + pass.attachmentCount = 1; + pass.pAttachments = &attachment; + pass.subpassCount = 1; + pass.pSubpasses = &subpass; + VK_VERIFY(vkCreateRenderPass(m_device, &pass, nullptr, &target->renderPass)); + VkPipelineShaderStageCreateInfo stages[2]{}; + for (auto& stage : stages) { + stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage.pName = "main"; + } + stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[0].module = cache.vertex; + stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[1].module = cache.fragment; + const VkPipelineVertexInputStateCreateInfo vertex{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + viewport.viewportCount = viewport.scissorCount = 1; + VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + raster.polygonMode = VK_POLYGON_MODE_FILL; + raster.cullMode = VK_CULL_MODE_NONE; + raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + raster.lineWidth = 1.0f; + VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + VkPipelineColorBlendAttachmentState colorMask{}; + colorMask.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + blend.attachmentCount = 1; + blend.pAttachments = &colorMask; + const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + dynamic.dynamicStateCount = 2; + dynamic.pDynamicStates = dynamicStates; + VkGraphicsPipelineCreateInfo pipeline{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + pipeline.stageCount = 2; + pipeline.pStages = stages; + pipeline.pVertexInputState = &vertex; + pipeline.pInputAssemblyState = &assembly; + pipeline.pViewportState = &viewport; + pipeline.pRasterizationState = &raster; + pipeline.pMultisampleState = &samples; + pipeline.pColorBlendState = &blend; + pipeline.pDynamicState = &dynamic; + pipeline.layout = cache.pipelineLayout; + pipeline.renderPass = target->renderPass; + VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipeline, nullptr, &target->pipeline)); + } + + m_textureManager->FlushPendingUploads(); + TransitionWireImage(source, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + TransitionWireImage(destination, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + const auto makeView = [&](const WireImage& image) { + VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + view.image = image.image; + view.viewType = VK_IMAGE_VIEW_TYPE_2D; + view.format = image.format; + // A layered framebuffer blit reads its first layer, as the native path does. + view.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, image.level, 1, image.layer, 1}; + VkImageView handle = VK_NULL_HANDLE; + VK_VERIFY(vkCreateImageView(m_device, &view, nullptr, &handle)); + return handle; + }; + const VkImageView sourceView = makeView(source), destinationView = makeView(destination); + VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + framebufferInfo.renderPass = target->renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = &destinationView; + framebufferInfo.width = destination.extent.width; + framebufferInfo.height = destination.extent.height; + framebufferInfo.layers = 1; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer)); + const VkDescriptorImageInfo sampled{filter == GL_LINEAR ? cache.linear : cache.nearest, + sourceView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + write.dstSet = cache.descriptorSet; + write.dstBinding = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.pImageInfo = &sampled; + vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr); + + const float sw = static_cast(source.extent.width), sh = static_cast(source.extent.height); + const Bool quarterTurn = transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || + transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; + const float dw = static_cast(quarterTurn ? destination.extent.height : destination.extent.width); + const float dh = static_cast(quarterTurn ? destination.extent.width : destination.extent.height); + const Constants constants{{sx0 / sw, sy0 / sh, (static_cast(sx1) - sx0) / sw, + (static_cast(sy1) - sy0) / sh}, + {dx0 / dw, dy0 / dh, (static_cast(dx1) - dx0) / dw, + (static_cast(dy1) - dy0) / dh}, + static_cast(ToBlitSurfaceTransform(transform))}; + auto& frame = m_frameContext.GetCurrent(); + VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + begin.renderPass = target->renderPass; + begin.framebuffer = framebuffer; + begin.renderArea = {{0, 0}, destination.extent}; + vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, target->pipeline); + const VkViewport viewport{0.0f, 0.0f, static_cast(destination.extent.width), + static_cast(destination.extent.height), 0.0f, 1.0f}; + const VkRect2D scissor{{0, 0}, destination.extent}; + vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport); + vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); + vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + cache.pipelineLayout, 0, 1, &cache.descriptorSet, 0, nullptr); + vkCmdPushConstants(frame.commandBuffer, cache.pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, + 0, sizeof(constants), &constants); + vkCmdDraw(frame.commandBuffer, 6, 1, 0, 0); + vkCmdEndRenderPass(frame.commandBuffer); + ResetDynamicStateShadow(); + InvalidatePipelineMemo(); + if (!SubmitReadbackCommandsAndWait(frame)) MagmaWireFatal("color-blit-submit"); + vkDestroyFramebuffer(m_device, framebuffer, nullptr); + vkDestroyImageView(m_device, sourceView, nullptr); + vkDestroyImageView(m_device, destinationView, nullptr); + m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true); + return true; + } +#endif diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.vert b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.vert new file mode 100644 index 00000000..bafb7b99 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.vert @@ -0,0 +1,25 @@ +#version 450 +// Native Vulkan blit: no GL program, registry entry, or runtime transpilation. +layout(push_constant) uniform BlitRect { + vec4 srcRect; + vec4 dstRect; + int surfaceTransform; +} rect; +layout(location = 0) out vec2 texCoord; + +void main() { + // A quad, rather than an oversized triangle, also clips partial blits to + // their destination rectangle. The Vulkan viewport covers the native image. + const vec2 corners[6] = vec2[]( + vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0), + vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0)); + vec2 uv = corners[gl_VertexIndex]; + vec2 p = (rect.dstRect.xy + uv * rect.dstRect.zw) * 2.0 - 1.0; + // Same GL-bottom-left to native-image mapping as the monolithic blit. + p.y = -p.y; + if (rect.surfaceTransform == 1) p = vec2(-p.y, p.x); + else if (rect.surfaceTransform == 2) p = -p; + else if (rect.surfaceTransform == 3) p = vec2(p.y, -p.x); + gl_Position = vec4(p, 0.0, 1.0); + texCoord = rect.srcRect.xy + uv * rect.srcRect.zw; +} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h new file mode 100644 index 00000000..acf485d8 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h @@ -0,0 +1,127 @@ +// Generated from WireColorBlit.vert / WireColorBlit.frag with: +// glslangValidator -V --target-env vulkan1.0 -o .spv +// Validated with spirv-val --target-env vulkan1.0. Do not edit the words by hand. +#pragma once + +static constexpr Uint32 kWireColorBlitVertexSpirv[] = { + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000070u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0008000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000016u, 0x0000005du, 0x00000066u, + 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, + 0x00000009u, 0x00007675u, 0x00060005u, 0x00000016u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, + 0x00050005u, 0x00000019u, 0x65646e69u, 0x6c626178u, 0x00000065u, 0x00030005u, 0x0000001cu, 0x00000070u, + 0x00050005u, 0x0000001eu, 0x74696c42u, 0x74636552u, 0x00000000u, 0x00050006u, 0x0000001eu, 0x00000000u, + 0x52637273u, 0x00746365u, 0x00050006u, 0x0000001eu, 0x00000001u, 0x52747364u, 0x00746365u, 0x00080006u, + 0x0000001eu, 0x00000002u, 0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u, 0x00040005u, + 0x00000020u, 0x74636572u, 0x00000000u, 0x00060005u, 0x0000005bu, 0x505f6c67u, 0x65567265u, 0x78657472u, + 0x00000000u, 0x00060006u, 0x0000005bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, + 0x0000005bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000005bu, + 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000005bu, 0x00000003u, + 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000005du, 0x00000000u, 0x00050005u, + 0x00000066u, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000016u, 0x0000000bu, 0x0000002au, + 0x00030047u, 0x0000001eu, 0x00000002u, 0x00050048u, 0x0000001eu, 0x00000000u, 0x00000023u, 0x00000000u, + 0x00050048u, 0x0000001eu, 0x00000001u, 0x00000023u, 0x00000010u, 0x00050048u, 0x0000001eu, 0x00000002u, + 0x00000023u, 0x00000020u, 0x00030047u, 0x0000005bu, 0x00000002u, 0x00050048u, 0x0000005bu, 0x00000000u, + 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000005bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, + 0x0000005bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000005bu, 0x00000003u, 0x0000000bu, + 0x00000004u, 0x00040047u, 0x00000066u, 0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, + 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, + 0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u, + 0x00000000u, 0x0004002bu, 0x0000000au, 0x0000000bu, 0x00000006u, 0x0004001cu, 0x0000000cu, 0x00000007u, + 0x0000000bu, 0x0004002bu, 0x00000006u, 0x0000000du, 0x00000000u, 0x0005002cu, 0x00000007u, 0x0000000eu, + 0x0000000du, 0x0000000du, 0x0004002bu, 0x00000006u, 0x0000000fu, 0x3f800000u, 0x0005002cu, 0x00000007u, + 0x00000010u, 0x0000000fu, 0x0000000du, 0x0005002cu, 0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu, + 0x0005002cu, 0x00000007u, 0x00000012u, 0x0000000fu, 0x0000000fu, 0x0009002cu, 0x0000000cu, 0x00000013u, + 0x0000000eu, 0x00000010u, 0x00000011u, 0x00000011u, 0x00000010u, 0x00000012u, 0x00040015u, 0x00000014u, + 0x00000020u, 0x00000001u, 0x00040020u, 0x00000015u, 0x00000001u, 0x00000014u, 0x0004003bu, 0x00000015u, + 0x00000016u, 0x00000001u, 0x00040020u, 0x00000018u, 0x00000007u, 0x0000000cu, 0x00040017u, 0x0000001du, + 0x00000006u, 0x00000004u, 0x0005001eu, 0x0000001eu, 0x0000001du, 0x0000001du, 0x00000014u, 0x00040020u, + 0x0000001fu, 0x00000009u, 0x0000001eu, 0x0004003bu, 0x0000001fu, 0x00000020u, 0x00000009u, 0x0004002bu, + 0x00000014u, 0x00000021u, 0x00000001u, 0x00040020u, 0x00000022u, 0x00000009u, 0x0000001du, 0x0004002bu, + 0x00000006u, 0x0000002cu, 0x40000000u, 0x0004002bu, 0x0000000au, 0x00000030u, 0x00000001u, 0x00040020u, + 0x00000031u, 0x00000007u, 0x00000006u, 0x0004002bu, 0x00000014u, 0x00000036u, 0x00000002u, 0x00040020u, + 0x00000037u, 0x00000009u, 0x00000014u, 0x00020014u, 0x0000003au, 0x0004002bu, 0x0000000au, 0x00000041u, + 0x00000000u, 0x0004002bu, 0x00000014u, 0x00000050u, 0x00000003u, 0x0004001cu, 0x0000005au, 0x00000006u, + 0x00000030u, 0x0006001eu, 0x0000005bu, 0x0000001du, 0x00000006u, 0x0000005au, 0x0000005au, 0x00040020u, + 0x0000005cu, 0x00000003u, 0x0000005bu, 0x0004003bu, 0x0000005cu, 0x0000005du, 0x00000003u, 0x0004002bu, + 0x00000014u, 0x0000005eu, 0x00000000u, 0x00040020u, 0x00000063u, 0x00000003u, 0x0000001du, 0x00040020u, + 0x00000065u, 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000065u, 0x00000066u, 0x00000003u, 0x00050036u, + 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, 0x00000008u, + 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000018u, 0x00000019u, 0x00000007u, 0x0004003bu, 0x00000008u, + 0x0000001cu, 0x00000007u, 0x0004003du, 0x00000014u, 0x00000017u, 0x00000016u, 0x0003003eu, 0x00000019u, + 0x00000013u, 0x00050041u, 0x00000008u, 0x0000001au, 0x00000019u, 0x00000017u, 0x0004003du, 0x00000007u, + 0x0000001bu, 0x0000001au, 0x0003003eu, 0x00000009u, 0x0000001bu, 0x00050041u, 0x00000022u, 0x00000023u, + 0x00000020u, 0x00000021u, 0x0004003du, 0x0000001du, 0x00000024u, 0x00000023u, 0x0007004fu, 0x00000007u, + 0x00000025u, 0x00000024u, 0x00000024u, 0x00000000u, 0x00000001u, 0x0004003du, 0x00000007u, 0x00000026u, + 0x00000009u, 0x00050041u, 0x00000022u, 0x00000027u, 0x00000020u, 0x00000021u, 0x0004003du, 0x0000001du, + 0x00000028u, 0x00000027u, 0x0007004fu, 0x00000007u, 0x00000029u, 0x00000028u, 0x00000028u, 0x00000002u, + 0x00000003u, 0x00050085u, 0x00000007u, 0x0000002au, 0x00000026u, 0x00000029u, 0x00050081u, 0x00000007u, + 0x0000002bu, 0x00000025u, 0x0000002au, 0x0005008eu, 0x00000007u, 0x0000002du, 0x0000002bu, 0x0000002cu, + 0x00050050u, 0x00000007u, 0x0000002eu, 0x0000000fu, 0x0000000fu, 0x00050083u, 0x00000007u, 0x0000002fu, + 0x0000002du, 0x0000002eu, 0x0003003eu, 0x0000001cu, 0x0000002fu, 0x00050041u, 0x00000031u, 0x00000032u, + 0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u, 0x00000033u, 0x00000032u, 0x0004007fu, 0x00000006u, + 0x00000034u, 0x00000033u, 0x00050041u, 0x00000031u, 0x00000035u, 0x0000001cu, 0x00000030u, 0x0003003eu, + 0x00000035u, 0x00000034u, 0x00050041u, 0x00000037u, 0x00000038u, 0x00000020u, 0x00000036u, 0x0004003du, + 0x00000014u, 0x00000039u, 0x00000038u, 0x000500aau, 0x0000003au, 0x0000003bu, 0x00000039u, 0x00000021u, + 0x000300f7u, 0x0000003du, 0x00000000u, 0x000400fau, 0x0000003bu, 0x0000003cu, 0x00000045u, 0x000200f8u, + 0x0000003cu, 0x00050041u, 0x00000031u, 0x0000003eu, 0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u, + 0x0000003fu, 0x0000003eu, 0x0004007fu, 0x00000006u, 0x00000040u, 0x0000003fu, 0x00050041u, 0x00000031u, + 0x00000042u, 0x0000001cu, 0x00000041u, 0x0004003du, 0x00000006u, 0x00000043u, 0x00000042u, 0x00050050u, + 0x00000007u, 0x00000044u, 0x00000040u, 0x00000043u, 0x0003003eu, 0x0000001cu, 0x00000044u, 0x000200f9u, + 0x0000003du, 0x000200f8u, 0x00000045u, 0x00050041u, 0x00000037u, 0x00000046u, 0x00000020u, 0x00000036u, + 0x0004003du, 0x00000014u, 0x00000047u, 0x00000046u, 0x000500aau, 0x0000003au, 0x00000048u, 0x00000047u, + 0x00000036u, 0x000300f7u, 0x0000004au, 0x00000000u, 0x000400fau, 0x00000048u, 0x00000049u, 0x0000004du, + 0x000200f8u, 0x00000049u, 0x0004003du, 0x00000007u, 0x0000004bu, 0x0000001cu, 0x0004007fu, 0x00000007u, + 0x0000004cu, 0x0000004bu, 0x0003003eu, 0x0000001cu, 0x0000004cu, 0x000200f9u, 0x0000004au, 0x000200f8u, + 0x0000004du, 0x00050041u, 0x00000037u, 0x0000004eu, 0x00000020u, 0x00000036u, 0x0004003du, 0x00000014u, + 0x0000004fu, 0x0000004eu, 0x000500aau, 0x0000003au, 0x00000051u, 0x0000004fu, 0x00000050u, 0x000300f7u, + 0x00000053u, 0x00000000u, 0x000400fau, 0x00000051u, 0x00000052u, 0x00000053u, 0x000200f8u, 0x00000052u, + 0x00050041u, 0x00000031u, 0x00000054u, 0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u, 0x00000055u, + 0x00000054u, 0x00050041u, 0x00000031u, 0x00000056u, 0x0000001cu, 0x00000041u, 0x0004003du, 0x00000006u, + 0x00000057u, 0x00000056u, 0x0004007fu, 0x00000006u, 0x00000058u, 0x00000057u, 0x00050050u, 0x00000007u, + 0x00000059u, 0x00000055u, 0x00000058u, 0x0003003eu, 0x0000001cu, 0x00000059u, 0x000200f9u, 0x00000053u, + 0x000200f8u, 0x00000053u, 0x000200f9u, 0x0000004au, 0x000200f8u, 0x0000004au, 0x000200f9u, 0x0000003du, + 0x000200f8u, 0x0000003du, 0x0004003du, 0x00000007u, 0x0000005fu, 0x0000001cu, 0x00050051u, 0x00000006u, + 0x00000060u, 0x0000005fu, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000061u, 0x0000005fu, 0x00000001u, + 0x00070050u, 0x0000001du, 0x00000062u, 0x00000060u, 0x00000061u, 0x0000000du, 0x0000000fu, 0x00050041u, + 0x00000063u, 0x00000064u, 0x0000005du, 0x0000005eu, 0x0003003eu, 0x00000064u, 0x00000062u, 0x00050041u, + 0x00000022u, 0x00000067u, 0x00000020u, 0x0000005eu, 0x0004003du, 0x0000001du, 0x00000068u, 0x00000067u, + 0x0007004fu, 0x00000007u, 0x00000069u, 0x00000068u, 0x00000068u, 0x00000000u, 0x00000001u, 0x0004003du, + 0x00000007u, 0x0000006au, 0x00000009u, 0x00050041u, 0x00000022u, 0x0000006bu, 0x00000020u, 0x0000005eu, + 0x0004003du, 0x0000001du, 0x0000006cu, 0x0000006bu, 0x0007004fu, 0x00000007u, 0x0000006du, 0x0000006cu, + 0x0000006cu, 0x00000002u, 0x00000003u, 0x00050085u, 0x00000007u, 0x0000006eu, 0x0000006au, 0x0000006du, + 0x00050081u, 0x00000007u, 0x0000006fu, 0x00000069u, 0x0000006eu, 0x0003003eu, 0x00000066u, 0x0000006fu, + 0x000100fdu, 0x00010038u, +}; + +static constexpr Uint32 kWireColorBlitFragmentSpirv[] = { + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000027u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000au, 0x0000001fu, 0x00030010u, + 0x00000004u, 0x00000007u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, + 0x00000000u, 0x00050005u, 0x0000000au, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00050005u, 0x0000001fu, + 0x4374756fu, 0x726f6c6fu, 0x00000000u, 0x00050005u, 0x00000023u, 0x72756f73u, 0x6d496563u, 0x00656761u, + 0x00040047u, 0x0000000au, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000001fu, 0x0000001eu, 0x00000000u, + 0x00040047u, 0x00000023u, 0x00000021u, 0x00000000u, 0x00040047u, 0x00000023u, 0x00000022u, 0x00000000u, + 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00020014u, 0x00000006u, 0x00030016u, + 0x00000007u, 0x00000020u, 0x00040017u, 0x00000008u, 0x00000007u, 0x00000002u, 0x00040020u, 0x00000009u, + 0x00000001u, 0x00000008u, 0x0004003bu, 0x00000009u, 0x0000000au, 0x00000001u, 0x0004002bu, 0x00000007u, + 0x0000000cu, 0x00000000u, 0x0005002cu, 0x00000008u, 0x0000000du, 0x0000000cu, 0x0000000cu, 0x00040017u, + 0x0000000eu, 0x00000006u, 0x00000002u, 0x0004002bu, 0x00000007u, 0x00000015u, 0x3f800000u, 0x0005002cu, + 0x00000008u, 0x00000016u, 0x00000015u, 0x00000015u, 0x00040017u, 0x0000001du, 0x00000007u, 0x00000004u, + 0x00040020u, 0x0000001eu, 0x00000003u, 0x0000001du, 0x0004003bu, 0x0000001eu, 0x0000001fu, 0x00000003u, + 0x00090019u, 0x00000020u, 0x00000007u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, + 0x00000000u, 0x0003001bu, 0x00000021u, 0x00000020u, 0x00040020u, 0x00000022u, 0x00000000u, 0x00000021u, + 0x0004003bu, 0x00000022u, 0x00000023u, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, + 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000008u, 0x0000000bu, 0x0000000au, 0x000500b8u, + 0x0000000eu, 0x0000000fu, 0x0000000bu, 0x0000000du, 0x0004009au, 0x00000006u, 0x00000010u, 0x0000000fu, + 0x000400a8u, 0x00000006u, 0x00000011u, 0x00000010u, 0x000300f7u, 0x00000013u, 0x00000000u, 0x000400fau, + 0x00000011u, 0x00000012u, 0x00000013u, 0x000200f8u, 0x00000012u, 0x0004003du, 0x00000008u, 0x00000014u, + 0x0000000au, 0x000500bau, 0x0000000eu, 0x00000017u, 0x00000014u, 0x00000016u, 0x0004009au, 0x00000006u, + 0x00000018u, 0x00000017u, 0x000200f9u, 0x00000013u, 0x000200f8u, 0x00000013u, 0x000700f5u, 0x00000006u, + 0x00000019u, 0x00000010u, 0x00000005u, 0x00000018u, 0x00000012u, 0x000300f7u, 0x0000001bu, 0x00000000u, + 0x000400fau, 0x00000019u, 0x0000001au, 0x0000001bu, 0x000200f8u, 0x0000001au, 0x000100fcu, 0x000200f8u, + 0x0000001bu, 0x0004003du, 0x00000021u, 0x00000024u, 0x00000023u, 0x0004003du, 0x00000008u, 0x00000025u, + 0x0000000au, 0x00070058u, 0x0000001du, 0x00000026u, 0x00000024u, 0x00000025u, 0x00000002u, 0x0000000cu, + 0x0003003eu, 0x0000001fu, 0x00000026u, 0x000100fdu, 0x00010038u, +}; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index 32f46bd4..8ea97670 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -298,6 +298,19 @@ WireImage destination = ResolveWireImage(*draw,dst,aspect); if (!source.image || !destination.image) return; m_textureManager->FlushPendingUploads(); + // GL names logical window coordinates. Android's native swapchain may + // be quarter-turned (e.g. logical 2620x1280 over an image 1280x2620). + // An axis-aligned vkCmdBlitImage cannot express that rotation; issuing + // the logical rectangle directly also writes outside the native image. + if (aspect == VK_IMAGE_ASPECT_COLOR_BIT && destination.isDefault && !source.isDefault && + source.samples == VK_SAMPLE_COUNT_1_BIT && destination.samples == VK_SAMPLE_COUNT_1_BIT && + source.image != destination.image) { + if (!BlitWireColorToDefault(source, destination, sx0, sy0, sx1, sy1, + dx0, dy0, dx1, dy1, filter)) + MagmaWireFatal("default-color-blit-shape@P7"); + m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true); + return; + } const Bool sameImage = source.image == destination.image; const VkImageLayout srcLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; const VkImageLayout dstLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 9496e67e..c0b8627e 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2432,6 +2432,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED) F1WireScenario.UniformBufferRangeAndRebindPixels F1WireScenario.ShortUniformBufferRangePadsMissingBytes F1WireScenario.ComputeWrittenVertexAndIndexBuffersDrawAndReadBack + F1WireScenario.ColorBlitToDefaultIgnoresViewportAndPreservesOrientation + F1WireScenario.ColorBlitToDefaultHonorsScissorAndReversedRect IndexedDrawFamilyScenario.AnElementBufferDrawElementsOffsetSelectsTheRightQuad IndexedDrawFamilyScenario.DrawElementsBaseVertexMovesTheSameIndicesToTheOtherQuad IndexedDrawFamilyScenario.DrawRangeElementsDrawsTheRangedRun diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index 74b9c416..71fc6c97 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -95,6 +95,28 @@ protected: if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); } ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) << "F1.setup.framebuffer"; } + void PaintNonSquareBlitSource() { + // GL coordinates: red/green on the bottom, blue/yellow on the top. + // A non-square source makes accidental extent/axis interchange visible. + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 6, 4); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + constexpr GLfloat colors[4][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; + glEnable(GL_SCISSOR_TEST); + for (int quadrant = 0; quadrant < 4; ++quadrant) { + glScissor((quadrant % 2) * 3, (quadrant / 2) * 2, 3, 2); + glClearColor(colors[quadrant][0], colors[quadrant][1], colors[quadrant][2], colors[quadrant][3]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "paint non-square source FBO"; + } + std::array ReadOnePixel(int x, int y) { + std::array pixel{31, 47, 63, 79}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + return pixel; + } + }; } @@ -977,4 +999,62 @@ TEST_F(F1WireScenario, NamedBlitDefaultEndpointPixels) { EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "default read endpoint"; EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); } + +TEST_F(F1WireScenario, ColorBlitToDefaultIgnoresViewportAndPreservesOrientation) { + if (!Ready()) return; + ASSERT_GE(Gl().Width(), 16); + ASSERT_GE(Gl().Height(), 16); + ASSERT_NO_FATAL_FAILURE(PaintNonSquareBlitSource()); + const int width = Gl().Width(), height = Gl().Height(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // The destination really is the default framebuffer. + glClearColor(1, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(1, 2, 3, 5); // glBlitFramebuffer must ignore this unrelated viewport. + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBlitFramebuffer(0, 0, 6, 4, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + EXPECT_EQ(ReadOnePixel(width / 4, height / 4), (std::array{255, 0, 0, 255})) << "bottom left"; + EXPECT_EQ(ReadOnePixel(3 * width / 4, height / 4), (std::array{0, 255, 0, 255})) << "bottom right"; + EXPECT_EQ(ReadOnePixel(width / 4, 3 * height / 4), (std::array{0, 0, 255, 255})) << "top left"; + EXPECT_EQ(ReadOnePixel(3 * width / 4, 3 * height / 4), (std::array{255, 255, 0, 255})) << "top right"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + GLint viewport[4]{}; + glGetIntegerv(GL_VIEWPORT, viewport); + EXPECT_EQ((std::array{viewport[0], viewport[1], viewport[2], viewport[3]}), + (std::array{1, 2, 3, 5})) << "blit must not mutate the application's viewport"; + Gl().EndFrame(); +} + +TEST_F(F1WireScenario, ColorBlitToDefaultHonorsScissorAndReversedRect) { + if (!Ready()) return; + ASSERT_GE(Gl().Width(), 16); + ASSERT_GE(Gl().Height(), 16); + ASSERT_NO_FATAL_FAILURE(PaintNonSquareBlitSource()); + const int width = Gl().Width(), height = Gl().Height(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glClearColor(1, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(2, 1, 5, 3); + // Copy into the centre half with BOTH destination axes reversed. Scissor + // keeps only its left half, so a plain unclipped vkCmdBlitImage is incorrect. + glScissor(width / 4, height / 4, width / 4, height / 2); + glEnable(GL_SCISSOR_TEST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBlitFramebuffer(0, 0, 6, 4, 3 * width / 4, 3 * height / 4, + width / 4, height / 4, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + EXPECT_EQ(ReadOnePixel(3 * width / 8, 3 * height / 8), (std::array{255, 255, 0, 255})) + << "reversed bottom-left destination reads the source top-right yellow quadrant"; + EXPECT_EQ(ReadOnePixel(3 * width / 8, 5 * height / 8), (std::array{0, 255, 0, 255})) + << "reversed top-left destination reads the source bottom-right green quadrant"; + const std::array untouched{255, 0, 255, 255}; + EXPECT_EQ(ReadOnePixel(5 * width / 8, 3 * height / 8), untouched) << "inside destination, outside scissor"; + EXPECT_EQ(ReadOnePixel(5 * width / 8, 5 * height / 8), untouched) << "upper destination outside scissor"; + EXPECT_EQ(ReadOnePixel(width / 8, height / 2), untouched) << "outside destination rectangle"; + EXPECT_EQ(ReadOnePixel(3 * width / 8, height / 8), untouched) << "below destination and scissor"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glDisable(GL_SCISSOR_TEST); + Gl().EndFrame(); +} + } // namespace MGITest