12 void VK_Stencil::initialize(
const VulkanContext &context, VkExtent2D
extent, VkFormat color_format, VkFormat depth_format, VkPipelineCache pipeline_cache,
const std::string &mask_vertex_shader,
const std::string &mask_fragment_shader,
const std::string &content_vertex_shader,
const std::string &content_fragment_shader) {
16 render_color_format = color_format;
17 render_depth_format = depth_format;
18 cache = pipeline_cache;
19 mask_vertex_path = mask_vertex_shader;
20 mask_fragment_path = mask_fragment_shader;
21 content_vertex_path = content_vertex_shader;
22 content_fragment_path = content_fragment_shader;
23 stencil_image_format = choose_stencil_format();
33 render_color_format = VK_FORMAT_UNDEFINED;
34 render_depth_format = VK_FORMAT_UNDEFINED;
35 stencil_image_format = VK_FORMAT_UNDEFINED;
36 cache = VK_NULL_HANDLE;
37 mask_vertex_path.clear();
38 mask_fragment_path.clear();
39 content_vertex_path.clear();
40 content_fragment_path.clear();
44 if (
extent.width == stencil_extent.width &&
extent.height == stencil_extent.height) {
57 VkImageMemoryBarrier2 barrier{};
58 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
59 barrier.srcStageMask = image_initialized ? VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT : VK_PIPELINE_STAGE_2_NONE;
60 barrier.srcAccessMask = image_initialized ? VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT : VK_ACCESS_2_NONE;
61 barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
62 barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
63 barrier.oldLayout = image_initialized ? VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
64 barrier.newLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
65 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
66 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
67 barrier.image = image;
68 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
69 barrier.subresourceRange.levelCount = 1;
70 barrier.subresourceRange.layerCount = 1;
72 VkDependencyInfo dependency{};
73 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
74 dependency.imageMemoryBarrierCount = 1;
75 dependency.pImageMemoryBarriers = &barrier;
76 vkCmdPipelineBarrier2(cmd, &dependency);
77 image_initialized =
true;
84 attachment.imageView = view;
85 attachment.imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
86 attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
87 attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
88 attachment.clearValue.depthStencil = {1.0f, 0U};
92 if (mask_pipeline == VK_NULL_HANDLE || mask_layout == VK_NULL_HANDLE) {
95 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, mask_pipeline);
96 vkCmdPushConstants(cmd, mask_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(
PushConstants), &push_constants);
97 vkCmdDraw(cmd, 3, 1, 0, 0);
101 if (content_pipeline == VK_NULL_HANDLE || content_layout == VK_NULL_HANDLE) {
104 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, content_pipeline);
105 vkCmdPushConstants(cmd, content_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(
PushConstants), &push_constants);
106 vkCmdDraw(cmd, 3, 1, 0, 0);
109 bool VK_Stencil::valid() const noexcept {
return vk_context.device != VK_NULL_HANDLE && image != VK_NULL_HANDLE && view != VK_NULL_HANDLE && stencil_extent.width > 0U && stencil_extent.height > 0U; }
111 void VK_Stencil::create_resources() {
112 if (vk_context.
device == VK_NULL_HANDLE || stencil_extent.width == 0U || stencil_extent.height == 0U) {
115 create_image(vk_context, stencil_extent.width, stencil_extent.height, stencil_image_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, image, memory);
117 image_initialized =
false;
120 void VK_Stencil::create_pipelines() {
121 VkPushConstantRange push_constant{};
122 push_constant.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
123 push_constant.offset = 0;
126 VkPipelineLayoutCreateInfo layout_info{};
127 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
128 layout_info.pushConstantRangeCount = 1;
129 layout_info.pPushConstantRanges = &push_constant;
130 if (vkCreatePipelineLayout(vk_context.device, &layout_info,
nullptr, &mask_layout) != VK_SUCCESS || vkCreatePipelineLayout(vk_context.device, &layout_info,
nullptr, &content_layout) != VK_SUCCESS) {
131 throw mxvk::Exception(
"failed to create stencil pipeline layout");
134 mask_pipeline = create_pipeline(mask_vertex_path, mask_fragment_path, mask_layout,
true);
135 content_pipeline = create_pipeline(content_vertex_path, content_fragment_path, content_layout,
false);
138 void VK_Stencil::destroy_pipelines() {
139 if (vk_context.device == VK_NULL_HANDLE) {
140 mask_layout = VK_NULL_HANDLE;
141 content_layout = VK_NULL_HANDLE;
142 mask_pipeline = VK_NULL_HANDLE;
143 content_pipeline = VK_NULL_HANDLE;
146 if (mask_pipeline != VK_NULL_HANDLE) {
147 vkDestroyPipeline(vk_context.device, mask_pipeline,
nullptr);
149 if (content_pipeline != VK_NULL_HANDLE) {
150 vkDestroyPipeline(vk_context.device, content_pipeline,
nullptr);
152 if (mask_layout != VK_NULL_HANDLE) {
153 vkDestroyPipelineLayout(vk_context.device, mask_layout,
nullptr);
155 if (content_layout != VK_NULL_HANDLE) {
156 vkDestroyPipelineLayout(vk_context.device, content_layout,
nullptr);
158 mask_layout = VK_NULL_HANDLE;
159 content_layout = VK_NULL_HANDLE;
160 mask_pipeline = VK_NULL_HANDLE;
161 content_pipeline = VK_NULL_HANDLE;
164 void VK_Stencil::destroy_image() {
165 if (vk_context.device == VK_NULL_HANDLE) {
166 image = VK_NULL_HANDLE;
167 memory = VK_NULL_HANDLE;
168 view = VK_NULL_HANDLE;
169 image_initialized =
false;
172 if (view != VK_NULL_HANDLE) {
173 vkDestroyImageView(vk_context.device, view,
nullptr);
175 if (image != VK_NULL_HANDLE) {
176 vkDestroyImage(vk_context.device, image,
nullptr);
178 if (memory != VK_NULL_HANDLE) {
179 vkFreeMemory(vk_context.device, memory,
nullptr);
181 image = VK_NULL_HANDLE;
182 memory = VK_NULL_HANDLE;
183 view = VK_NULL_HANDLE;
184 image_initialized =
false;
187 VkFormat VK_Stencil::choose_stencil_format()
const {
188 const std::array<VkFormat, 3> candidates{
190 VK_FORMAT_D24_UNORM_S8_UINT,
191 VK_FORMAT_D32_SFLOAT_S8_UINT,
194 for (
const VkFormat format : candidates) {
195 VkFormatProperties properties{};
196 vkGetPhysicalDeviceFormatProperties(vk_context.physical_device, format, &properties);
197 if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0U) {
201 throw mxvk::Exception(
"failed to find a supported Vulkan stencil format");
204 VkPipeline VK_Stencil::create_pipeline(
const std::string &vertex_shader,
const std::string &fragment_shader, VkPipelineLayout layout,
bool writes_stencil)
const {
205 const std::vector<char> vert_bytes =
load_spv(vertex_shader);
206 const std::vector<char> frag_bytes =
load_spv(fragment_shader);
208 VkShaderModule frag_module = VK_NULL_HANDLE;
209 VkPipeline pipeline = VK_NULL_HANDLE;
213 std::array<VkPipelineShaderStageCreateInfo, 2> stages{};
214 stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
215 stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
216 stages[0].module = vert_module;
217 stages[0].pName =
"main";
218 stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
219 stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
220 stages[1].module = frag_module;
221 stages[1].pName =
"main";
223 VkPipelineVertexInputStateCreateInfo vertex_input{};
224 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
226 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
227 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
228 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
230 VkPipelineViewportStateCreateInfo viewport_state{};
231 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
232 viewport_state.viewportCount = 1;
233 viewport_state.scissorCount = 1;
235 const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
236 VkPipelineDynamicStateCreateInfo dynamic_state{};
237 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
238 dynamic_state.dynamicStateCount = 2;
239 dynamic_state.pDynamicStates = dynamic_states;
241 VkPipelineRasterizationStateCreateInfo rasterizer{};
242 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
243 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
244 rasterizer.cullMode = VK_CULL_MODE_NONE;
245 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
246 rasterizer.lineWidth = 1.0f;
248 VkPipelineMultisampleStateCreateInfo multisample{};
249 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
250 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
252 VkStencilOpState stencil_op{};
253 stencil_op.failOp = VK_STENCIL_OP_KEEP;
254 stencil_op.passOp = writes_stencil ? VK_STENCIL_OP_REPLACE : VK_STENCIL_OP_KEEP;
255 stencil_op.depthFailOp = VK_STENCIL_OP_KEEP;
256 stencil_op.compareOp = writes_stencil ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_EQUAL;
257 stencil_op.compareMask = 0xffU;
258 stencil_op.writeMask = writes_stencil ? 0xffU : 0U;
259 stencil_op.reference = 1U;
261 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
262 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
263 depth_stencil.depthTestEnable = VK_FALSE;
264 depth_stencil.depthWriteEnable = VK_FALSE;
265 depth_stencil.stencilTestEnable = VK_TRUE;
266 depth_stencil.front = stencil_op;
267 depth_stencil.back = stencil_op;
269 VkPipelineColorBlendAttachmentState blend_attachment{};
270 blend_attachment.colorWriteMask = writes_stencil ? 0U : (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
272 VkPipelineColorBlendStateCreateInfo color_blend{};
273 color_blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
274 color_blend.attachmentCount = 1;
275 color_blend.pAttachments = &blend_attachment;
277 VkPipelineRenderingCreateInfo rendering{};
278 rendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
279 rendering.colorAttachmentCount = 1;
280 rendering.pColorAttachmentFormats = &render_color_format;
281 rendering.depthAttachmentFormat = render_depth_format;
282 rendering.stencilAttachmentFormat = stencil_image_format;
284 VkGraphicsPipelineCreateInfo pipeline_info{};
285 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
286 pipeline_info.pNext = &rendering;
287 pipeline_info.stageCount =
static_cast<uint32_t
>(stages.size());
288 pipeline_info.pStages = stages.data();
289 pipeline_info.pVertexInputState = &vertex_input;
290 pipeline_info.pInputAssemblyState = &input_assembly;
291 pipeline_info.pViewportState = &viewport_state;
292 pipeline_info.pRasterizationState = &rasterizer;
293 pipeline_info.pMultisampleState = &multisample;
294 pipeline_info.pDepthStencilState = &depth_stencil;
295 pipeline_info.pColorBlendState = &color_blend;
296 pipeline_info.pDynamicState = &dynamic_state;
297 pipeline_info.layout = layout;
299 if (vkCreateGraphicsPipelines(vk_context.device, cache, 1, &pipeline_info,
nullptr, &pipeline) != VK_SUCCESS) {
300 throw mxvk::Exception(
"failed to create stencil graphics pipeline");
303 if (pipeline != VK_NULL_HANDLE) {
304 vkDestroyPipeline(vk_context.device, pipeline,
nullptr);
306 if (frag_module != VK_NULL_HANDLE) {
307 vkDestroyShaderModule(vk_context.device, frag_module,
nullptr);
309 vkDestroyShaderModule(vk_context.device, vert_module,
nullptr);
313 vkDestroyShaderModule(vk_context.device, frag_module,
nullptr);
314 vkDestroyShaderModule(vk_context.device, vert_module,
nullptr);
void initialize(const VulkanContext &context, VkExtent2D extent, VkFormat color_format, VkFormat depth_format, VkPipelineCache pipeline_cache, const std::string &mask_vertex_shader, const std::string &mask_fragment_shader, const std::string &content_vertex_shader, const std::string &content_fragment_shader)
Create the stencil attachment and fullscreen mask/content pipelines.
~VK_Stencil()
Destroy any live Vulkan resources owned by the helper.
void draw_mask(VkCommandBuffer cmd, const PushConstants &push_constants) const
Draw the fullscreen stencil-writing mask pass.
VkExtent2D extent() const noexcept
Return the current stencil attachment extent.
void draw_content(VkCommandBuffer cmd, const PushConstants &push_constants) const
Draw the fullscreen content pass clipped by stencil reference value 1.
void prepare_for_rendering(VkCommandBuffer cmd)
Transition the stencil image for use as a dynamic-rendering stencil attachment.
void destroy()
Release all Vulkan resources and reset the helper to an empty state.
void resize(VkExtent2D extent)
Recreate the stencil image for a new render extent.
void configure_attachment(VkRenderingAttachmentInfo &attachment) const
Fill a VkRenderingAttachmentInfo for VK_Window dynamic rendering.
bool valid() const noexcept
Check whether image resources are ready for rendering.
Reusable dynamic-rendering stencil image and fullscreen stencil pipelines.
Utilities for loading and saving PNG images.
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
void create_image(const VulkanContext &context, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
Create a 2D VkImage and allocate/bind its memory.
VkImageView create_image_view(VkDevice device, VkImage image, VkFormat format, VkImageAspectFlags aspect)
Create a 2D image view for an image.
std::vector< char > load_spv(const std::string &path)
Load a SPIR-V file from disk.
Push-constant payload shared by the mask and content shaders.
Minimal Vulkan handles required by MXVK resource helpers.
VkDevice device
Logical device used to create and destroy resources.