Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ 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)

# Enable debug info for Slang shaders to trigger OpExtInstWithForwardRef emission
set(SLANGC_ADDITIONAL_ARGUMENTS "-g3" "-O0")

add_sample_with_tags(
ID ${FOLDER_NAME}
CATEGORY ${CATEGORY_NAME}
AUTHOR "Holochip"
NAME "Shader relaxed extended instruction"
DESCRIPTION "Demonstrates enabling VK_KHR_shader_relaxed_extended_instruction and requesting its feature"
SHADER_FILES_SLANG
"shader_relaxed_extended_instruction/slang/relaxed_demo.comp.slang"
SHADER_FILES_HLSL
"shader_relaxed_extended_instruction/hlsl/relaxed_demo.comp.hlsl"
DXC_ADDITIONAL_ARGUMENTS "-fspv-target-env=vulkan1.3 -fspv-debug=vulkan-with-source -O3"
)
49 changes: 25 additions & 24 deletions samples/extensions/shader_relaxed_extended_instruction/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,15 @@ The feature is exposed via `VkPhysicalDeviceShaderRelaxedExtendedInstructionFeat
== What this sample does
- Enables device extensions: `VK_KHR_shader_relaxed_extended_instruction` and `VK_KHR_shader_non_semantic_info`.
- Requests the feature via the framework’s feature‑chaining helper.
- Builds a tiny compute pipeline whose shader calls `debugPrintfEXT` (a non‑semantic extended instruction).
- Records a per‑frame command buffer that dispatches the compute shader once, then transitions the swapchain image to `PRESENT` and presents. This keeps WSI synchronization correct and demonstrates consumption of a shader module that contains non‑semantic extended instructions.
- Builds a tiny compute pipeline whose shader calls `debugPrintfEXT` (a non‑semantic extended instruction) using a class method to demonstrate the relaxed forward‑reference pattern.
- Renders a visible UI and background and only dispatches the compute shader on demand (or when a UI value changes). This avoids per‑frame logging spam while still demonstrating consumption of a module that contains non‑semantic extended instructions.

== How to use it
- The sample shows a small UI panel with:
* A `Value` slider (integer). Changing it triggers a one‑shot compute dispatch that prints the new value from the shader.
* A `Dispatch once` button to manually run a one‑shot dispatch.
* “Last messages” — the last 5 lines captured from `debugPrintf` via `VK_EXT_debug_utils`.
- The shader prints only from thread `(0,0,0)` and only when you request a dispatch, so there is no per‑frame console spam.

== Required Vulkan extensions and features
- Device extensions (required by this sample):
Expand All @@ -50,6 +57,8 @@ The feature is exposed via `VkPhysicalDeviceShaderRelaxedExtendedInstructionFeat
- Instance extension for feature chaining: `VK_KHR_get_physical_device_properties2` (the framework enables this; the sample requests it explicitly).
- Device feature (required): `VkPhysicalDeviceShaderRelaxedExtendedInstructionFeaturesKHR::shaderRelaxedExtendedInstruction = VK_TRUE`

NOTE: This sample relies on the Vulkan Validation Layers (VVL) to capture and display `debugPrintfEXT` output from the non‑semantic instruction set. Ensure that VVL is enabled when running the sample. Debug builds of Vulkan‑Samples automatically enable validation; on Release builds you may need to enable validation explicitly via your environment or runtime settings.

Code excerpt:
[source,cpp]
----
Expand All @@ -69,39 +78,31 @@ void ShaderRelaxedExtendedInstruction::request_gpu_features(vkb::core::PhysicalD
}
----

Shader (GLSL) used by this sample:
[source,glsl]
----
#version 450
#extension GL_EXT_debug_printf : enable
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
void main() {
debugPrintfEXT("relaxed-ext-inst demo: gid = %u", gl_GlobalInvocationID.x);
}
Shader (HLSL, compiled with DXC) used by this sample:
[source,hlsl]
----
// RUN: %dxc %s -T cs_6_0 -spirv -fspv-target-env=vulkan1.3 -E main -fspv-debug=vulkan-with-source -O3

== Slang shader variant and OpExtInstWithForwardRef
struct PushConstants { uint value; };
[[vk::push_constant]] PushConstants pc;

This sample also includes a Slang shader variant that demonstrates the pattern intended to trigger `OpExtInstWithForwardRef` emission. The Slang shader uses a struct with a method that calls `printf`, which creates the structure that should produce circular dependencies in debug instructions when compiled with rich debug info (e.g., `NonSemantic.Shader.DebugInfo.100`).

[source,slang]
----
struct A {
void foo(uint3 gid) {
printf("relaxed-ext-inst demo: gid = %u", gid.x);
}
class A {
void foo(uint v) {
printf("relaxed-ext-inst demo: value = %u", v);
}
};

[shader("compute")]
[numthreads(1, 1, 1)]
void main(uint3 gid : SV_DispatchThreadID)
{
A a;
a.foo(gid);
A a;
if (all(gid == uint3(0,0,0))) { a.foo(pc.value); }
}
----

NOTE: As of Slang in Vulkan SDK 1.4.328, the compiler does not yet emit the rich debug info needed to trigger `OpExtInstWithForwardRef` emission. The shader is structured correctly and will emit `OpExtInstWithForwardRef` once Slang's debug info support is enhanced. The `OpExtInstWithForwardRef` instruction is specifically designed to handle forward references in extended instruction sets, which can occur when debug info creates circular dependencies (e.g., a class method referencing debug info that references the class).
== Why HLSL (DXC) instead of Slang or GLSL?

At the time of writing, Slang and glslc in the Vulkan SDK does not emit the necessary debug information patterns that lead to `OpExtInstWithForwardRef` in SPIR-V for this demonstration. The HLSL/DXC path above does emit the required SPIR-V relaxed extended instruction sequence, so this sample uses HLSL for reliability.

TIP: To actually see the `debugPrintfEXT` output, run with validation configured to capture debug printf (see the `shader_debugprintf` sample or use VK_EXT_layer_settings to enable `VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT`). This sample registers an INFO‑severity `VkDebugUtilsMessengerEXT` so messages are visible when validation is active.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "common/vk_common.h"
#include "common/vk_initializers.h"

// Minimal debug utils callback to print INFO severity messages (e.g., debugPrintfEXT)
// Minimal debug utils callback to capture INFO severity messages (e.g., debugPrintfEXT)
static VKAPI_ATTR VkBool32 VKAPI_CALL s_debug_utils_message_callback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
Expand All @@ -29,10 +29,10 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL s_debug_utils_message_callback(
{
(void) messageSeverity;
(void) messageType;
(void) pUserData;
if (pCallbackData && pCallbackData->pMessage)
if (pCallbackData && pCallbackData->pMessage && pUserData)
{
LOGI("{}", pCallbackData->pMessage);
auto *self = reinterpret_cast<ShaderRelaxedExtendedInstruction *>(pUserData);
self->append_message(pCallbackData->pMessage);
}
return VK_FALSE;
}
Expand All @@ -43,25 +43,22 @@ ShaderRelaxedExtendedInstruction::ShaderRelaxedExtendedInstruction()

// Instance prerequisite for feature chaining and layer settings (optional)
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
add_instance_extension(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME, /*optional*/ true);

// Device extensions used by this demo
add_device_extension(VK_KHR_SHADER_RELAXED_EXTENDED_INSTRUCTION_EXTENSION_NAME);
// Non-semantic info is the SPIR-V mechanism for non-semantic extended instruction sets
add_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME);

// Optionally, enable debug printf so shaders using debugPrintfEXT will print via VVL
// Enable debug printf so shaders using debugPrintfEXT will print via VVL
{
static const char *enables[] = {
"VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT",
};
static const VkBool32 printf_enable = VK_TRUE;

VkLayerSettingEXT layer_setting{};
layer_setting.pLayerName = "VK_LAYER_KHRONOS_validation";
layer_setting.pSettingName = "enables";
layer_setting.type = VK_LAYER_SETTING_TYPE_STRING_EXT;
layer_setting.valueCount = static_cast<uint32_t>(std::size(enables));
layer_setting.pValues = enables;
layer_setting.pSettingName = "printf_enable";
layer_setting.type = VK_LAYER_SETTING_TYPE_BOOL32_EXT;
layer_setting.valueCount = 1;
layer_setting.pValues = &printf_enable;
add_layer_setting(layer_setting);
}
}
Expand Down Expand Up @@ -90,7 +87,33 @@ ShaderRelaxedExtendedInstruction::~ShaderRelaxedExtendedInstruction()

void ShaderRelaxedExtendedInstruction::build_command_buffers()
{
// Intentionally empty; this sample records per-frame in render().
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();

VkClearValue clear_values[2];
clear_values[0].color = {{0.02f, 0.02f, 0.03f, 1.0f}}; // subtle dark background
clear_values[1].depthStencil = {1.0f, 0};

for (int32_t i = 0; i < static_cast<int32_t>(draw_cmd_buffers.size()); ++i)
{
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));

VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
render_pass_begin_info.framebuffer = framebuffers[i];
render_pass_begin_info.renderPass = render_pass;
render_pass_begin_info.clearValueCount = 2;
render_pass_begin_info.renderArea.extent.width = width;
render_pass_begin_info.renderArea.extent.height = height;
render_pass_begin_info.pClearValues = clear_values;

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

// Draw only the UI for this sample
draw_ui(draw_cmd_buffers[i]);

vkCmdEndRenderPass(draw_cmd_buffers[i]);

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

void ShaderRelaxedExtendedInstruction::request_gpu_features(vkb::core::PhysicalDeviceC &gpu)
Expand All @@ -102,7 +125,8 @@ void ShaderRelaxedExtendedInstruction::request_gpu_features(vkb::core::PhysicalD
std::unique_ptr<vkb::core::InstanceC> ShaderRelaxedExtendedInstruction::create_instance()
{
LOGI("ShaderRelaxedExtendedInstruction::create_instance override invoked");
auto debugprintf_api_version = VK_API_VERSION_1_2;
// Use Vulkan 1.3 so validation uses SPIR-V 1.6 semantics, matching our DXC target
auto debugprintf_api_version = VK_API_VERSION_1_3;

// Enumerate layers to find VVL and its version
uint32_t layer_property_count = 0;
Expand All @@ -115,7 +139,8 @@ std::unique_ptr<vkb::core::InstanceC> ShaderRelaxedExtendedInstruction::create_i
return strcmp(p.layerName, validation_layer_name) == 0;
});

if (vvl_properties != layer_properties.end())
const bool validation_layer_available = (vvl_properties != layer_properties.end());
if (validation_layer_available)
{
// Does VVL advertise VK_EXT_layer_settings?
uint32_t vvl_extension_count = 0;
Expand Down Expand Up @@ -160,15 +185,19 @@ std::unique_ptr<vkb::core::InstanceC> ShaderRelaxedExtendedInstruction::create_i
}
#endif

// Enable validation features to activate debugPrintf
// Enable validation features to activate debugPrintf (works when validation layer is present)
enabled_extensions.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);

VkApplicationInfo app_info{VK_STRUCTURE_TYPE_APPLICATION_INFO};
app_info.pApplicationName = "Shader relaxed extended instruction";
app_info.pEngineName = "Vulkan Samples";
app_info.apiVersion = debugprintf_api_version;

std::vector<const char *> validation_layers = {validation_layer_name};
std::vector<const char *> validation_layers;
if (validation_layer_available)
{
validation_layers.push_back(validation_layer_name);
}

std::vector<VkValidationFeatureEnableEXT> validation_feature_enables = {VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT};
VkValidationFeaturesEXT validation_features{VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT};
Expand All @@ -179,7 +208,7 @@ std::unique_ptr<vkb::core::InstanceC> ShaderRelaxedExtendedInstruction::create_i
instance_create_info.ppEnabledExtensionNames = enabled_extensions.data();
instance_create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
instance_create_info.pApplicationInfo = &app_info;
instance_create_info.ppEnabledLayerNames = validation_layers.data();
instance_create_info.ppEnabledLayerNames = validation_layers.empty() ? nullptr : validation_layers.data();
instance_create_info.enabledLayerCount = static_cast<uint32_t>(validation_layers.size());
#if (defined(VKB_ENABLE_PORTABILITY))
if (portability_enumeration_available)
Expand Down Expand Up @@ -210,58 +239,40 @@ bool ShaderRelaxedExtendedInstruction::prepare(const vkb::ApplicationOptions &op
ci.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
ci.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
ci.pfnUserCallback = s_debug_utils_message_callback;
ci.pUserData = this;
VK_CHECK(vkCreateDebugUtilsMessengerEXT(get_instance().get_handle(), &ci, nullptr, &debug_utils_messenger));
}

// Create a minimal compute pipeline using a shader that emits a non-semantic
// extended instruction (debugPrintfEXT) to demonstrate interaction with
// VK_KHR_shader_non_semantic_info and the SPIR-V relaxed extended instruction rules.
{
VkPushConstantRange pc_range{};
pc_range.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
pc_range.offset = 0;
pc_range.size = sizeof(uint32_t);

VkPipelineLayoutCreateInfo layout_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = &pc_range;
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &layout_info, nullptr, &pipeline_layout));

VkPipelineShaderStageCreateInfo stage = load_shader("shader_relaxed_extended_instruction/slang/relaxed_demo.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
VkPipelineShaderStageCreateInfo stage = load_shader("shader_relaxed_extended_instruction/hlsl/relaxed_demo.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);

VkComputePipelineCreateInfo compute_ci{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
compute_ci.stage = stage;
compute_ci.layout = pipeline_layout;
VK_CHECK(vkCreateComputePipelines(get_device().get_handle(), pipeline_cache, 1, &compute_ci, nullptr, &compute_pipeline));
}
// Record the UI render pass command buffers now so they are valid for submission
build_command_buffers();

prepared = true;
return true;
}

void ShaderRelaxedExtendedInstruction::record_minimal_present_cmd(VkCommandBuffer cmd) const
{
VkCommandBufferBeginInfo begin_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
VK_CHECK(vkBeginCommandBuffer(cmd, &begin_info));

// Demonstrate a non-semantic extended instruction by dispatching the compute shader
// which calls debugPrintfEXT. If validation is configured to capture debug printf,
// you should see a message per dispatch.
if (compute_pipeline != VK_NULL_HANDLE)
{
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, compute_pipeline);
vkCmdDispatch(cmd, 1, 1, 1);
}

// Transition the acquired swapchain image to PRESENT so validation is happy
VkImageSubresourceRange subresource_range{};
subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresource_range.baseMipLevel = 0;
subresource_range.levelCount = 1;
subresource_range.baseArrayLayer = 0;
subresource_range.layerCount = 1;

vkb::image_layout_transition(cmd,
swapchain_buffers[current_buffer].image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
subresource_range);

VK_CHECK(vkEndCommandBuffer(cmd));
}
void ShaderRelaxedExtendedInstruction::record_minimal_present_cmd(VkCommandBuffer) const
{}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty function?


void ShaderRelaxedExtendedInstruction::render(float)
{
Expand All @@ -273,13 +284,24 @@ void ShaderRelaxedExtendedInstruction::render(float)
// Acquire next image
prepare_frame();

// Recreate and record a minimal command buffer for this frame
recreate_current_command_buffer();
auto &cmd = draw_cmd_buffers[current_buffer];
record_minimal_present_cmd(cmd);
// Conditionally run the compute dispatch if the UI value changed or if explicitly requested
const bool value_changed = (ui_value_ != last_dispatched_value_);
if ((value_changed || request_dispatch_once_) && compute_pipeline != VK_NULL_HANDLE)
{
request_dispatch_once_ = false;
last_dispatched_value_ = ui_value_;
auto push_value = ui_value_;

with_command_buffer([&](VkCommandBuffer command_buffer) {
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute_pipeline);
vkCmdPushConstants(command_buffer, pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(uint32_t), &push_value);
vkCmdDispatch(command_buffer, 1, 1, 1);
});
}

// Submit with acquire->present synchronization
VkPipelineStageFlags wait_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
// Submit pre-recorded UI render commands
auto &cmd = draw_cmd_buffers[current_buffer];
VkPipelineStageFlags wait_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &semaphores.acquired_image_ready;
Expand All @@ -299,3 +321,48 @@ std::unique_ptr<vkb::Application> create_shader_relaxed_extended_instruction()
{
return std::make_unique<ShaderRelaxedExtendedInstruction>();
}

void ShaderRelaxedExtendedInstruction::append_message(const char *msg)
{
if (!msg)
return;
last_messages_.emplace_back(msg);
while (last_messages_.size() > kMaxMessages_)
{
last_messages_.pop_front();
}
}

void ShaderRelaxedExtendedInstruction::on_update_ui_overlay(vkb::Drawer &drawer)
{
if (drawer.header("VK_KHR_shader_relaxed_extended_instruction"))
{
const bool has_ext = get_device().is_extension_enabled(VK_KHR_SHADER_RELAXED_EXTENDED_INSTRUCTION_EXTENSION_NAME);
const bool has_info = get_device().is_extension_enabled(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME);
drawer.text("Device extensions: relaxed_extended_instruction=%s, non_semantic_info=%s", has_ext ? "ON" : "OFF", has_info ? "ON" : "OFF");
drawer.text("This feature enables SPIR-V modules that use relaxed forward-refs in extended instruction sets (e.g., DebugPrintf).\nUseful when tools emit richer debug info that would otherwise be rejected.");

int32_t value_ui = static_cast<int32_t>(ui_value_);
if (drawer.slider_int("Value", &value_ui, 0, 1000))
{
ui_value_ = static_cast<uint32_t>(value_ui);
}
if (drawer.button("Dispatch once"))
{
request_dispatch_once_ = true;
}

drawer.text("Last messages (max %d):", static_cast<int>(kMaxMessages_));
if (last_messages_.empty())
{
drawer.text("<no messages yet>");
}
else
{
for (auto it = last_messages_.rbegin(); it != last_messages_.rend(); ++it)
{
drawer.text("%s", it->c_str());
}
}
}
}
Loading
Loading