Skip to content

Commit aec3b4c

Browse files
Mes0903karnkaulCDDing
authored
Translate chapter 2 into Traditional Chinese (zh-TW) (#30)
* CI: add `workflow_dispatch` * Port present semaphore sync fix to main (#26) * 🐛 Fix present semaphore sync (#25) * Refactor build scripts and presets (#24) - Move compile flags to presets - Update CI script * 🐛 Fix present semaphore sync Discovered on SDK 1.4.313 * Fixup guide * Fixup guide * ko-translation revised (#27) * Fix typo and remove redundant blank space (#29) * Fix typo and remove redundant blank space * Fix typo * Translate chapter 2 into Traditional Chinese --------- Co-authored-by: Karn Kaul <[email protected]> Co-authored-by: MyungKun Chang <[email protected]>
1 parent 33f5364 commit aec3b4c

File tree

14 files changed

+678
-5
lines changed

14 files changed

+678
-5
lines changed

guide/src/descriptor_sets/descriptor_buffer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Descriptor Buffer
22

3-
Uniform and Storage buffers need to be N-buffered unless they are "GPU const", ie contents do not change after creation. Encapsulate a `vma::Buffer` per virtual frame in a `DescriptorBuffer`:
3+
Uniform and Storage buffers need to be N-buffered unless they are "GPU const", ie contents do not change after creation. Encapsulate a `vma::Buffer` per virtual frame in a `DescriptorBuffer`:
44

55
```cpp
66
class DescriptorBuffer {

guide/src/initialization/device.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A [Vulkan Device](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-devices) is a logical instance of a Physical Device, and will the primary interface for everything Vulkan now onwards. [Vulkan Queues](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-queues) are owned by the Device, we will need one from the queue family stored in the `Gpu` to submit recorded command buffers. We also need to explicitly declare all features we want to use, eg [Dynamic Rendering](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_dynamic_rendering.html) and [Synchronization2](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html).
44

5-
Setup a `vk::QueueCreateInfo` object:
5+
Setup a `vk::DeviceQueueCreateInfo` object:
66

77
```cpp
88
auto queue_ci = vk::DeviceQueueCreateInfo{};

guide/src/initialization/instance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);
1616
} // namespace
1717
```
1818

19-
In `App`, create a new member function `create_instance()` and call it after `create_window()` in `run()`. After initializing the dispatcher, check that the loader meets the version requirement:
19+
In `App`, create a new member function `create_instance()` and call it after `create_window()` in `run()`. After initializing the dispatcher, check that the loader meets the version requirement:
2020

2121
```cpp
2222
void App::create_instance() {

guide/src/initialization/scoped_waiter.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A useful abstraction to have is an object that in its destructor waits/blocks until the Device is idle. It is incorrect usage to destroy Vulkan objects while they are in use by the GPU, such an object helps with making sure the device is idle before some dependent resource gets destroyed.
44

5-
Being able to do arbitary things on scope exit will be useful in other spots too, so we encapsulate that in a basic class template `Scoped`. It's somewhat like a `unique_ptr<Type, Deleter>` that stores the value (`Type`) instead of a pointer (`Type*`), with some constraints:
5+
Being able to do arbitrary things on scope exit will be useful in other spots too, so we encapsulate that in a basic class template `Scoped`. It's somewhat like a `unique_ptr<Type, Deleter>` that stores the value (`Type`) instead of a pointer (`Type*`), with some constraints:
66

77
1. `Type` must be default constructible
88
1. Assumes a default constructed `Type` is equivalent to null (does not call `Deleter`)

guide/translations/ko-KR/src/initialization/device.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[디바이스](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-devices)는 Physical Device의 논리적 인스턴스이며, 이후의 모든 Vulkan 작업에서 주요 인터페이스 역할을 하게 됩니다. [](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-queues)는 디바이스가 소유하는 것으로, `Gpu` 구조체에 저장된 큐 패밀리에서 하나를 가져와 기록된 커맨드 버퍼를 제출하는 데 사용할 것입니다. 또한 사용하기를 원하는 [Dynamic Rendering](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_dynamic_rendering.html)[Synchronization2](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html)같은 기능들을 명시적으로 선언해야 합니다.
44

5-
`vk::QueueCreateInfo`객체를 설정합시다.
5+
`vk::DeviceQueueCreateInfo`객체를 설정합시다.
66

77
```cpp
88
auto queue_ci = vk::DeviceQueueCreateInfo{};

guide/translations/zh-TW/src/SUMMARY.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@
88
- [Project Layout](getting_started/project_layout.md)
99
- [Validation Layers](getting_started/validation_layers.md)
1010
- [class App](getting_started/class_app.md)
11+
- [Initialization](initialization/README.md)
12+
- [GLFW Window](initialization/glfw_window.md)
13+
- [Vulkan Instance](initialization/instance.md)
14+
- [Vulkan Surface](initialization/surface.md)
15+
- [Vulkan Physical Device](initialization/gpu.md)
16+
- [Vulkan Device](initialization/device.md)
17+
- [Scoped Waiter](initialization/scoped_waiter.md)
18+
- [Swapchain](initialization/swapchain.md)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Initialization
2+
3+
本節將處理所有必要系統的初始化工作,包括:
4+
5+
- 初始化 GLFW 並建立視窗
6+
- 建立 Vulkan Instance
7+
- 建立 Vulkan Surface
8+
- 選擇 Vulkan Physical Device
9+
- 建立 Vulkan logical Device
10+
- 建立 Vulkan Swapchain
11+
12+
如果其中任何一步失敗,都會是致命錯誤,之後的操作都會變得沒有意義
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Vulkan Device
2+
3+
[Vulkan Device](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-devices) 是實體裝置(Physical Device)的邏輯實例,它是我們之後操作 Vulkan 的主要介面。 [Vulkan Queues](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-queues) 由 Device 所擁有,我們需要從儲存在 `Gpu` 中的佇列家族(queue family)中取得一個 Device,來提交已記錄的指令緩衝區(command buffer)。 我們還需要顯式地宣告所有想要使用的功能,例如 [Dynamic Rendering](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_dynamic_rendering.html)[Synchronization2](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html)
4+
5+
接下來讓我們來設定各項功能。 首先要設定 `vk::DeviceQueueCreateInfo` 物件:
6+
7+
```cpp
8+
auto queue_ci = vk::DeviceQueueCreateInfo{};
9+
// since we use only one queue, it has the entire priority range, ie, 1.0
10+
static constexpr auto queue_priorities_v = std::array{1.0f};
11+
queue_ci.setQueueFamilyIndex(m_gpu.queue_family)
12+
.setQueueCount(1)
13+
.setQueuePriorities(queue_priorities_v);
14+
```
15+
16+
接著設定核心功能:
17+
18+
```cpp
19+
// nice-to-have optional core features, enable if GPU supports them.
20+
auto enabled_features = vk::PhysicalDeviceFeatures{};
21+
enabled_features.fillModeNonSolid = m_gpu.features.fillModeNonSolid;
22+
enabled_features.wideLines = m_gpu.features.wideLines;
23+
enabled_features.samplerAnisotropy = m_gpu.features.samplerAnisotropy;
24+
enabled_features.sampleRateShading = m_gpu.features.sampleRateShading;
25+
```
26+
27+
設定額外功能,並用 `setPNext()` 把它們串接起來:
28+
29+
```cpp
30+
// extra features that need to be explicitly enabled.
31+
auto sync_feature = vk::PhysicalDeviceSynchronization2Features{vk::True};
32+
auto dynamic_rendering_feature =
33+
vk::PhysicalDeviceDynamicRenderingFeatures{vk::True};
34+
// sync_feature.pNext => dynamic_rendering_feature,
35+
// and later device_ci.pNext => sync_feature.
36+
// this is 'pNext chaining'.
37+
sync_feature.setPNext(&dynamic_rendering_feature);
38+
```
39+
40+
建立並設定一個 `vk::DeviceCreateInfo` 物件:
41+
42+
```cpp
43+
auto device_ci = vk::DeviceCreateInfo{};
44+
// we only need one device extension: Swapchain.
45+
static constexpr auto extensions_v =
46+
std::array{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
47+
device_ci.setPEnabledExtensionNames(extensions_v)
48+
.setQueueCreateInfos(queue_ci)
49+
.setPEnabledFeatures(&enabled_features)
50+
.setPNext(&sync_feature);
51+
```
52+
53+
`m_gpu` 之後宣告一個 `vk::UniqueDevice` 成員,建立它,並針對它初始化 dispatcher:
54+
55+
```cpp
56+
m_device = m_gpu.device.createDeviceUnique(device_ci);
57+
// initialize the dispatcher against the created Device.
58+
VULKAN_HPP_DEFAULT_DISPATCHER.init(*m_device);
59+
```
60+
61+
宣告一個 `vk::Queue` 成員(順序無所謂,因為它只是個 handle,實際的 Queue 由 Device 擁有),並完成初始化:
62+
63+
```cpp
64+
static constexpr std::uint32_t queue_index_v{0};
65+
m_queue = m_device->getQueue(m_gpu.queue_family, queue_index_v);
66+
```
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# GLFW Window
2+
3+
本教學使用 GLFW(3.4)來處理視窗與相關事件。 這個函式庫和其他外部相依套件一樣,會在 `ext/CMakeLists.txt` 中設定並加入建置樹(build tree)。 對使用者而言,我們會定義 `GLFW_INCLUDE_VULKAN`,以啟用 GLFW 的 Vulkan 相關功能,這些功能被稱為「視窗系統整合(Window System Integration, WSI)」
4+
5+
GLFW 3.4 在 Linux 上支援 Wayland,並且預設會同時建置 X11 與 Wayland 兩種後端。 因此,你必須同時安裝[兩個平台的開發套件](https://www.glfw.org/docs/latest/compile_guide.html#compile_deps_wayland)(以及一些其他 Wayland/CMake 相依套件),才能順利完成設定與建置。 如果需要,你也可以在執行階段透過 `GLFW_PLATFORM` 來指定要使用的特定後端
6+
7+
雖然 Vulkan-GLFW 的應用程式可以同時擁有多個視窗,但這不在本書探討範圍內。 對我們而言,GLFW(函式庫)與單一視窗被視為一個整體單元,它們會一起初始化並一起銷毀。 由於 GLFW 會回傳一個不透明指標(opaque pointer) `GLFWwindow*`,因此你可以將它封裝在帶有自訂刪除器的 `std::unique_ptr` 中來管理
8+
9+
```cpp
10+
// window.hpp
11+
namespace lvk::glfw {
12+
struct Deleter {
13+
void operator()(GLFWwindow* window) const noexcept;
14+
};
15+
16+
using Window = std::unique_ptr<GLFWwindow, Deleter>;
17+
18+
// Returns a valid Window if successful, else throws.
19+
[[nodiscard]] auto create_window(glm::ivec2 size, char const* title) -> Window;
20+
} // namespace lvk::glfw
21+
22+
// window.cpp
23+
void Deleter::operator()(GLFWwindow* window) const noexcept {
24+
glfwDestroyWindow(window);
25+
glfwTerminate();
26+
}
27+
```
28+
29+
GLFW 可以建立全螢幕或無邊框的視窗,本教學會使用帶有邊框與標題列的標準視窗。 由於如果無法建立視窗,就無法再進行任何有意義的操作,因此其他所有分支都會丟出致命例外(會在 main 中被捕捉)
30+
31+
```cpp
32+
auto glfw::create_window(glm::ivec2 const size, char const* title) -> Window {
33+
static auto const on_error = [](int const code, char const* description) {
34+
std::println(stderr, "[GLFW] Error {}: {}", code, description);
35+
};
36+
glfwSetErrorCallback(on_error);
37+
if (glfwInit() != GLFW_TRUE) {
38+
throw std::runtime_error{"Failed to initialize GLFW"};
39+
}
40+
// check for Vulkan support.
41+
if (glfwVulkanSupported() != GLFW_TRUE) {
42+
throw std::runtime_error{"Vulkan not supported"};
43+
}
44+
auto ret = Window{};
45+
// tell GLFW that we don't want an OpenGL context.
46+
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
47+
ret.reset(glfwCreateWindow(size.x, size.y, title, nullptr, nullptr));
48+
if (!ret) { throw std::runtime_error{"Failed to create GLFW Window"}; }
49+
return ret;
50+
}
51+
```
52+
53+
接著 `App` 會儲存一個 `glfw::Window`,並在 `run()` 中持續輪詢它,直到使用者關閉視窗為止。 雖然暫時還無法在視窗上繪製任何內容,但這是邁向該目標的第一步
54+
55+
首先將它宣告為私有成員:
56+
57+
```cpp
58+
private:
59+
glfw::Window m_window{};
60+
```
61+
62+
接著加上一些私有的成員函式來將相關的操作封裝起來:
63+
64+
```cpp
65+
void create_window();
66+
67+
void main_loop();
68+
```
69+
70+
最後補上函式定義,並在 `run()` 中呼叫它們:
71+
72+
```cpp
73+
void App::run() {
74+
create_window();
75+
76+
main_loop();
77+
}
78+
79+
void App::create_window() {
80+
m_window = glfw::create_window({1280, 720}, "Learn Vulkan");
81+
}
82+
83+
void App::main_loop() {
84+
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
85+
glfwPollEvents();
86+
}
87+
}
88+
```
89+
90+
> 如果是在 Wayland 上,此時還看不到視窗。 Wayland 需要應用程式將 framebuffer 給它後,視窗才會顯示出來
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Vulkan Physical Device
2+
3+
[Physical Device](https://docs.vulkan.org/spec/latest/chapters/devsandqueues.html#devsandqueues-physical-device-enumeration) 代表一個完整的 Vulkan 實作; 就我們的用途而言,可以把它視為一張 GPU(它也可能是像 Mesa/lavapipe 這樣的軟體算繪器)。 有些機器上可能同時存在多個 Physical Device,例如配備雙顯卡的筆電。 我們需要在下列限制條件下,挑選要使用的是哪一個:
4+
5+
1. 必須支援 Vulkan 1.3
6+
2. 必須支援 Vulkan Swapchain
7+
3. 必須有支援 Graphics 與 Transfer 作業的 Vulkan Queue
8+
4. 必須能呈現(present)先前建立的 Vulkan Surface
9+
5. (Optional)優先選擇獨立顯示卡(discrete GPU)
10+
11+
我們會把實際的 Physical Device 以及其他幾個實用物件包裝成 `struct Gpu`。 由於它會搭配一個相當龐大的工具函式,我們會把它放到獨立的 hpp/cpp 檔案中,並把 `vk_version_v` 這個常數移到新的標頭檔裡:
12+
13+
```cpp
14+
constexpr auto vk_version_v = VK_MAKE_VERSION(1, 3, 0);
15+
16+
struct Gpu {
17+
vk::PhysicalDevice device{};
18+
vk::PhysicalDeviceProperties properties{};
19+
vk::PhysicalDeviceFeatures features{};
20+
std::uint32_t queue_family{};
21+
};
22+
23+
[[nodiscard]] auto get_suitable_gpu(vk::Instance instance,
24+
vk::SurfaceKHR surface) -> Gpu;
25+
```
26+
27+
函式定義:
28+
29+
```cpp
30+
auto lvk::get_suitable_gpu(vk::Instance const instance,
31+
vk::SurfaceKHR const surface) -> Gpu {
32+
auto const supports_swapchain = [](Gpu const& gpu) {
33+
static constexpr std::string_view name_v =
34+
VK_KHR_SWAPCHAIN_EXTENSION_NAME;
35+
static constexpr auto is_swapchain =
36+
[](vk::ExtensionProperties const& properties) {
37+
return properties.extensionName.data() == name_v;
38+
};
39+
auto const properties = gpu.device.enumerateDeviceExtensionProperties();
40+
auto const it = std::ranges::find_if(properties, is_swapchain);
41+
return it != properties.end();
42+
};
43+
44+
auto const set_queue_family = [](Gpu& out_gpu) {
45+
static constexpr auto queue_flags_v =
46+
vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer;
47+
for (auto const [index, family] :
48+
std::views::enumerate(out_gpu.device.getQueueFamilyProperties())) {
49+
if ((family.queueFlags & queue_flags_v) == queue_flags_v) {
50+
out_gpu.queue_family = static_cast<std::uint32_t>(index);
51+
return true;
52+
}
53+
}
54+
return false;
55+
};
56+
57+
auto const can_present = [surface](Gpu const& gpu) {
58+
return gpu.device.getSurfaceSupportKHR(gpu.queue_family, surface) ==
59+
vk::True;
60+
};
61+
62+
auto fallback = Gpu{};
63+
for (auto const& device : instance.enumeratePhysicalDevices()) {
64+
auto gpu = Gpu{.device = device, .properties = device.getProperties()};
65+
if (gpu.properties.apiVersion < vk_version_v) { continue; }
66+
if (!supports_swapchain(gpu)) { continue; }
67+
if (!set_queue_family(gpu)) { continue; }
68+
if (!can_present(gpu)) { continue; }
69+
gpu.features = gpu.device.getFeatures();
70+
if (gpu.properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
71+
return gpu;
72+
}
73+
// keep iterating in case we find a Discrete Gpu later.
74+
fallback = gpu;
75+
}
76+
if (fallback.device) { return fallback; }
77+
78+
throw std::runtime_error{"No suitable Vulkan Physical Devices"};
79+
}
80+
```
81+
82+
最後,在 `App` 中加入 `Gpu` 成員,並在 `create_surface()` 之後初始化它:
83+
84+
```cpp
85+
create_surface();
86+
select_gpu();
87+
88+
// ...
89+
void App::select_gpu() {
90+
m_gpu = get_suitable_gpu(*m_instance, *m_surface);
91+
std::println("Using GPU: {}",
92+
std::string_view{m_gpu.properties.deviceName});
93+
}
94+
```

0 commit comments

Comments
 (0)