Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions antora/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
** xref:samples/extensions/ray_tracing_reflection/README.adoc[Ray tracing reflection]
** xref:samples/extensions/ray_tracing_position_fetch/README.adoc[Ray tracing position fetch]
** xref:samples/extensions/shader_object/README.adoc[Shader Object]
** xref:samples/extensions/shader_quad_control/README.adoc[Shader quad control]
** xref:samples/extensions/shader_debugprintf/README.adoc[Shader Debug Printf]
** xref:samples/extensions/sparse_image/README.adoc[Sparse Image]
** xref:samples/extensions/synchronization_2/README.adoc[Synchronization 2]
Expand Down
6 changes: 6 additions & 0 deletions framework/vulkan_type_mapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ struct HPPType<VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR>
using Type = vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
};

template <>
struct HPPType<VkPhysicalDeviceShaderQuadControlFeaturesKHR>
{
using Type = vk::PhysicalDeviceShaderQuadControlFeaturesKHR;
};

template <>
struct HPPType<VkPhysicalDeviceFragmentShadingRateFeaturesKHR>
{
Expand Down
8 changes: 7 additions & 1 deletion samples/extensions/README.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
////
- Copyright (c) 2021-2024, The Khronos Group
- Copyright (c) 2021-2025, The Khronos Group
-
- SPDX-License-Identifier: Apache-2.0
-
Expand Down Expand Up @@ -302,3 +302,9 @@ Demonstrate the use of the host image extension to directly copy from a host buf
*Extensions:* https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state3.html[`VK_EXT_line_rasterization`], https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state3.html[`VK_EXT_extended_dynamic_state3`]

Demonstrate how to use dynamic multisample rasterization (MSAA)

=== xref:./{extension_samplespath}shader_quad_control/README.adoc[Shader quad control]

*Extension*: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_quad_control.html[`VK_KHR_shader_quad_control`]

Demonstrates quad‑scope operations in fragment shaders, such as broadcasting values within a 2x2 quad, and explains the `layout(full_quads)` and `layout(quad_derivatives)` execution modes.
29 changes: 29 additions & 0 deletions samples/extensions/shader_quad_control/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (c) 2025, Holochip Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 the "License";
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)

add_sample(
ID ${FOLDER_NAME}
CATEGORY ${CATEGORY_NAME}
AUTHOR "Holochip"
NAME "Shader quad control"
DESCRIPTION "Demonstrates VK_KHR_shader_quad_control with a simple fullscreen draw using quad vote ops."
SHADER_FILES_GLSL
"shader_quad_control/glsl/quad_control.vert"
"shader_quad_control/glsl/quad_control.frag")
84 changes: 84 additions & 0 deletions samples/extensions/shader_quad_control/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
////
- Copyright (c) 2025, Holochip Inc.
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
////

ifdef::site-gen-antora[]
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/shader_quad_control[Khronos Vulkan samples github repository].
endif::[]

= Shader quad control (VK_KHR_shader_quad_control)

This sample demonstrates VK_KHR_shader_quad_control in a minimal graphics pipeline.
It renders a full‑screen triangle and uses quad‑scope operations in the fragment shader to
broadcast values within a 2x2 pixel quad. This produces a characteristic “blocky” 2x2 pattern,
clearly showing how quad operations affect shading.

== What is shader quad control?
VK_KHR_shader_quad_control exposes SPIR‑V and shading language capabilities that operate at the
scope of a fragment “quad” – the 2x2 group of fragment shader invocations that participate in
coherent derivative calculation. The extension adds:

- A shader execution mode to control quad formation: `layout(full_quads)` or `layout(quad_derivatives)`.
- Built‑ins and functions to communicate within a quad, like `subgroupQuadBroadcast(...)`, and vote
operations `subgroupQuadAll(...)` / `subgroupQuadAny(...)`.

Typical uses include:

- Stabilizing derivatives, LOD selection, and gradient‑sensitive operations across the 2x2 quad.
- Sharing values between lanes in a quad without using shared memory.

== What this sample does
- Creates a simple graphics pipeline (full‑screen triangle, no descriptors).
- The fragment shader enables `GL_EXT_shader_quad_control` and declares `layout(full_quads) in;`.
- It calls `subgroupQuadBroadcast(vUV, 0)` to broadcast the top‑left lane’s interpolant to the
whole quad, so each 2x2 block shows one color based on the leader lane.

This minimal approach keeps focus on the quad control feature rather than complex rendering.

== Required extensions and features
To run this sample, the device must support `VK_KHR_shader_quad_control`.

No descriptors or additional states are required for this minimal demo. The sample uses the
framework’s default render pass and a basic pipeline configuration.

== Fragment shader
The fragment shader uses quad control to broadcast a varying from the quad leader:

[source,glsl]
----
#version 450
#extension GL_EXT_shader_quad_control : require

layout(full_quads) in; // Control quad scope
layout(location = 0) in vec2 vUV; // Interpolated UV from VS
layout(location = 0) out vec4 outColor;

void main()
{
vec2 uv_leader = subgroupQuadBroadcast(vUV, 0); // From top-left of the 2x2 quad
outColor = vec4(uv_leader, 0.5, 1.0);
}
----

Switching to `layout(quad_derivatives) in;` would instead ensure that implicit derivatives are
computed in a quad‑coherent way (useful if you perform gradient operations like texture LODs).
This sample focuses on the broadcasting operation for clarity.

== Specification
- Vulkan: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_quad_control.html[VK_KHR_shader_quad_control]
- GLSL: https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GLSL_EXT_shader_quad_control.txt[GLSL_EXT_shader_quad_control]
190 changes: 190 additions & 0 deletions samples/extensions/shader_quad_control/shader_quad_control.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/* Copyright (c) 2025, Holochip Inc.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "shader_quad_control.h"

ShaderQuadControl::ShaderQuadControl()
{
title = "Shader quad control";
set_api_version(VK_API_VERSION_1_2);

add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
// VK_KHR_shader_quad_control requires VK_KHR_shader_maximal_reconvergence per spec
add_device_extension(VK_KHR_SHADER_MAXIMAL_RECONVERGENCE_EXTENSION_NAME);
add_device_extension(VK_KHR_SHADER_QUAD_CONTROL_EXTENSION_NAME);
}

ShaderQuadControl::~ShaderQuadControl()
{
if (has_device())
{
if (pipeline != VK_NULL_HANDLE)
{
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
}
if (pipeline_layout != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
}
}
}

bool ShaderQuadControl::prepare(const vkb::ApplicationOptions &options)
{
if (!ApiVulkanSample::prepare(options))
{
return false;
}

create_pipeline_layout();
create_pipeline();
build_command_buffers();

prepared = true;
return true;
}

void ShaderQuadControl::request_gpu_features(vkb::core::PhysicalDeviceC &gpu)
{
REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceShaderQuadControlFeaturesKHR, shaderQuadControl);
}

void ShaderQuadControl::create_pipeline_layout()
{
VkPipelineLayoutCreateInfo pipeline_layout_ci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_ci, nullptr, &pipeline_layout));
}

void ShaderQuadControl::create_pipeline()
{
std::array<VkPipelineShaderStageCreateInfo, 2> stages{};
stages[0] = load_shader("shader_quad_control/glsl/quad_control.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
stages[1] = load_shader("shader_quad_control/glsl/quad_control.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);

VkPipelineVertexInputStateCreateInfo vertex_input{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};

VkPipelineInputAssemblyStateCreateInfo input_assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;

VkPipelineViewportStateCreateInfo viewport_state{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
viewport_state.viewportCount = 1;
viewport_state.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 msaa{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

VkPipelineDepthStencilStateCreateInfo depth_stencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
depth_stencil.depthTestEnable = VK_FALSE;
depth_stencil.depthWriteEnable = VK_FALSE;

VkPipelineColorBlendAttachmentState color_blend_attachment{};
color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

VkPipelineColorBlendStateCreateInfo color_blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
color_blend.attachmentCount = 1;
color_blend.pAttachments = &color_blend_attachment;

std::array<VkDynamicState, 2> dynamic_states{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamic_state{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
dynamic_state.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic_state.pDynamicStates = dynamic_states.data();

VkGraphicsPipelineCreateInfo pipeline_ci{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
pipeline_ci.stageCount = static_cast<uint32_t>(stages.size());
pipeline_ci.pStages = stages.data();
pipeline_ci.pVertexInputState = &vertex_input;
pipeline_ci.pInputAssemblyState = &input_assembly;
pipeline_ci.pViewportState = &viewport_state;
pipeline_ci.pRasterizationState = &raster;
pipeline_ci.pMultisampleState = &msaa;
pipeline_ci.pDepthStencilState = &depth_stencil;
pipeline_ci.pColorBlendState = &color_blend;
pipeline_ci.pDynamicState = &dynamic_state;
pipeline_ci.layout = pipeline_layout;
pipeline_ci.renderPass = render_pass;

VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &pipeline_ci, nullptr, &pipeline));
}

void ShaderQuadControl::build_command_buffers()
{
VkCommandBufferBeginInfo begin_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
VkClearValue clear_values[2];
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
clear_values[1].depthStencil = {1.0f, 0};

VkRenderPassBeginInfo render_pass_begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
render_pass_begin.renderPass = render_pass;
render_pass_begin.renderArea.offset = {0, 0};
render_pass_begin.renderArea.extent = get_render_context().get_surface_extent();
render_pass_begin.clearValueCount = 2;
render_pass_begin.pClearValues = clear_values;

for (size_t i = 0; i < draw_cmd_buffers.size(); i++)
{
render_pass_begin.framebuffer = framebuffers[i];
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &begin_info));

vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);

VkViewport viewport{};
viewport.width = static_cast<float>(get_render_context().get_surface_extent().width);
viewport.height = static_cast<float>(get_render_context().get_surface_extent().height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);

VkRect2D scissor{{0, 0}, get_render_context().get_surface_extent()};
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);

vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);

draw_ui(draw_cmd_buffers[i]);
vkCmdEndRenderPass(draw_cmd_buffers[i]);
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
}
}

void ShaderQuadControl::draw()
{
prepare_frame();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
submit_frame();
}

void ShaderQuadControl::render(float)
{
if (!prepared)
{
return;
}
draw();
}

std::unique_ptr<vkb::VulkanSampleC> create_shader_quad_control()
{
return std::make_unique<ShaderQuadControl>();
}
42 changes: 42 additions & 0 deletions samples/extensions/shader_quad_control/shader_quad_control.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* Copyright (c) 2025, Holochip Inc.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include "api_vulkan_sample.h"

class ShaderQuadControl : public ApiVulkanSample
{
public:
ShaderQuadControl();
~ShaderQuadControl() override;

bool prepare(const vkb::ApplicationOptions &options) override;
void build_command_buffers() override;
void request_gpu_features(vkb::core::PhysicalDeviceC &gpu) override;
void render(float delta_time) override;

private:
void create_pipeline_layout();
void create_pipeline();
void draw();

VkPipeline pipeline{VK_NULL_HANDLE};
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
};

std::unique_ptr<vkb::VulkanSampleC> create_shader_quad_control();
Loading
Loading