33*
44* This sample shows how to read data from a buffer device address (aka "reference") instead of using uniforms
55* The application passes buffer device addresses to the shader via push constants, and the shader then simply reads the data behind that address
6+ * This makes stuff like descriptor handling a lot easier, as we now only pass around references akin to pointers and no longer need descriptors for buffers
67* See cube.vert for the shader side of things
78*
8- * Copyright (C) 2024 by Sascha Willems - www.saschawillems.de
9+ * Copyright (C) 2024-2025 by Sascha Willems - www.saschawillems.de
910*
1011*/
1112
@@ -19,9 +20,9 @@ class VulkanExample : public VulkanExampleBase
1920
2021 struct Cube {
2122 glm::mat4 modelMatrix;
22- vks::Buffer buffer;
2323 glm::vec3 rotation;
24- VkDeviceAddress bufferDeviceAddress{};
24+ std::array<vks::Buffer, maxConcurrentFrames> buffers{};
25+ std::array<VkDeviceAddress, maxConcurrentFrames> bufferDeviceAddresses{};
2526 };
2627 std::array<Cube, 2 > cubes{};
2728
@@ -31,8 +32,8 @@ class VulkanExample : public VulkanExampleBase
3132 // Global matrices
3233 struct Scene {
3334 glm::mat4 mvp;
34- vks::Buffer buffer ;
35- VkDeviceAddress bufferDeviceAddress {};
35+ std::array< vks::Buffer, maxConcurrentFrames> buffers ;
36+ std::array< VkDeviceAddress, maxConcurrentFrames> bufferDeviceAddresses {};
3637 } scene;
3738
3839 VkPipeline pipeline{ VK_NULL_HANDLE };
@@ -53,6 +54,8 @@ class VulkanExample : public VulkanExampleBase
5354
5455 VulkanExample () : VulkanExampleBase()
5556 {
57+ useNewSync = true ;
58+
5659 title = " Buffer device address" ;
5760 camera.type = Camera::CameraType::lookat;
5861 camera.setPerspective (60 .0f , (float )width / (float )height, 0 .1f , 512 .0f );
@@ -77,10 +80,14 @@ class VulkanExample : public VulkanExampleBase
7780 vkDestroyPipelineLayout (device, pipelineLayout, nullptr );
7881 vkDestroyDescriptorSetLayout (device, descriptorSetLayout, nullptr );
7982 texture.destroy ();
80- for (auto cube : cubes) {
81- cube.buffer .destroy ();
83+ for (auto & cube : cubes) {
84+ for (auto & buffer : cube.buffers ) {
85+ buffer.destroy ();
86+ }
87+ }
88+ for (auto & buffer : scene.buffers ) {
89+ buffer.destroy ();
8290 }
83- scene.buffer .destroy ();
8491 }
8592 }
8693
@@ -170,34 +177,35 @@ class VulkanExample : public VulkanExampleBase
170177 VK_CHECK_RESULT (vkCreateGraphicsPipelines (device, pipelineCache, 1 , &pipelineCI, nullptr , &pipeline));
171178 }
172179
173- void prepareBuffers ()
180+ void prepareUniformBuffers ()
174181 {
175- // Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
176- VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &scene.buffer , sizeof (glm::mat4)));
177- VK_CHECK_RESULT (scene.buffer .map ());
178-
179- // Get the device of this buffer that is later on passed to the shader (aka "reference")
180- VkBufferDeviceAddressInfo bufferDeviceAdressInfo{};
181- bufferDeviceAdressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
182- bufferDeviceAdressInfo.buffer = scene.buffer .buffer ;
183- scene.bufferDeviceAddress = vkGetBufferDeviceAddressKHR (device, &bufferDeviceAdressInfo);
184-
185- for (auto & cube : cubes) {
182+ for (uint32_t i = 0 ; i < maxConcurrentFrames; i++) {
186183 // Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
187- VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &cube. buffer , sizeof (glm::mat4)));
188- VK_CHECK_RESULT (cube. buffer .map ());
184+ VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &scene. buffers [i] , sizeof (glm::mat4)));
185+ VK_CHECK_RESULT (scene. buffers [i] .map ());
189186
190187 // Get the device of this buffer that is later on passed to the shader (aka "reference")
191- bufferDeviceAdressInfo.buffer = cube.buffer .buffer ;
192- cube.bufferDeviceAddress = vkGetBufferDeviceAddressKHR (device, &bufferDeviceAdressInfo);
188+ VkBufferDeviceAddressInfo bufferDeviceAdressInfo{};
189+ bufferDeviceAdressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
190+ bufferDeviceAdressInfo.buffer = scene.buffers [i].buffer ;
191+ scene.bufferDeviceAddresses [i] = vkGetBufferDeviceAddressKHR (device, &bufferDeviceAdressInfo);
192+
193+ for (auto & cube : cubes) {
194+ // Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
195+ VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &cube.buffers [i], sizeof (glm::mat4)));
196+ VK_CHECK_RESULT (cube.buffers [i].map ());
197+
198+ // Get the device of this buffer that is later on passed to the shader (aka "reference")
199+ bufferDeviceAdressInfo.buffer = cube.buffers [i].buffer ;
200+ cube.bufferDeviceAddresses [i] = vkGetBufferDeviceAddressKHR (device, &bufferDeviceAdressInfo);
201+ }
193202 }
194- updateBuffers ();
195203 }
196204
197- void updateBuffers ()
205+ void updateUniformBuffers ()
198206 {
199207 scene.mvp = camera.matrices .perspective * camera.matrices .view ;
200- memcpy (scene.buffer .mapped , &scene, sizeof (glm::mat4));
208+ memcpy (scene.buffers [currentBuffer] .mapped , &scene, sizeof (glm::mat4));
201209
202210 cubes[0 ].modelMatrix = glm::translate (glm::mat4 (1 .0f ), glm::vec3 (-2 .0f , 0 .0f , 0 .0f ));
203211 cubes[1 ].modelMatrix = glm::translate (glm::mat4 (1 .0f ), glm::vec3 (1 .5f , 0 .5f , 0 .0f ));
@@ -207,7 +215,7 @@ class VulkanExample : public VulkanExampleBase
207215 cube.modelMatrix = glm::rotate (cube.modelMatrix , glm::radians (cube.rotation .y ), glm::vec3 (0 .0f , 1 .0f , 0 .0f ));
208216 cube.modelMatrix = glm::rotate (cube.modelMatrix , glm::radians (cube.rotation .z ), glm::vec3 (0 .0f , 0 .0f , 1 .0f ));
209217 cube.modelMatrix = glm::scale (cube.modelMatrix , glm::vec3 (0 .25f ));
210- memcpy (cube.buffer .mapped , &cube.modelMatrix , sizeof (glm::mat4));
218+ memcpy (cube.buffers [currentBuffer] .mapped , &cube.modelMatrix , sizeof (glm::mat4));
211219 }
212220 }
213221
@@ -219,18 +227,20 @@ class VulkanExample : public VulkanExampleBase
219227 vkGetBufferDeviceAddressKHR = reinterpret_cast <PFN_vkGetBufferDeviceAddressKHR>(vkGetDeviceProcAddr (device, " vkGetBufferDeviceAddressKHR" ));
220228
221229 loadAssets ();
222- prepareBuffers ();
230+ prepareUniformBuffers ();
223231 setupDescriptors ();
224232 preparePipelines ();
225- buildCommandBuffers ();
226233 prepared = true ;
227234 }
228235
229- void buildCommandBuffers ()
236+ void buildCommandBuffer ()
230237 {
238+ VkCommandBuffer cmdBuffer = drawCmdBuffers[currentBuffer];
239+ vkResetCommandBuffer (cmdBuffer, 0 );
240+
231241 VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo ();
232242
233- VkClearValue clearValues[2 ];
243+ VkClearValue clearValues[2 ]{} ;
234244 clearValues[0 ].color = defaultClearColor;
235245 clearValues[1 ].depthStencil = { 1 .0f , 0 };
236246
@@ -242,64 +252,52 @@ class VulkanExample : public VulkanExampleBase
242252 renderPassBeginInfo.renderArea .extent .height = height;
243253 renderPassBeginInfo.clearValueCount = 2 ;
244254 renderPassBeginInfo.pClearValues = clearValues;
255+ renderPassBeginInfo.framebuffer = frameBuffers[currentImageIndex];
245256
246- for (int32_t i = 0 ; i < drawCmdBuffers.size (); ++i) {
247- renderPassBeginInfo.framebuffer = frameBuffers[i];
248-
249- VK_CHECK_RESULT (vkBeginCommandBuffer (drawCmdBuffers[i], &cmdBufInfo));
257+ VK_CHECK_RESULT (vkBeginCommandBuffer (cmdBuffer, &cmdBufInfo));
250258
251- vkCmdBeginRenderPass (drawCmdBuffers[i] , &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
259+ vkCmdBeginRenderPass (cmdBuffer , &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
252260
253- vkCmdBindPipeline (drawCmdBuffers[i] , VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
261+ vkCmdBindPipeline (cmdBuffer , VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
254262
255- VkViewport viewport = vks::initializers::viewport ((float )width, (float )height, 0 .0f , 1 .0f );
256- vkCmdSetViewport (drawCmdBuffers[i] , 0 , 1 , &viewport);
263+ VkViewport viewport = vks::initializers::viewport ((float )width, (float )height, 0 .0f , 1 .0f );
264+ vkCmdSetViewport (cmdBuffer , 0 , 1 , &viewport);
257265
258- VkRect2D scissor = vks::initializers::rect2D (width, height, 0 , 0 );
259- vkCmdSetScissor (drawCmdBuffers[i] , 0 , 1 , &scissor);
266+ VkRect2D scissor = vks::initializers::rect2D (width, height, 0 , 0 );
267+ vkCmdSetScissor (cmdBuffer , 0 , 1 , &scissor);
260268
261- vkCmdBindDescriptorSets (drawCmdBuffers[i] , VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0 , 1 , &descriptorSet, 0 , nullptr );
269+ vkCmdBindDescriptorSets (cmdBuffer , VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0 , 1 , &descriptorSet, 0 , nullptr );
262270
263- model.bindBuffers (drawCmdBuffers[i] );
271+ model.bindBuffers (cmdBuffer );
264272
265- // Instead of using descriptors to pass global and per-model matrices to the shader, we can now simply pass buffer references via push constants
266- // The shader then simply reads data from the address of that reference
267- PushConstantBlock references{};
268- // Pass pointer to the global matrix via a buffer device address
269- references.sceneReference = scene.bufferDeviceAddress ;
273+ // Instead of using descriptors to pass global and per-model matrices to the shader, we can now simply pass buffer references via push constants
274+ // The shader then simply reads data from the address of that reference
275+ PushConstantBlock references{};
276+ // Pass pointer to the global matrix via a buffer device address
277+ references.sceneReference = scene.bufferDeviceAddresses [currentBuffer] ;
270278
271- for (auto & cube : cubes) {
272- // Pass pointer to this cube's data buffer via a buffer device address
273- // So instead of having to bind different descriptors, we only pass a different device address
274- // This doesn't have to be an address from a different buffer, but could very well be just another address in the same buffer
275- references.modelReference = cube.bufferDeviceAddress ;
276- vkCmdPushConstants (drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0 , sizeof (PushConstantBlock), &references);
277-
278- model.draw (drawCmdBuffers[i]);
279- }
279+ for (auto & cube : cubes) {
280+ // Pass pointer to this cube's data buffer via a buffer device address
281+ // So instead of having to bind different descriptors, we only pass a different device address
282+ // This doesn't have to be an address from a different buffer, but could very well be just another address in the same buffer
283+ references.modelReference = cube.bufferDeviceAddresses [currentBuffer];
284+ vkCmdPushConstants (cmdBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0 , sizeof (PushConstantBlock), &references);
280285
281- drawUI (drawCmdBuffers[i]);
286+ model.draw (cmdBuffer);
287+ }
282288
283- vkCmdEndRenderPass (drawCmdBuffers[i] );
289+ drawUI (cmdBuffer );
284290
285- VK_CHECK_RESULT (vkEndCommandBuffer (drawCmdBuffers[i]));
286- }
287- }
291+ vkCmdEndRenderPass (cmdBuffer);
288292
289- void draw ()
290- {
291- VulkanExampleBase::prepareFrame ();
292- submitInfo.commandBufferCount = 1 ;
293- submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
294- VK_CHECK_RESULT (vkQueueSubmit (queue, 1 , &submitInfo, VK_NULL_HANDLE));
295- VulkanExampleBase::submitFrame ();
293+ VK_CHECK_RESULT (vkEndCommandBuffer (cmdBuffer));
296294 }
297295
298296 virtual void render ()
299297 {
300298 if (!prepared)
301299 return ;
302- draw ();
300+ VulkanExampleBase::prepareFrame ();
303301 if (animate && !paused) {
304302 cubes[0 ].rotation .x += 2 .5f * frameTimer;
305303 if (cubes[0 ].rotation .x > 360 .0f )
@@ -308,9 +306,9 @@ class VulkanExample : public VulkanExampleBase
308306 if (cubes[1 ].rotation .x > 360 .0f )
309307 cubes[1 ].rotation .x -= 360 .0f ;
310308 }
311- if ((camera. updated ) || (animate && !paused)) {
312- updateBuffers ();
313- }
309+ updateUniformBuffers ();
310+ buildCommandBuffer ();
311+ VulkanExampleBase::submitFrame ();
314312 }
315313
316314 virtual void OnUpdateUIOverlay (vks::UIOverlay* overlay)
0 commit comments