[Piglit] [PATCH crucible 2/2] Add test for VK_KHR_multiview

Caio Marcelo de Oliveira Filho caio.oliveira at intel.com
Wed Sep 26 19:01:49 UTC 2018


Uses the extension to write to two different layers of an image, then
merge those side-by-side in a final image.
---

This patch is available in my 'multiview' branch:

    https://gitlab.freedesktop.org/cmarcelo/crucible/commits/multiview

 Makefile.am                 |   2 +
 data/func.multiview.ref.png | Bin 0 -> 1878 bytes
 src/tests/func/multiview.c  | 373 ++++++++++++++++++++++++++++++++++++
 3 files changed, 375 insertions(+)
 create mode 100644 data/func.multiview.ref.png
 create mode 100644 src/tests/func/multiview.c

diff --git a/Makefile.am b/Makefile.am
index dc24a99..621ee83 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -99,6 +99,7 @@ bin_crucible_SOURCES = \
 	src/tests/func/compute-num-workgroups.c \
 	src/tests/func/interleaved-cmd-buffers.c \
 	src/tests/func/miptree/miptree.c \
+	src/tests/func/multiview.c \
 	src/tests/func/push-constants/basic.c \
 	src/tests/func/shader/constants.c \
 	src/tests/func/shader/fragcoord.c \
@@ -145,6 +146,7 @@ BUILT_SOURCES = \
 	src/tests/func/compute-num-workgroups-spirv.h \
 	src/tests/func/miptree/miptree-spirv.h \
 	src/tests/func/miptree/miptree_gen.c \
+	src/tests/func/multiview-spirv.h \
 	src/tests/func/push-constants/basic-spirv.h \
 	src/tests/func/amd/gcn_shader-spirv.h \
 	src/tests/func/amd/shader_trinary_minmax-spirv.h \
diff --git a/data/func.multiview.ref.png b/data/func.multiview.ref.png
new file mode 100644
index 0000000000000000000000000000000000000000..dfc81c2b6b3c60886dee1c006756e8b596ff8391
GIT binary patch
literal 1878
zcmds&|3A}t0LR~-eHF9jTG_?4>x;vk$JRMfYs}Ze=o57!v2$rB-KLW-H8N7^oa!El
z4l$QR*H$@GSbXGi5&5#^OW4+B%a_ueWNCB{kNf5RgL^#g$LBB4*W>xq>kJ&U&D?ag
zDFDFSZ at YIe01yBGh)i$*fKk~UQvmQ(KW~p+DaEfY({E6H$gN#*MXRkTuq#6xZwI at Q
z?W|1+cO4iS1_muX#YLWw2zHEe9GQ|?g;vmL@;Z57ZQH5gqjOzT#jiVx)%4A6frYid
zsqf5m4$e%~S{|rva9~5^TdBh^9l!v~!9ik$4U8yB;2<7gIe3UhUIQb)|NpR at C`y-J
zue5~hxLbnpNX@%tKxW4E?8PB1J|N<Uev}<|Z7>cg&FO6VoDBuEI?kfVoT7zjsg}ov
z0(|R1N_-&5yWwcD=c`^Udou}$Q2E)rApMX9#4yJQuCFq&<2IHr6Gd4TEz|?ePPUeG
zN%SQY|E<D_q&$kYUFQRgslI?g%G-n01^fW^@Lz_Y0F2;x24Fbs0~jPS1*dE4hV(5Z
zRUhmj<cg??CyjWVxc453>XrUFM%|y*Wlw=KMs~BlFwm#Nvj{^_YUsP$>bw*J=47jP
zM&6w=rbrT$UJnE>tOt1dHN7j_DAj1g{KJrv3~~D>8_VV9L(p(s{Jdh>-SC0 at M{yl#
zw1|T2XDG2&$D at sXyMMAdN^-nF5_BIv2cmZ4(q&x)jI&!CP5a##Q9L`o!&37?t1FJy
z(|>U`rBt8|#)l!L|1u|TT^Pp{b7of?FZwU6536E&Vc4B5eb^I(MgOZ)W_^>KxpSbj
zGxD<HE#2LT^d{-?nWs9p+Czu<`{xSYx2T<5Y8eLM-0p_nM7(~wJ*4UF&jOQU^#i0}
z)u~s&%`|@g(+P3{VX?iW at PLVW%gvFW_-JlvXnUpyF8yk-`fg-~)jl^=s;jU%W~j;7
z!S3#QWSi$3^Smbp-`yTHwS0%ARZp)*c((tUcQO?b<_0Z1<>4~j8u>VTK}!-yMiu|%
z$9#}_qA9qFJ|b;BDMg8Oel`Etqg?l0K#y!i$s3xR^pDuxBb(e07Rh!GQjRE~motdd
zn+vKnX<~*!`skwbwH%^G^1wcl+4-?a`^z4$_Mux;qzDR1*pi{!^oNb{qJP>%eA*yd
zT#-h{ny<{(6AXT8;_2g<lyAyr^4)LpXk+8iUdp1Tjwc(-n4)d-HX#<o6X70ctg<5C
zCs0t4=s!Tz$ijyD)T-xJnUQDt9p9sU?Itgrry{>X#!;JCenRO|#qz?)!%|QgJ1lI*
znR*}Z6E?Y at ZegM1)!m9KEFNv_`Q(90m!q5E%NA0!Yx(PqE8m?5)15u_Q*X5Kl|;?e
zFs~=K&Sr3h=R(U=!`g=v*$gU@^XVAVvl%Suljje+aDcMLTBgBZ*Sae%vYaJzuiUJO
z(<b`+p3E5GWA-hXf>$lLFbl6LB~jS{%h#^4p*3#ye_p8CZ+2FWPT}`CJYAXy=-(lp
zwROz>%(a)O3BxZ4?I>J*sDtypo>i1sp!{;O?r~i|_Z+Gr<HvNCw558R=-P=pFI4GK
z*<W+kE3N7eMR@!)lDv0P$nkO8#ZFDgYjwW&Y!%55uOIa+kbR(rj`B5mYA4o|Dq`Kl
z_8QfX*Q-<RCW9Rq{U_gF<z+QTRVm+8?0pL=W1XTmlD1Ck1~xDZ+7-?pk6UPN2Ms0Q
zG99N*u_|4T9KL>oDk^CFW7lYl?S_LrIo|{d8e=8_-%<LZ`%F8V at r>P_fX)XoZ77Yp
zZt?BBS=r5IwN~@pzIRc90%Sz(DNocdtdOAx$72=wx&7~rxOqtyfSKnnOE!hvj0raZ
zx1p+?g2s=;C1H|39ZY~^v2F>=vq&I(VWwB%vY3?n!8qROYJm|2y_eUhU{+tquVj`L
zihV`MR~9)&zxvAl*d~m~cTQgE5<Yq?^`w;N2 at G*1R<pJ_kvcO-^LffaigB<RTvA<1
z$O8H&OnL*j7DrEf&&ll)0li|2%S7PH+e!V#!WEV?UjQtO{N;fcCjl5aLIR%C5SBxL
e{sU}AYK-`-aCm2U^w{#H+w}7Z@|JpXGyewE7$+Y9

literal 0
HcmV?d00001

diff --git a/src/tests/func/multiview.c b/src/tests/func/multiview.c
new file mode 100644
index 0000000..bd0767f
--- /dev/null
+++ b/src/tests/func/multiview.c
@@ -0,0 +1,373 @@
+// Copyright 2018 Intel Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice (including the next
+// paragraph) shall be included in all copies or substantial portions of the
+// Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "tapi/t.h"
+
+#include "multiview-spirv.h"
+
+// Uses two subpasses, the first pass writes using VK_KHR_multiview to
+// a two layer image, with a displacement based on the gl_ViewIndex.
+// The second pass takes both layers as input and render them side by
+// side.
+
+static void
+test_multiview(void)
+{
+    t_require_ext("VK_KHR_multiview");
+
+    VkRenderPassMultiviewCreateInfo multiviewInfo = {
+        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO,
+        .subpassCount = 2,
+        .pViewMasks = (uint32_t[]) { 3, 1 },
+    };
+
+    VkRenderPass pass = qoCreateRenderPass(t_device,
+        .pNext = &multiviewInfo,
+        .attachmentCount = 4,
+        .pAttachments = (VkAttachmentDescription[]) {
+            {
+                // Multi layered image written in first subpass.
+                QO_ATTACHMENT_DESCRIPTION_DEFAULTS,
+                .format = VK_FORMAT_R8G8B8A8_UNORM,
+                .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+                .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+            },
+            {
+                QO_ATTACHMENT_DESCRIPTION_DEFAULTS,
+                .format = VK_FORMAT_R8G8B8A8_UNORM,
+                .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+                .initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+            },
+            {
+                QO_ATTACHMENT_DESCRIPTION_DEFAULTS,
+                .format = VK_FORMAT_R8G8B8A8_UNORM,
+                .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+                .initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+            },
+            {
+                // Final output merging both layers.
+                QO_ATTACHMENT_DESCRIPTION_DEFAULTS,
+                .format = VK_FORMAT_R8G8B8A8_UNORM,
+                .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+            },
+        },
+        .subpassCount = 2,
+        .pSubpasses = (VkSubpassDescription[]) {
+            {
+                QO_SUBPASS_DESCRIPTION_DEFAULTS,
+                .colorAttachmentCount = 1,
+                .pColorAttachments = (VkAttachmentReference[]) {
+                    {
+                        .attachment = 0,
+                        .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+                    },
+                },
+            },
+            {
+                QO_SUBPASS_DESCRIPTION_DEFAULTS,
+                .inputAttachmentCount = 2,
+                .pInputAttachments = (VkAttachmentReference[]) {
+                    {
+                        .attachment = 1,
+                        .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                    },
+                    {
+                        .attachment = 2,
+                        .layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                    }
+                },
+                .colorAttachmentCount = 1,
+                .pColorAttachments = (VkAttachmentReference[]) {
+                    {
+                        .attachment = 3,
+                        .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+                    },
+                },
+            },
+        },
+        .dependencyCount = 2,
+        .pDependencies = (VkSubpassDependency[]) {
+            {
+                .srcSubpass = VK_SUBPASS_EXTERNAL,
+                .dstSubpass = 0,
+                .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                .srcAccessMask = 0,
+                .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+            },
+            {
+                .srcSubpass = 0,
+                .dstSubpass = 1,
+                .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
+                .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
+            },
+        });
+
+    VkShaderModule first_vs = qoCreateShaderModuleGLSL(t_device, VERTEX,
+        QO_EXTENSION GL_EXT_multiview : enable
+
+        layout(location = 0) out vec4 v_color;
+
+        vec4 positions[3] = vec4[](
+            vec4(-0.5, -0.5, 0, 1),
+            vec4(-0.2,  0.5, 0, 1),
+            vec4(-0.8,  0.5, 0, 1)
+        );
+
+        vec4 colors[3] = vec4[](
+            vec4(1, 1, 0, 1),
+            vec4(1, 0, 1, 1),
+            vec4(0, 1, 1, 1)
+        );
+
+        vec4 displacement[2] = vec4[](
+            vec4(0, 0,   0, 0),
+            vec4(1, 0.2, 0, 0)
+        );
+
+        void main()
+        {
+            gl_Position = displacement[gl_ViewIndex] + positions[gl_VertexIndex];
+            v_color = colors[gl_VertexIndex];
+        }
+    );
+
+    VkShaderModule first_fs = qoCreateShaderModuleGLSL(t_device, FRAGMENT,
+        layout(location = 0) in vec4 v_color;
+        layout(location = 0) out vec4 f_color;
+
+        void main()
+        {
+            f_color = v_color;
+        }
+    );
+
+
+    VkPipelineLayout first_layout = qoCreatePipelineLayout(t_device);
+
+    VkPipeline first_pipeline = qoCreateGraphicsPipeline(t_device, t_pipeline_cache,
+        &(QoExtraGraphicsPipelineCreateInfo) {
+            QO_EXTRA_GRAPHICS_PIPELINE_CREATE_INFO_DEFAULTS,
+            .vertexShader = first_vs,
+            .fragmentShader = first_fs,
+            .pNext =
+        &(VkGraphicsPipelineCreateInfo) {
+            .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+            .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo) {
+                QO_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO_DEFAULTS,
+                .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+            },
+            .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo) {
+                QO_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO_DEFAULTS
+            },
+            .pVertexInputState = &(VkPipelineVertexInputStateCreateInfo) {},
+            .renderPass = pass,
+            .layout = first_layout,
+            .subpass = 0,
+        }});
+
+    VkShaderModule second_vs = qoCreateShaderModuleGLSL(t_device, VERTEX,
+        vec2 positions[6] = vec2[](
+            vec2( -1, -1 ),
+            vec2(  1, -1 ),
+            vec2(  1,  1 ),
+            vec2(  1,  1 ),
+            vec2( -1,  1 ),
+            vec2( -1, -1 )
+        );
+
+        void main()
+        {
+            gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+        }
+    );
+
+    VkShaderModule second_fs = qoCreateShaderModuleGLSL(t_device, FRAGMENT,
+        layout(input_attachment_index=0, set=0, binding=0) uniform subpassInput theInput[2];
+        layout(location = 0) out vec4 f_color;
+
+        void main()
+        {
+            f_color.a = 1.0;
+            f_color.r = subpassLoad(theInput[0]).r;
+            f_color.g = subpassLoad(theInput[1]).g;
+            f_color.b = 0.0;
+        }
+    );
+
+    VkDescriptorSetLayout set_layout = qoCreateDescriptorSetLayout(t_device,
+        .bindingCount = 1,
+        .pBindings = (VkDescriptorSetLayoutBinding[]) {
+            {
+                .binding = 0,
+                .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
+                .descriptorCount = 2,
+                .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
+            },
+        });
+
+    VkPipelineLayout second_layout = qoCreatePipelineLayout(t_device,
+        .setLayoutCount = 1,
+        .pSetLayouts = &set_layout);
+
+    VkPipeline second_pipeline = qoCreateGraphicsPipeline(t_device, t_pipeline_cache,
+        &(QoExtraGraphicsPipelineCreateInfo) {
+            QO_EXTRA_GRAPHICS_PIPELINE_CREATE_INFO_DEFAULTS,
+            .vertexShader = second_vs,
+            .fragmentShader = second_fs,
+            .pNext =
+        &(VkGraphicsPipelineCreateInfo) {
+            .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+            .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo) {
+                QO_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO_DEFAULTS,
+                .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+            },
+            .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo) {
+                QO_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO_DEFAULTS
+            },
+            .pVertexInputState = &(VkPipelineVertexInputStateCreateInfo) {},
+            .renderPass = pass,
+            .layout = second_layout,
+            .subpass = 1,
+        }});
+
+    VkImage layered_image = qoCreateImage(t_device,
+        .imageType = VK_IMAGE_TYPE_2D,
+        .format = VK_FORMAT_R8G8B8A8_UNORM,
+        .tiling = VK_IMAGE_TILING_OPTIMAL,
+        .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT,
+        .mipLevels = 1,
+        .arrayLayers = 2,
+        .extent = {
+            .width = t_width,
+            .height = t_height,
+            .depth = 1,
+        });
+
+    VkMemoryRequirements mem_reqs;
+    vkGetImageMemoryRequirements(t_device, layered_image, &mem_reqs);
+
+    VkDeviceMemory layered_mem = qoAllocMemoryFromRequirements(t_device, &mem_reqs);
+    qoBindImageMemory(t_device, layered_image, layered_mem, 0);
+
+    VkImageView full_image_view = qoCreateImageView(t_device,
+        QO_IMAGE_VIEW_CREATE_INFO_DEFAULTS,
+        .format = VK_FORMAT_R8G8B8A8_UNORM,
+        .image = layered_image,
+        .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
+        .subresourceRange.layerCount = 2);
+
+    VkImageView first_image_view = qoCreateImageView(t_device,
+        QO_IMAGE_VIEW_CREATE_INFO_DEFAULTS,
+        .format = VK_FORMAT_R8G8B8A8_UNORM,
+        .image = layered_image,
+        .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
+        .subresourceRange.layerCount = 1);
+
+    VkImageView second_image_view = qoCreateImageView(t_device,
+        QO_IMAGE_VIEW_CREATE_INFO_DEFAULTS,
+        .format = VK_FORMAT_R8G8B8A8_UNORM,
+        .image = layered_image,
+        .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
+        .subresourceRange.baseArrayLayer = 1,
+        .subresourceRange.layerCount = 1);
+
+    VkImageView attachments[] = {
+        full_image_view,
+        first_image_view,
+        second_image_view,
+
+        // We create a new framebuffer but reuse the framework's image
+        // view as a final output, so we get the bootstrapping and
+        // comparing logic for free.
+        t_color_image_view,
+    };
+
+    VkFramebuffer framebuffer = qoCreateFramebuffer(t_device,
+        .renderPass = pass,
+        .width = t_width,
+        .height = t_height,
+        .layers = 1,
+        .attachmentCount = 4,
+        .pAttachments = attachments);
+
+    VkDescriptorSet set = qoAllocateDescriptorSet(t_device,
+        .descriptorPool = t_descriptor_pool,
+        .pSetLayouts = &set_layout);
+
+    vkUpdateDescriptorSets(t_device,
+        1, /* writeCount */
+        &(VkWriteDescriptorSet) {
+            .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+            .dstSet = set,
+            .dstBinding = 0,
+            .dstArrayElement = 0,
+            .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
+            .descriptorCount = 2,
+            .pImageInfo = (VkDescriptorImageInfo[]) {
+                {
+                    .imageView = first_image_view,
+                    .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                },
+                {
+                    .imageView = second_image_view,
+                    .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                },
+            },
+        }, 0, NULL);
+    
+    vkCmdBeginRenderPass(t_cmd_buffer,
+        &(VkRenderPassBeginInfo) {
+            .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+            .renderPass = pass,
+            .framebuffer = framebuffer,
+            .renderArea = { { 0, 0 }, { t_width, t_height } },
+            .clearValueCount = 4,
+            .pClearValues = (VkClearValue[]) {
+                { .color = { .float32 = {0.0, 0.0, 0.0, 1.0} } },
+                { .color = { .float32 = {0.0, 0.0, 0.0, 1.0} } },
+                { .color = { .float32 = {0.0, 0.0, 0.0, 1.0} } },
+                { .color = { .float32 = {0.0, 0.0, 0.0, 1.0} } },
+            }
+        }, VK_SUBPASS_CONTENTS_INLINE);
+
+    // Draw triangle in both views, with displacement based on the view index.
+    vkCmdBindPipeline(t_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, first_pipeline);
+    vkCmdDraw(t_cmd_buffer, 3, 1, 0, 0);
+
+    // Render the views side by side.
+    vkCmdNextSubpass(t_cmd_buffer, VK_SUBPASS_CONTENTS_INLINE);
+    vkCmdBindPipeline(t_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, second_pipeline);
+    vkCmdBindDescriptorSets(t_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, second_layout,
+        0, 1, &set, 0, NULL);
+    vkCmdDraw(t_cmd_buffer, 6, 1, 0, 0);
+
+    vkCmdEndRenderPass(t_cmd_buffer);
+    qoEndCommandBuffer(t_cmd_buffer);
+    qoQueueSubmit(t_queue, 1, &t_cmd_buffer, VK_NULL_HANDLE);
+}
+
+test_define {
+    .name = "func.multiview",
+    .start = test_multiview,
+};
-- 
2.19.0



More information about the Piglit mailing list