MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_stencil.cpp
Go to the documentation of this file.
2
5
6#include <array>
7#include <vector>
8
9namespace mxvk {
13
15 VkExtent2D extent,
16 VkFormat color_format,
17 VkFormat depth_format,
18 VkPipelineCache pipeline_cache,
19 const std::string &mask_vertex_shader,
20 const std::string &mask_fragment_shader,
21 const std::string &content_vertex_shader,
22 const std::string &content_fragment_shader) {
23 destroy();
24 vk_context = context;
25 stencil_extent = extent;
26 render_color_format = color_format;
27 render_depth_format = depth_format;
28 cache = pipeline_cache;
29 mask_vertex_path = mask_vertex_shader;
30 mask_fragment_path = mask_fragment_shader;
31 content_vertex_path = content_vertex_shader;
32 content_fragment_path = content_fragment_shader;
33 stencil_image_format = choose_stencil_format();
34 create_resources();
35 create_pipelines();
36 }
37
39 destroy_pipelines();
40 destroy_image();
41 vk_context = {};
42 stencil_extent = {};
43 render_color_format = VK_FORMAT_UNDEFINED;
44 render_depth_format = VK_FORMAT_UNDEFINED;
45 stencil_image_format = VK_FORMAT_UNDEFINED;
46 cache = VK_NULL_HANDLE;
47 mask_vertex_path.clear();
48 mask_fragment_path.clear();
49 content_vertex_path.clear();
50 content_fragment_path.clear();
51 }
52
53 void VK_Stencil::resize(VkExtent2D extent) {
54 if (extent.width == stencil_extent.width && extent.height == stencil_extent.height) {
55 return;
56 }
57 stencil_extent = extent;
58 destroy_image();
59 create_resources();
60 }
61
62 void VK_Stencil::prepare_for_rendering(VkCommandBuffer cmd) {
63 if (!valid()) {
64 return;
65 }
66
67 VkImageMemoryBarrier2 barrier{};
68 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
69 barrier.srcStageMask = image_initialized ? VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT : VK_PIPELINE_STAGE_2_NONE;
70 barrier.srcAccessMask = image_initialized ? VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT : VK_ACCESS_2_NONE;
71 barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
72 barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
73 barrier.oldLayout = image_initialized ? VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
74 barrier.newLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
75 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
76 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
77 barrier.image = image;
78 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
79 barrier.subresourceRange.levelCount = 1;
80 barrier.subresourceRange.layerCount = 1;
81
82 VkDependencyInfo dependency{};
83 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
84 dependency.imageMemoryBarrierCount = 1;
85 dependency.pImageMemoryBarriers = &barrier;
86 vkCmdPipelineBarrier2(cmd, &dependency);
87 image_initialized = true;
88 }
89
90 void VK_Stencil::configure_attachment(VkRenderingAttachmentInfo &attachment) const {
91 if (!valid()) {
92 return;
93 }
94 attachment.imageView = view;
95 attachment.imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
96 attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
97 attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
98 attachment.clearValue.depthStencil = {1.0f, 0U};
99 }
100
101 void VK_Stencil::draw_mask(VkCommandBuffer cmd, const PushConstants &push_constants) const {
102 if (mask_pipeline == VK_NULL_HANDLE || mask_layout == VK_NULL_HANDLE) {
103 return;
104 }
105 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, mask_pipeline);
106 vkCmdPushConstants(cmd, mask_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &push_constants);
107 vkCmdDraw(cmd, 3, 1, 0, 0);
108 }
109
110 void VK_Stencil::draw_content(VkCommandBuffer cmd, const PushConstants &push_constants) const {
111 if (content_pipeline == VK_NULL_HANDLE || content_layout == VK_NULL_HANDLE) {
112 return;
113 }
114 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, content_pipeline);
115 vkCmdPushConstants(cmd, content_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &push_constants);
116 vkCmdDraw(cmd, 3, 1, 0, 0);
117 }
118
119 bool VK_Stencil::valid() const noexcept {
120 return vk_context.device != VK_NULL_HANDLE && image != VK_NULL_HANDLE && view != VK_NULL_HANDLE &&
121 stencil_extent.width > 0U && stencil_extent.height > 0U;
122 }
123
124 void VK_Stencil::create_resources() {
125 if (vk_context.device == VK_NULL_HANDLE || stencil_extent.width == 0U || stencil_extent.height == 0U) {
126 return;
127 }
128 create_image(vk_context,
129 stencil_extent.width,
130 stencil_extent.height,
131 stencil_image_format,
132 VK_IMAGE_TILING_OPTIMAL,
133 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
134 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
135 image,
136 memory);
137 view = create_image_view(vk_context.device, image, stencil_image_format, VK_IMAGE_ASPECT_STENCIL_BIT);
138 image_initialized = false;
139 }
140
141 void VK_Stencil::create_pipelines() {
142 VkPushConstantRange push_constant{};
143 push_constant.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
144 push_constant.offset = 0;
145 push_constant.size = sizeof(PushConstants);
146
147 VkPipelineLayoutCreateInfo layout_info{};
148 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
149 layout_info.pushConstantRangeCount = 1;
150 layout_info.pPushConstantRanges = &push_constant;
151 if (vkCreatePipelineLayout(vk_context.device, &layout_info, nullptr, &mask_layout) != VK_SUCCESS ||
152 vkCreatePipelineLayout(vk_context.device, &layout_info, nullptr, &content_layout) != VK_SUCCESS) {
153 throw mxvk::Exception("failed to create stencil pipeline layout");
154 }
155
156 mask_pipeline = create_pipeline(mask_vertex_path, mask_fragment_path, mask_layout, true);
157 content_pipeline = create_pipeline(content_vertex_path, content_fragment_path, content_layout, false);
158 }
159
160 void VK_Stencil::destroy_pipelines() {
161 if (vk_context.device == VK_NULL_HANDLE) {
162 mask_layout = VK_NULL_HANDLE;
163 content_layout = VK_NULL_HANDLE;
164 mask_pipeline = VK_NULL_HANDLE;
165 content_pipeline = VK_NULL_HANDLE;
166 return;
167 }
168 if (mask_pipeline != VK_NULL_HANDLE) {
169 vkDestroyPipeline(vk_context.device, mask_pipeline, nullptr);
170 }
171 if (content_pipeline != VK_NULL_HANDLE) {
172 vkDestroyPipeline(vk_context.device, content_pipeline, nullptr);
173 }
174 if (mask_layout != VK_NULL_HANDLE) {
175 vkDestroyPipelineLayout(vk_context.device, mask_layout, nullptr);
176 }
177 if (content_layout != VK_NULL_HANDLE) {
178 vkDestroyPipelineLayout(vk_context.device, content_layout, nullptr);
179 }
180 mask_layout = VK_NULL_HANDLE;
181 content_layout = VK_NULL_HANDLE;
182 mask_pipeline = VK_NULL_HANDLE;
183 content_pipeline = VK_NULL_HANDLE;
184 }
185
186 void VK_Stencil::destroy_image() {
187 if (vk_context.device == VK_NULL_HANDLE) {
188 image = VK_NULL_HANDLE;
189 memory = VK_NULL_HANDLE;
190 view = VK_NULL_HANDLE;
191 image_initialized = false;
192 return;
193 }
194 if (view != VK_NULL_HANDLE) {
195 vkDestroyImageView(vk_context.device, view, nullptr);
196 }
197 if (image != VK_NULL_HANDLE) {
198 vkDestroyImage(vk_context.device, image, nullptr);
199 }
200 if (memory != VK_NULL_HANDLE) {
201 vkFreeMemory(vk_context.device, memory, nullptr);
202 }
203 image = VK_NULL_HANDLE;
204 memory = VK_NULL_HANDLE;
205 view = VK_NULL_HANDLE;
206 image_initialized = false;
207 }
208
209 VkFormat VK_Stencil::choose_stencil_format() const {
210 const std::array<VkFormat, 3> candidates{
211 VK_FORMAT_S8_UINT,
212 VK_FORMAT_D24_UNORM_S8_UINT,
213 VK_FORMAT_D32_SFLOAT_S8_UINT,
214 };
215
216 for (const VkFormat format : candidates) {
217 VkFormatProperties properties{};
218 vkGetPhysicalDeviceFormatProperties(vk_context.physical_device, format, &properties);
219 if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0U) {
220 return format;
221 }
222 }
223 throw mxvk::Exception("failed to find a supported Vulkan stencil format");
224 }
225
226 VkPipeline VK_Stencil::create_pipeline(const std::string &vertex_shader,
227 const std::string &fragment_shader,
228 VkPipelineLayout layout,
229 bool writes_stencil) const {
230 const std::vector<char> vert_bytes = load_spv(vertex_shader);
231 const std::vector<char> frag_bytes = load_spv(fragment_shader);
232 const VkShaderModule vert_module = create_shader_module(vk_context.device, vert_bytes);
233 VkShaderModule frag_module = VK_NULL_HANDLE;
234 VkPipeline pipeline = VK_NULL_HANDLE;
235 try {
236 frag_module = create_shader_module(vk_context.device, frag_bytes);
237
238 std::array<VkPipelineShaderStageCreateInfo, 2> stages{};
239 stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
240 stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
241 stages[0].module = vert_module;
242 stages[0].pName = "main";
243 stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
244 stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
245 stages[1].module = frag_module;
246 stages[1].pName = "main";
247
248 VkPipelineVertexInputStateCreateInfo vertex_input{};
249 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
250
251 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
252 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
253 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
254
255 VkPipelineViewportStateCreateInfo viewport_state{};
256 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
257 viewport_state.viewportCount = 1;
258 viewport_state.scissorCount = 1;
259
260 const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
261 VkPipelineDynamicStateCreateInfo dynamic_state{};
262 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
263 dynamic_state.dynamicStateCount = 2;
264 dynamic_state.pDynamicStates = dynamic_states;
265
266 VkPipelineRasterizationStateCreateInfo rasterizer{};
267 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
268 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
269 rasterizer.cullMode = VK_CULL_MODE_NONE;
270 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
271 rasterizer.lineWidth = 1.0f;
272
273 VkPipelineMultisampleStateCreateInfo multisample{};
274 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
275 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
276
277 VkStencilOpState stencil_op{};
278 stencil_op.failOp = VK_STENCIL_OP_KEEP;
279 stencil_op.passOp = writes_stencil ? VK_STENCIL_OP_REPLACE : VK_STENCIL_OP_KEEP;
280 stencil_op.depthFailOp = VK_STENCIL_OP_KEEP;
281 stencil_op.compareOp = writes_stencil ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_EQUAL;
282 stencil_op.compareMask = 0xffU;
283 stencil_op.writeMask = writes_stencil ? 0xffU : 0U;
284 stencil_op.reference = 1U;
285
286 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
287 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
288 depth_stencil.depthTestEnable = VK_FALSE;
289 depth_stencil.depthWriteEnable = VK_FALSE;
290 depth_stencil.stencilTestEnable = VK_TRUE;
291 depth_stencil.front = stencil_op;
292 depth_stencil.back = stencil_op;
293
294 VkPipelineColorBlendAttachmentState blend_attachment{};
295 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);
296
297 VkPipelineColorBlendStateCreateInfo color_blend{};
298 color_blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
299 color_blend.attachmentCount = 1;
300 color_blend.pAttachments = &blend_attachment;
301
302 VkPipelineRenderingCreateInfo rendering{};
303 rendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
304 rendering.colorAttachmentCount = 1;
305 rendering.pColorAttachmentFormats = &render_color_format;
306 rendering.depthAttachmentFormat = render_depth_format;
307 rendering.stencilAttachmentFormat = stencil_image_format;
308
309 VkGraphicsPipelineCreateInfo pipeline_info{};
310 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
311 pipeline_info.pNext = &rendering;
312 pipeline_info.stageCount = static_cast<uint32_t>(stages.size());
313 pipeline_info.pStages = stages.data();
314 pipeline_info.pVertexInputState = &vertex_input;
315 pipeline_info.pInputAssemblyState = &input_assembly;
316 pipeline_info.pViewportState = &viewport_state;
317 pipeline_info.pRasterizationState = &rasterizer;
318 pipeline_info.pMultisampleState = &multisample;
319 pipeline_info.pDepthStencilState = &depth_stencil;
320 pipeline_info.pColorBlendState = &color_blend;
321 pipeline_info.pDynamicState = &dynamic_state;
322 pipeline_info.layout = layout;
323
324 if (vkCreateGraphicsPipelines(vk_context.device, cache, 1, &pipeline_info, nullptr, &pipeline) != VK_SUCCESS) {
325 throw mxvk::Exception("failed to create stencil graphics pipeline");
326 }
327 } catch (...) {
328 if (pipeline != VK_NULL_HANDLE) {
329 vkDestroyPipeline(vk_context.device, pipeline, nullptr);
330 }
331 if (frag_module != VK_NULL_HANDLE) {
332 vkDestroyShaderModule(vk_context.device, frag_module, nullptr);
333 }
334 vkDestroyShaderModule(vk_context.device, vert_module, nullptr);
335 throw;
336 }
337
338 vkDestroyShaderModule(vk_context.device, frag_module, nullptr);
339 vkDestroyShaderModule(vk_context.device, vert_module, nullptr);
340 return pipeline;
341 }
342
343} // namespace mxvk
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.
Definition mxvk.hpp:30
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.