Skip to content

Commit 026c77a

Browse files
committed
RenderTarget, Swapchain updates
1 parent 712d8ab commit 026c77a

File tree

5 files changed

+176
-5
lines changed

5 files changed

+176
-5
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
- [Rendering](rendering/README.md)
2020
- [Swapchain Loop](rendering/swapchain_loop.md)
2121
- [Render Sync](rendering/render_sync.md)
22+
- [Swapchain Update](rendering/swapchain_update.md)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Swapchain Acquire/Present
2+
3+
Swapchain acquire/present operations can have various results. We constrain ourselves to the following:
4+
5+
- `eSuccess`: all good
6+
- `eSuboptimalKHR`: also all good (this is also unlikely to occur on a desktop)
7+
- `eErrorOutOfDateKHR`: Swapchain needs to be recreated, consider the operation to have not succeeded
8+
- Any other `vk::Result`: fatal/unexpected error
9+
10+
Expressing as a helper function in `swapchain.cpp`:
11+
12+
```cpp
13+
auto needs_recreation(vk::Result const result) -> bool {
14+
switch (result) {
15+
case vk::Result::eSuccess:
16+
case vk::Result::eSuboptimalKHR: return false;
17+
case vk::Result::eErrorOutOfDateKHR: return true;
18+
default: break;
19+
}
20+
throw std::runtime_error{"Swapchain Error"};
21+
}
22+
```
23+
24+
We also want to return the Image, Image View, and size upon successful acquisition of the underlying Swapchain Image. Wrapping those in a `struct`:
25+
26+
```cpp
27+
struct RenderTarget {
28+
vk::Image image{};
29+
vk::ImageView image_view{};
30+
vk::Extent2D extent{};
31+
};
32+
```
33+
34+
VulkanHPP's primary API throws if the `vk::Result` corresponds to an error (based on the spec). `eErrorOutOfDateKHR` is technically an error, but it's quite possible to get it when the framebuffer size doesn't match the Swapchain size. To avoid having to deal with exceptions here, we use the alternate API for the acquire and present calls (overloads distinguished by pointer arguments and/or out parameters, and returning a `vk::Result`).
35+
36+
Implementing the acquire operation:
37+
38+
```cpp
39+
auto Swapchain::acquire_next_image(vk::Semaphore const to_signal)
40+
-> std::optional<RenderTarget> {
41+
assert(!m_image_index);
42+
static constexpr auto timeout_v = std::numeric_limits<std::uint64_t>::max();
43+
auto image_index = std::uint32_t{};
44+
auto const result = m_device.acquireNextImageKHR(
45+
*m_swapchain, timeout_v, to_signal, {}, &image_index);
46+
if (needs_recreation(result)) { return {}; }
47+
48+
m_image_index = static_cast<std::size_t>(image_index);
49+
return RenderTarget{
50+
.image = m_images.at(*m_image_index),
51+
.image_view = *m_image_views.at(*m_image_index),
52+
.extent = m_ci.imageExtent,
53+
};
54+
}
55+
```
56+
57+
Similarly, present:
58+
59+
```cpp
60+
auto Swapchain::present(vk::Queue const queue, vk::Semaphore const to_wait)
61+
-> bool {
62+
assert(m_image_index);
63+
auto const image_index = static_cast<std::uint32_t>(*m_image_index);
64+
auto present_info = vk::PresentInfoKHR{};
65+
present_info.setSwapchains(*m_swapchain)
66+
.setImageIndices(image_index)
67+
.setWaitSemaphores(to_wait);
68+
auto const result = queue.presentKHR(&present_info);
69+
m_image_index.reset();
70+
return !needs_recreation(result);
71+
}
72+
```
73+
74+
It is the responsibility of the user (`class App`) to recreate the Swapchain on receiving `std::nullopt` / `false` return values for either operation. Users will also need to transition the layouts of the returned images between acquire and present operations. Add a helper to assist in that process, and extract the Image Subresource Range out as a common constant:
75+
76+
```cpp
77+
constexpr auto subresource_range_v = [] {
78+
auto ret = vk::ImageSubresourceRange{};
79+
ret.setAspectMask(vk::ImageAspectFlagBits::eColor)
80+
.setLayerCount(1)
81+
.setLevelCount(1);
82+
return ret;
83+
}();
84+
85+
// ...
86+
auto Swapchain::base_barrier() const -> vk::ImageMemoryBarrier2 {
87+
assert(m_image_index);
88+
// fill up the parts common to all barriers.
89+
auto ret = vk::ImageMemoryBarrier2{};
90+
ret.setImage(m_images.at(*m_image_index))
91+
.setSubresourceRange(subresource_range_v)
92+
.setSrcQueueFamilyIndex(m_gpu.queue_family)
93+
.setDstQueueFamilyIndex(m_gpu.queue_family);
94+
return ret;
95+
}
96+
```

src/render_target.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
#include <vulkan/vulkan.hpp>
3+
4+
namespace lvk {
5+
struct RenderTarget {
6+
vk::Image image{};
7+
vk::ImageView image_view{};
8+
vk::Extent2D extent{};
9+
};
10+
} // namespace lvk

src/swapchain.cpp

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <algorithm>
33
#include <array>
44
#include <cassert>
5+
#include <limits>
56
#include <print>
67
#include <stdexcept>
78

@@ -14,6 +15,14 @@ constexpr auto srgb_formats_v = std::array{
1415
vk::Format::eB8G8R8A8Srgb,
1516
};
1617

18+
constexpr auto subresource_range_v = [] {
19+
auto ret = vk::ImageSubresourceRange{};
20+
ret.setAspectMask(vk::ImageAspectFlagBits::eColor)
21+
.setLayerCount(1)
22+
.setLevelCount(1);
23+
return ret;
24+
}();
25+
1726
[[nodiscard]] constexpr auto
1827
get_surface_format(std::span<vk::SurfaceFormatKHR const> supported)
1928
-> vk::SurfaceFormatKHR {
@@ -58,6 +67,16 @@ get_image_count(vk::SurfaceCapabilitiesKHR const& capabilities)
5867
void require_success(vk::Result const result, char const* error_msg) {
5968
if (result != vk::Result::eSuccess) { throw std::runtime_error{error_msg}; }
6069
}
70+
71+
auto needs_recreation(vk::Result const result) -> bool {
72+
switch (result) {
73+
case vk::Result::eSuccess:
74+
case vk::Result::eSuboptimalKHR: return false;
75+
case vk::Result::eErrorOutOfDateKHR: return true;
76+
default: break;
77+
}
78+
throw std::runtime_error{"Swapchain Error"};
79+
}
6180
} // namespace
6281

6382
Swapchain::Swapchain(vk::Device const device, Gpu const& gpu,
@@ -100,6 +119,47 @@ auto Swapchain::recreate(glm::ivec2 size) -> bool {
100119
return true;
101120
}
102121

122+
auto Swapchain::acquire_next_image(vk::Semaphore const to_signal)
123+
-> std::optional<RenderTarget> {
124+
assert(!m_image_index);
125+
static constexpr auto timeout_v = std::numeric_limits<std::uint64_t>::max();
126+
auto image_index = std::uint32_t{};
127+
auto const result = m_device.acquireNextImageKHR(
128+
*m_swapchain, timeout_v, to_signal, {}, &image_index);
129+
if (needs_recreation(result)) { return {}; }
130+
131+
m_image_index = static_cast<std::size_t>(image_index);
132+
return RenderTarget{
133+
.image = m_images.at(*m_image_index),
134+
.image_view = *m_image_views.at(*m_image_index),
135+
.extent = m_ci.imageExtent,
136+
};
137+
}
138+
139+
auto Swapchain::base_barrier() const -> vk::ImageMemoryBarrier2 {
140+
assert(m_image_index);
141+
// fill up the parts common to all barriers.
142+
auto ret = vk::ImageMemoryBarrier2{};
143+
ret.setImage(m_images.at(*m_image_index))
144+
.setSubresourceRange(subresource_range_v)
145+
.setSrcQueueFamilyIndex(m_gpu.queue_family)
146+
.setDstQueueFamilyIndex(m_gpu.queue_family);
147+
return ret;
148+
}
149+
150+
auto Swapchain::present(vk::Queue const queue, vk::Semaphore const to_wait)
151+
-> bool {
152+
assert(m_image_index);
153+
auto const image_index = static_cast<std::uint32_t>(*m_image_index);
154+
auto present_info = vk::PresentInfoKHR{};
155+
present_info.setSwapchains(*m_swapchain)
156+
.setImageIndices(image_index)
157+
.setWaitSemaphores(to_wait);
158+
auto const result = queue.presentKHR(&present_info);
159+
m_image_index.reset();
160+
return !needs_recreation(result);
161+
}
162+
103163
void Swapchain::populate_images() {
104164
auto image_count = std::uint32_t{};
105165
auto result =
@@ -113,14 +173,10 @@ void Swapchain::populate_images() {
113173
}
114174

115175
void Swapchain::create_image_views() {
116-
auto subresource_range = vk::ImageSubresourceRange{};
117-
subresource_range.setAspectMask(vk::ImageAspectFlagBits::eColor)
118-
.setLayerCount(1)
119-
.setLevelCount(1);
120176
auto image_view_ci = vk::ImageViewCreateInfo{};
121177
image_view_ci.setViewType(vk::ImageViewType::e2D)
122178
.setFormat(m_ci.imageFormat)
123-
.setSubresourceRange(subresource_range);
179+
.setSubresourceRange(subresource_range_v);
124180
m_image_views.clear();
125181
m_image_views.reserve(m_images.size());
126182
for (auto const image : m_images) {

src/swapchain.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include <glm/vec2.hpp>
33
#include <gpu.hpp>
4+
#include <render_target.hpp>
45
#include <optional>
56
#include <vector>
67

@@ -16,6 +17,13 @@ class Swapchain {
1617
return {m_ci.imageExtent.width, m_ci.imageExtent.height};
1718
}
1819

20+
[[nodiscard]] auto acquire_next_image(vk::Semaphore to_signal)
21+
-> std::optional<RenderTarget>;
22+
23+
[[nodiscard]] auto base_barrier() const -> vk::ImageMemoryBarrier2;
24+
25+
[[nodiscard]] auto present(vk::Queue queue, vk::Semaphore to_wait) -> bool;
26+
1927
private:
2028
void populate_images();
2129
void create_image_views();

0 commit comments

Comments
 (0)