Skip to content

Fixups for Rendering #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 23, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 12 additions & 12 deletions guide/src/getting_started/class_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
// app.hpp
namespace lvk {
class App {
public:
void run();
public:
void run();
};
} // namespace lvk

// app.cpp
namespace lvk {
void App::run() {
// TODO
// TODO
}
} // namespace lvk
```
Expand All @@ -26,14 +26,14 @@ void App::run() {
```cpp
// main.cpp
auto main() -> int {
try {
lvk::App{}.run();
} catch (std::exception const& e) {
std::println(stderr, "PANIC: {}", e.what());
return EXIT_FAILURE;
} catch (...) {
std::println("PANIC!");
return EXIT_FAILURE;
}
try {
lvk::App{}.run();
} catch (std::exception const& e) {
std::println(stderr, "PANIC: {}", e.what());
return EXIT_FAILURE;
} catch (...) {
std::println("PANIC!");
return EXIT_FAILURE;
}
}
```
59 changes: 33 additions & 26 deletions guide/src/initialization/device.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,62 @@ A [Vulkan Device](https://registry.khronos.org/vulkan/specs/latest/man/html/VkDe
Setup a `vk::QueueCreateInfo` object:

```cpp
auto queue_ci = vk::DeviceQueueCreateInfo{};
// since we use only one queue, it has the entire priority range, ie, 1.0
static constexpr auto queue_priorities_v = std::array{1.0f};
queue_ci.setQueueFamilyIndex(m_gpu.queue_family)
.setQueueCount(1)
.setQueuePriorities(queue_priorities_v);
auto queue_ci = vk::DeviceQueueCreateInfo{};
// since we use only one queue, it has the entire priority range, ie, 1.0
static constexpr auto queue_priorities_v = std::array{1.0f};
queue_ci.setQueueFamilyIndex(m_gpu.queue_family)
.setQueueCount(1)
.setQueuePriorities(queue_priorities_v);
```

Setup the core device features:

```cpp
auto enabled_features = vk::PhysicalDeviceFeatures{};
enabled_features.fillModeNonSolid = m_gpu.features.fillModeNonSolid;
enabled_features.wideLines = m_gpu.features.wideLines;
enabled_features.samplerAnisotropy = m_gpu.features.samplerAnisotropy;
enabled_features.sampleRateShading = m_gpu.features.sampleRateShading;
// nice-to-have optional core features, enable if GPU supports them.
auto enabled_features = vk::PhysicalDeviceFeatures{};
enabled_features.fillModeNonSolid = m_gpu.features.fillModeNonSolid;
enabled_features.wideLines = m_gpu.features.wideLines;
enabled_features.samplerAnisotropy = m_gpu.features.samplerAnisotropy;
enabled_features.sampleRateShading = m_gpu.features.sampleRateShading;
```

Setup the additional features, using `setPNext()` to chain them:

```cpp
auto sync_feature = vk::PhysicalDeviceSynchronization2Features{vk::True};
auto dynamic_rendering_feature =
vk::PhysicalDeviceDynamicRenderingFeatures{vk::True};
sync_feature.setPNext(&dynamic_rendering_feature);
// extra features that need to be explicitly enabled.
auto sync_feature = vk::PhysicalDeviceSynchronization2Features{vk::True};
auto dynamic_rendering_feature =
vk::PhysicalDeviceDynamicRenderingFeatures{vk::True};
// sync_feature.pNext => dynamic_rendering_feature,
// and later device_ci.pNext => sync_feature.
// this is 'pNext chaining'.
sync_feature.setPNext(&dynamic_rendering_feature);
```

Setup a `vk::DeviceCreateInfo` object:

```cpp
auto device_ci = vk::DeviceCreateInfo{};
static constexpr auto extensions_v =
std::array{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
device_ci.setPEnabledExtensionNames(extensions_v)
.setQueueCreateInfos(queue_ci)
.setPEnabledFeatures(&enabled_features)
.setPNext(&sync_feature);
auto device_ci = vk::DeviceCreateInfo{};
// we only need one device extension: Swapchain.
static constexpr auto extensions_v =
std::array{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
device_ci.setPEnabledExtensionNames(extensions_v)
.setQueueCreateInfos(queue_ci)
.setPEnabledFeatures(&enabled_features)
.setPNext(&sync_feature);
```

Declare a `vk::UniqueDevice` member after `m_gpu`, create it, and initialize the dispatcher against it:

```cpp
m_device = m_gpu.device.createDeviceUnique(device_ci);
VULKAN_HPP_DEFAULT_DISPATCHER.init(*m_device);
m_device = m_gpu.device.createDeviceUnique(device_ci);
// initialize the dispatcher against the created Device.
VULKAN_HPP_DEFAULT_DISPATCHER.init(*m_device);
```

Declare a `vk::Queue` member (order doesn't matter since it's just a handle, the actual Queue is owned by the Device) and initialize it:

```cpp
static constexpr std::uint32_t queue_index_v{0};
m_queue = m_device->getQueue(m_gpu.queue_family, queue_index_v);
static constexpr std::uint32_t queue_index_v{0};
m_queue = m_device->getQueue(m_gpu.queue_family, queue_index_v);
```
60 changes: 30 additions & 30 deletions guide/src/initialization/glfw_window.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Although it is quite feasible to have multiple windows in a Vulkan-GLFW applicat
// window.hpp
namespace lvk::glfw {
struct Deleter {
void operator()(GLFWwindow* window) const noexcept;
void operator()(GLFWwindow* window) const noexcept;
};

using Window = std::unique_ptr<GLFWwindow, Deleter>;
Expand All @@ -19,32 +19,32 @@ using Window = std::unique_ptr<GLFWwindow, Deleter>;

// window.cpp
void Deleter::operator()(GLFWwindow* window) const noexcept {
glfwDestroyWindow(window);
glfwTerminate();
glfwDestroyWindow(window);
glfwTerminate();
}
```

GLFW can create fullscreen and borderless windows, but we will stick to a standard window with decorations. Since we cannot do anything useful if we are unable to create a window, all other branches throw a fatal exception (caught in main).

```cpp
auto glfw::create_window(glm::ivec2 const size, char const* title) -> Window {
static auto const on_error = [](int const code, char const* description) {
std::println(stderr, "[GLFW] Error {}: {}", code, description);
};
glfwSetErrorCallback(on_error);
if (glfwInit() != GLFW_TRUE) {
throw std::runtime_error{"Failed to initialize GLFW"};
}
// check for Vulkan support.
if (glfwVulkanSupported() != GLFW_TRUE) {
throw std::runtime_error{"Vulkan not supported"};
}
auto ret = Window{};
// tell GLFW that we don't want an OpenGL context.
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
ret.reset(glfwCreateWindow(size.x, size.y, title, nullptr, nullptr));
if (!ret) { throw std::runtime_error{"Failed to create GLFW Window"}; }
return ret;
static auto const on_error = [](int const code, char const* description) {
std::println(stderr, "[GLFW] Error {}: {}", code, description);
};
glfwSetErrorCallback(on_error);
if (glfwInit() != GLFW_TRUE) {
throw std::runtime_error{"Failed to initialize GLFW"};
}
// check for Vulkan support.
if (glfwVulkanSupported() != GLFW_TRUE) {
throw std::runtime_error{"Vulkan not supported"};
}
auto ret = Window{};
// tell GLFW that we don't want an OpenGL context.
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
ret.reset(glfwCreateWindow(size.x, size.y, title, nullptr, nullptr));
if (!ret) { throw std::runtime_error{"Failed to create GLFW Window"}; }
return ret;
}
```

Expand All @@ -53,35 +53,35 @@ auto glfw::create_window(glm::ivec2 const size, char const* title) -> Window {
Declare it as a private member:

```cpp
private:
glfw::Window m_window{};
private:
glfw::Window m_window{};
```

Add some private member functions to encapsulate each operation:

```cpp
void create_window();
void create_window();

void main_loop();
void main_loop();
```

Implement them and call them in `run()`:

```cpp
void App::run() {
create_window();
create_window();

main_loop();
main_loop();
}

void App::create_window() {
m_window = glfw::create_window({1280, 720}, "Learn Vulkan");
m_window = glfw::create_window({1280, 720}, "Learn Vulkan");
}

void App::main_loop() {
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
glfwPollEvents();
}
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
glfwPollEvents();
}
}
```

Expand Down
119 changes: 59 additions & 60 deletions guide/src/initialization/gpu.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,82 +14,81 @@ We wrap the actual Physical Device and a few other useful objects into `struct G
constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);

struct Gpu {
vk::PhysicalDevice device{};
vk::PhysicalDeviceProperties properties{};
vk::PhysicalDeviceFeatures features{};
std::uint32_t queue_family{};
vk::PhysicalDevice device{};
vk::PhysicalDeviceProperties properties{};
vk::PhysicalDeviceFeatures features{};
std::uint32_t queue_family{};
};

[[nodiscard]] auto get_suitable_gpu(vk::Instance instance,
vk::SurfaceKHR surface) -> Gpu;
vk::SurfaceKHR surface) -> Gpu;
```

The implementation:

```cpp
auto lvk::get_suitable_gpu(vk::Instance const instance,
vk::SurfaceKHR const surface) -> Gpu {
auto const supports_swapchain = [](Gpu const& gpu) {
static constexpr std::string_view name_v =
VK_KHR_SWAPCHAIN_EXTENSION_NAME;
static constexpr auto is_swapchain =
[](vk::ExtensionProperties const& properties) {
return properties.extensionName.data() == name_v;
};
auto const properties = gpu.device.enumerateDeviceExtensionProperties();
auto const it = std::ranges::find_if(properties, is_swapchain);
return it != properties.end();
};

auto const set_queue_family = [](Gpu& out_gpu) {
static constexpr auto queue_flags_v =
vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer;
for (auto const [index, family] :
std::views::enumerate(out_gpu.device.getQueueFamilyProperties())) {
if ((family.queueFlags & queue_flags_v) == queue_flags_v) {
out_gpu.queue_family = static_cast<std::uint32_t>(index);
return true;
}
}
return false;
};

auto const can_present = [surface](Gpu const& gpu) {
return gpu.device.getSurfaceSupportKHR(gpu.queue_family, surface) ==
vk::True;
};

auto fallback = Gpu{};
for (auto const& device : instance.enumeratePhysicalDevices()) {
auto gpu = Gpu{.device = device, .properties = device.getProperties()};
if (gpu.properties.apiVersion < vk_version_v) { continue; }
if (!supports_swapchain(gpu)) { continue; }
if (!set_queue_family(gpu)) { continue; }
if (!can_present(gpu)) { continue; }
gpu.features = gpu.device.getFeatures();
if (gpu.properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
return gpu;
}
// keep iterating in case we find a Discrete Gpu later.
fallback = gpu;
}
if (fallback.device) { return fallback; }

throw std::runtime_error{"No suitable Vulkan Physical Devices"};
vk::SurfaceKHR const surface) -> Gpu {
auto const supports_swapchain = [](Gpu const& gpu) {
static constexpr std::string_view name_v =
VK_KHR_SWAPCHAIN_EXTENSION_NAME;
static constexpr auto is_swapchain =
[](vk::ExtensionProperties const& properties) {
return properties.extensionName.data() == name_v;
};
auto const properties = gpu.device.enumerateDeviceExtensionProperties();
auto const it = std::ranges::find_if(properties, is_swapchain);
return it != properties.end();
};

auto const set_queue_family = [](Gpu& out_gpu) {
static constexpr auto queue_flags_v =
vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer;
for (auto const [index, family] :
std::views::enumerate(out_gpu.device.getQueueFamilyProperties())) {
if ((family.queueFlags & queue_flags_v) == queue_flags_v) {
out_gpu.queue_family = static_cast<std::uint32_t>(index);
return true;
}
}
return false;
};

auto const can_present = [surface](Gpu const& gpu) {
return gpu.device.getSurfaceSupportKHR(gpu.queue_family, surface) ==
vk::True;
};

auto fallback = Gpu{};
for (auto const& device : instance.enumeratePhysicalDevices()) {
auto gpu = Gpu{.device = device, .properties = device.getProperties()};
if (gpu.properties.apiVersion < vk_version_v) { continue; }
if (!supports_swapchain(gpu)) { continue; }
if (!set_queue_family(gpu)) { continue; }
if (!can_present(gpu)) { continue; }
gpu.features = gpu.device.getFeatures();
if (gpu.properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
return gpu;
}
// keep iterating in case we find a Discrete Gpu later.
fallback = gpu;
}
if (fallback.device) { return fallback; }

throw std::runtime_error{"No suitable Vulkan Physical Devices"};
}
```

Finally, add a `Gpu` member in `App` and initialize it after `create_surface()`:

```cpp
create_surface();
select_gpu();
// ...

create_surface();
select_gpu();

// ...
void App::select_gpu() {
m_gpu = get_suitable_gpu(*m_instance, *m_surface);
std::println("Using GPU: {}",
std::string_view{m_gpu.properties.deviceName});
m_gpu = get_suitable_gpu(*m_instance, *m_surface);
std::println("Using GPU: {}",
std::string_view{m_gpu.properties.deviceName});
}
```
Loading