From 5a4e2c2e3243368e9d9e8ed409700b781671cfbe Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 18 Sep 2023 00:11:17 +0900
Subject: [PATCH 01/47] kr translate, 00 and 01

---
 kr/00_Introduction.md                         |  60 ++
 kr/01_Overview.md                             | 121 +++
 kr/02_Development_environment.md              | 544 +++++++++++++
 .../00_Setup/00_Base_code.md                  | 217 +++++
 .../00_Setup/01_Instance.md                   | 221 +++++
 .../00_Setup/02_Validation_layers.md          | 458 +++++++++++
 .../03_Physical_devices_and_queue_families.md | 364 +++++++++
 .../00_Setup/04_Logical_device_and_queues.md  | 171 ++++
 .../01_Presentation/00_Window_surface.md      | 233 ++++++
 .../01_Presentation/01_Swap_chain.md          | 603 ++++++++++++++
 .../01_Presentation/02_Image_views.md         | 127 +++
 .../00_Introduction.md                        |  99 +++
 .../01_Shader_modules.md                      | 467 +++++++++++
 .../02_Fixed_functions.md                     | 439 ++++++++++
 .../03_Render_passes.md                       | 215 +++++
 .../04_Conclusion.md                          | 122 +++
 .../03_Drawing/00_Framebuffers.md             | 107 +++
 .../03_Drawing/01_Command_buffers.md          | 344 ++++++++
 .../02_Rendering_and_presentation.md          | 577 +++++++++++++
 .../03_Drawing/03_Frames_in_flight.md         | 176 ++++
 .../04_Swap_chain_recreation.md               | 280 +++++++
 .../00_Vertex_input_description.md            | 225 +++++
 .../01_Vertex_buffer_creation.md              | 342 ++++++++
 kr/04_Vertex_buffers/02_Staging_buffer.md     | 267 ++++++
 kr/04_Vertex_buffers/03_Index_buffer.md       | 179 ++++
 .../00_Descriptor_layout_and_buffer.md        | 416 ++++++++++
 .../01_Descriptor_pool_and_sets.md            | 391 +++++++++
 kr/06_Texture_mapping/00_Images.md            | 769 ++++++++++++++++++
 .../01_Image_view_and_sampler.md              | 369 +++++++++
 .../02_Combined_image_sampler.md              | 296 +++++++
 kr/07_Depth_buffering.md                      | 600 ++++++++++++++
 kr/08_Loading_models.md                       | 332 ++++++++
 kr/09_Generating_Mipmaps.md                   | 354 ++++++++
 kr/10_Multisampling.md                        | 298 +++++++
 kr/11_Compute_Shader.md                       | 650 +++++++++++++++
 kr/90_FAQ.md                                  |  58 ++
 kr/95_Privacy_policy.md                       |  21 +
 37 files changed, 11512 insertions(+)
 create mode 100644 kr/00_Introduction.md
 create mode 100644 kr/01_Overview.md
 create mode 100644 kr/02_Development_environment.md
 create mode 100644 kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md
 create mode 100644 kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
 create mode 100644 kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
 create mode 100644 kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
 create mode 100644 kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
 create mode 100644 kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
 create mode 100644 kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
 create mode 100644 kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
 create mode 100644 kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
 create mode 100644 kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
 create mode 100644 kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
 create mode 100644 kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
 create mode 100644 kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
 create mode 100644 kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
 create mode 100644 kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
 create mode 100644 kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
 create mode 100644 kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
 create mode 100644 kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
 create mode 100644 kr/04_Vertex_buffers/00_Vertex_input_description.md
 create mode 100644 kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
 create mode 100644 kr/04_Vertex_buffers/02_Staging_buffer.md
 create mode 100644 kr/04_Vertex_buffers/03_Index_buffer.md
 create mode 100644 kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
 create mode 100644 kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
 create mode 100644 kr/06_Texture_mapping/00_Images.md
 create mode 100644 kr/06_Texture_mapping/01_Image_view_and_sampler.md
 create mode 100644 kr/06_Texture_mapping/02_Combined_image_sampler.md
 create mode 100644 kr/07_Depth_buffering.md
 create mode 100644 kr/08_Loading_models.md
 create mode 100644 kr/09_Generating_Mipmaps.md
 create mode 100644 kr/10_Multisampling.md
 create mode 100644 kr/11_Compute_Shader.md
 create mode 100644 kr/90_FAQ.md
 create mode 100644 kr/95_Privacy_policy.md

diff --git a/kr/00_Introduction.md b/kr/00_Introduction.md
new file mode 100644
index 00000000..626c6ac5
--- /dev/null
+++ b/kr/00_Introduction.md
@@ -0,0 +1,60 @@
+## 소개
+
+이 튜토리얼은 [Vulkan](https://www.khronos.org/vulkan/) 그래픽스와 계산 API를 사용하는 기본적인 내용을 알려 드립니다. Vulkan은 (OpenGL로 잘 알려진) [Khronos group](https://www.khronos.org/)에서 만든 새로운 API로 최신 그래픽카드에 대한 훨씬 잘 추상화된 API를 제공합니다. 이 새로운 인터페이스는 여러분의 응용 프로그램이 무엇을 하는 것인지를 더 잘 설명하게 해 주고, 이를 통해 기존 [OpenGL](https://en.wikipedia.org/wiki/OpenGL)
+및 [Direct3D](https://en.wikipedia.org/wiki/Direct3D)보다 높은 성능과 더 투명한 드라이버의 동작을 보장합니다. Vulkan의 배경이 되는 아이디어는 [Direct3D 12](https://en.wikipedia.org/wiki/Direct3D#Direct3D_12)
+나 [Metal](<https://en.wikipedia.org/wiki/Metal_(API)>)과 비슷하지만, Vulkan은 완전한 크로스 플랫폼을 보장하여 윈도우즈, 리눅스, 안드로이드에서 모두 동작하는 응용 프로그램을 개발할 수 있게 합니다.
+
+하지만, 이러한 이점을 활용하기 위해 여러분이 지불해야 할 비용은 훨씬 장황한 API를 다루어야 한다는 것입니다. 응용 프로그램에서 모든 그래픽스 API와 관련된 상세 사항들을 처음부터 설정해야 하는데, 초기 프레임 버퍼 생성이나 버퍼나 텍스처 이미지 객체들을 위한 메모리 관리 시스템을 만드는 것 등입니다. 그래픽 드라이버가 해 주는 일이 적어서 여러분의 응용 프로그램이 제대로 동작하기 위해서는 직접 더 많은 작업을 해 주어야 합니다.
+
+여기서 말하고자 하는 것은 Vulkan이 모든 사람들을 위해 만들어진 것은 아니라는 점입니다. Vulkan은 고성능 컴퓨터 그래픽스에 관심이 있고, 여기에 시간을 투자할 의지가 있는 프로그래머를 그 대상으로 하고 있습니다. 여러분이 컴퓨터 그래픽스보다는 게임 개발에 더 관심이 있다면, 그냥 OpenGL이나 Direct3D를 계속 사용하는 것이 더 나을 것입니다. Vulkan이 짧은 시간 내에 그 자리를 대체하지는 않을 겁니다. 다은 대안으로는 [Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4)
+이나 [Unity](<https://en.wikipedia.org/wiki/Unity_(game_engine)>) 같은 게임 엔진을 사용하는 것입니다. 게임 엔진을 사용하면 훨씬 고수준의 API를 통해 Vulkan을 사용 가능합니다.
+
+그럼 각설하고, 이 튜토리얼을 위해 준비해야 할 사항들은 다음과 같습니다:
+
+- Vulkan에 호환되는 그래픽 카드와 드라이버 ([NVIDIA](https://developer.nvidia.com/vulkan-driver), [AMD](http://www.amd.com/en-us/innovations/software-technologies/technologies-gaming/vulkan), [Intel](https://software.intel.com/en-us/blogs/2016/03/14/new-intel-vulkan-beta-1540204404-graphics-driver-for-windows-78110-1540), [Apple Silicon (Or the Apple M1)](https://www.phoronix.com/scan.php?page=news_item&px=Apple-Silicon-Vulkan-MoltenVK))
+- C++ 경험(RAII, initializer lists에 익숙해야 합니다.)
+- C++17 기능을 지원하는 컴파일러 (Visual Studio 2017+, GCC 7+, 또는 Clang 5+)
+- 3D 컴퓨터 그래픽스 경험
+
+이 튜토리얼은 OpenGL이나 Direct3D의 개념에 대한 사전지식을 가정하고 있지는 않지만 3D 컴퓨터 그래픽스에 대한 기본 지식은 필요합니다. 예를 들자면 원근 투영(Perspective projection)에 관한 수학적인 배경 등은 설명하지 않습니다. 컴퓨터 그래픽스 개념에 대한 개념서로 [이 책](https://paroj.github.io/gltut/)을 참고 하십시오. 다른 컴퓨터 그래픽스 관련 자료들은 다음과 같습니다:
+
+- [Ray tracing in one weekend](https://github.com/RayTracing/raytracing.github.io)
+- [Physically Based Rendering book](http://www.pbr-book.org/)
+- Vulkan은 오픈 소스 [Quake](https://github.com/Novum/vkQuake)와 [DOOM 3](https://github.com/DustinHLand/vkDOOM3)의 엔진에서도 사용되었습니다.
+
+원한다면 C++ 대신 C를 사용할 수도 있지만, 그러려면 다른 선형대수 라이브러리를 사용해야 하고 코드 구조를 스스로 설계하셔야 합니다. 우리는 클래스나 RAII 같은 C++ 기능을 로직(logic)과 리소스의 생애주기 관리를 위해 사용할 것입니다. Rust 개발자를 위한 대안으로 이 튜토리얼의 다음과 같은 버전들이 있습니다: [Vulkano based](https://github.com/bwasty/vulkan-tutorial-rs), [Vulkanalia based](https://kylemayes.github.io/vulkanalia).
+
+다른 프로그래밍 언어를 사용하는 개발자들이 따라오시기 쉽고, 기본 API에 대한 이해를 돕기 위해 Vulkan이 동작하도록 하는 데는 원본 C API를 사용할 것입니다. 하지만 C++ 개발자라면, 새로운 [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) 바인딩을 사용하시면 특정 종류의 오류를 방지할 수 있고, 몇 가지 지저분한 작업들을 하지 않아도 됩니다.
+
+## E-book
+
+이 튜토리얼을 e-book으로 보고 싶으시면 EPUB나 PDF 버전을 받으시면 됩니다:
+
+- [EPUB](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.epub)
+- [PDF](https://vulkan-tutorial.com/resources/vulkan_tutorial_en.pdf)
+
+## 튜토리얼 구조
+
+우리는 Vulkan이 어떻게 동작하는지에 대한 개요와 삼각형을 화면에 그리기 위해 해야 하는 일들을 설명하는 것으로 튜토리얼을 시작할 것입니다. 모든 각각의 상세한 단계들의 목적은 전체적인 그림을 이해하고 나면 좀 더 쉽게 이해가 될 것입니다. 다음으로 [Vulkan SDK](https://lunarg.com/vulkan-sdk/), 선형대수 연산을 위한 [GLM library](http://glm.g-truc.net/), 윈도우 생성을 위한 [GLFW](http://www.glfw.org/)를 포함한 개발 환경을 설정할 것입니다. 이 튜토리얼에서는 Visual Studio에 기반한 윈도우즈에서의 개발 환경, GCC를 활용한 우분투 리눅스에서의 개발 환경 설정을 설명할 것입니다.
+
+그 이후에는 삼각형을 화면에 그리기 위해 해야 하는 Vulkan 프로그램 기본 구성요소들을 구현해 볼 것입니다. 각 챕터들은 대략적으로 아래와 같은 구조를 따릅니다:
+
+- 새로운 개념과 그 목적에 대한 소개
+- 그러한 내용을 프로그램으로 작성하기 위해 필요한 API 호출 방법들
+- 해당 기능들을 헬퍼 함수로 만드는 추상화 작업
+
+각각의 챕터는 이전 챕터에 이어지는 것으로 쓰여졌지만, 각 챕터는 Vulkan의 특정 기능을 소개하는 개별적인 소개글이라 생각하고 읽으셔도 됩니다. 따라서 이 사이트를 유용한 참조 문서로 생각하셔도 됩니다. Vulkan 함수와 타입에 대한 모든 것들이 명세(specification)와 링크되어 있으니 더 알고 싶으시면 클릭하시면 됩니다. Vulkan은 새로운 API라서 명세 자체에 한계점이 있을 수 있습니다. [이 Khronos repository](https://github.com/KhronosGroup/Vulkan-Docs)에 적극적으로 피드백을 남겨 주세요.
+
+앞서 이야기 한 것처럼 Vulkan API는 여러분들에게 그래픽스 하드웨어에 대한 최대한의 제어권을 제공하는 장황한 API입니다. 이로 인해 텍스처를 생성하는 기본적인 연산도 여러 단계를 거쳐야만 하고 이러한 작업들을 여러 번 반복해야만 합니다. 따라서 우리는 자체적으로 헬퍼 함수들을 만들어 볼 것입니다.
+
+각 챕터마다 해당 단계에 해당하는 전체 코드에 대한 링크를 제공할 것입니다. 코드 구조가 잘 이해되지 않거나, 버그가 있거나, 비교를 해 보고 싶다면 참고 하십시오. 모든 코드는 다양한 제조사의 그래픽 카드에서 올바로 동작하는 것을 테스트 한 상태입니다. 각 챕터에는 또한 코멘트 섹션이 있어서 해당 주제에 대한 질문을 남기실 수 있습니다. 여러분의 플랫폼, 드라이버 버전, 소스 코드, 기대하는 동작과 실제 동작을 남겨서 우리가 여러분을 도울 수 있도록 도와 주세요.
+
+이 튜토리얼은 커뮤니티 활성화를 위한 목적도 있습니다. Vulkan은 아직 신생 API이고 모범 사례들이 아직 확립되지 않았습니다. 튜토리얼이나 사이트 자체에 대한 피드백이 있다면 [GitHub repository](https://github.com/Overv/VulkanTutorial)에 이슈나 풀 리퀘스트(pull request)를 남겨 주세요. 레포지토리를 *watch*하시면 튜토리얼이 업데이트 되면 알림을 받을 수 있습니다.
+
+여러분의 첫 삼각형을 Vulkan을 사용해서 화면에 그리는 의식을 치르고 나면, 선형 변환, 텍스처, 3D 모델 등을 포함할 수 있도록 프로그램을 확장할 것입니다.
+
+전에 그래픽스 API를 사용해 본 적이 있다면, 삼각형 하나를 그리기 위해서 여러 단계의 작업이 필요하다는 것을 아실 겁니다. Vulkan에서도 마찬가지인데, 이러한 개별적인 단계들이 이해하기 어렵지 않고 꼭 필요한 작업임을 알게 되실겁니다. 또 명심하여야 할 것은 단순한 삼각형을 한 번 그리기만 하면, 텍스처링된 3D 모델을 그리는 것은 그리 많은 추가 작업이 필요하지는 않다는 것입니다.
+
+이후 튜토리얼을 따라가다 문제가 있다면, 먼저 FAQ에 동일한 문제가 이미 해결된 적이 있는지부터 확인해 보세요. 그러고 나서도 문제를 해결하지 못했다면, 관련된 챕터의 코멘트 섹션에 편하게 질문을 남겨 주세요.
+
+미래의 고성능 그래픽스 API에 뛰어들 준비가 되셨나요? [출발해 봅시다!](!kr/Overview)
diff --git a/kr/01_Overview.md b/kr/01_Overview.md
new file mode 100644
index 00000000..325e5124
--- /dev/null
+++ b/kr/01_Overview.md
@@ -0,0 +1,121 @@
+이 챕터에서는 Vulkan에 대한 개요와 Vulkan이 해결하고자 하는 문제에 대한 설명부터 시작해 보겠습니다. 그 이후엔 첫 삼각형을 그리기 위해 필요한 재료들을 살펴볼 것입니다. 이를 통해 이후 챕터에서 다루는 내용들에 대한 큰 그림을 이해할 수 있을 것입니다. 마지막에는 Vulkan API의 구조와 일반적인 사용 패턴을 다루는 것으로 마무리 할 것입니다.
+
+## Vulkan의 탄생
+
+기존의 그래픽스 API와 마찬가지로, Vulkan은 [GPUs](https://en.wikipedia.org/wiki/Graphics_processing_unit)에 대한 크로스 플랫폼 추상화를 위해 설계되었습니다. 이러한 API들의 문제점은 그들이 설계된 시기에는 그래픽 하드웨어들이 대부분 고정된 기능 구성으로 제한되어 있었다는 것입니다. 프로그래머는 표준 포맷으로 정점(Vertex) 데이터를 넘겨주어야 했고 조명과 셰이딩 옵션은 API 제조사에서 제공하는 기능을 사용해야 했습니다.
+
+그래픽 카드의 아키텍처가 발전하면서 점점 더 많은 프로그램가능한(programmable) 기능을 제공하기 시작했습니다. 이러한 새로운 기능은 어떻게든 기존 API와 통합되어야 했습니다. 이에 따라 이상적인 추상화와는 멀어지기 시작했고, 프로그래머의 의도와 현대적인 그래픽스 아키텍처간의 맵핑을 위해 그래픽 드라이버 내부에 대한 어림짐작들이 포함되기 시작했습니다. 이것이 게임의 성능 향상을 위해 많은 드라이버 업데이트가 필요한 이유고, 때때로 인로 인해 많은 성능 향상이 일어나기도 합니다. 드라이버의 복잡성 때문에 응용 프로그램 개발자들은 벤더(vendor) 사이의 불일치를 다루어야 하기도 하는데, 예를 들어 어떤 [셰이더](https://en.wikipedia.org/wiki/Shader) 문법이 받아들여지는지 아닌지와 같은 것입니다. 이러한 새로운 기능 이외에도, 지난 십 년 동안 새로 시작에 편입된 강력한 그래픽 하드웨어를 가진 모바일 기기가 있습니다. 이 모바일 GPU는 전력과 공간 요구사항으로 인해 또다른 아키텍처를 가지게 되었습니다. 한 예로 [tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering)이 있는데, 이 기능에 대해 프로그래머에게 보다 많은 제어 권한을 부여함으로써 성능이 향상될 수 있었습니다. 이러한 API의 설계 시기에 기인한 또 다른 문제로 멀티쓰레딩의 제약이 있습니다. 이로 인해 CPU 단에서의 병목이 발생하기도 합니다.
+
+Vulkan은 처음부터 현대적인 그래픽스 아키텍처에 기반한 설계를 통해 이러한 문제를 해결합니다. 보다 장황한 API를 통해 프로그래머가 의도를 명확하게 기술할 수 있게 함으로써 드라이버의 오버헤드(overhead)를 줄이고, 멀티쓰레드로 커맨드(command)을 병렬적으로 생성 및 제출(submit)할 수 있습니다. 단일 컴파일러, 표준화된 바이트 코드로 변경함으로써 셰이더 컴파일의 불일치 문제도 해결합니다. 마지막으로 그래픽스와 계산 기능을 단일 API로 병합하여 현대 그래픽 카드의 범용 계산(general purpose) 기능을 공식적으로 지원합니다.
+
+## 삼각형을 그리기 위해 필요한 것들
+
+이제 잘 만들어진 Vulkan 프로그램이 삼각형 하나를 그리기 위해 어떤 단계들을 거치는지 알아볼 것입니다. 여기 소개하는 모든 개념들은 다음 챕터들에서 보다 상세히 다룰 것입니다. 지금은 모든 각각의 컴포넌트들의 관계에 대한 큰 그림을 이해하면 됩니다.
+
+### 1단계 - 인스턴스와 물리 장치(physical device) 선택
+
+Vulkan 응용 프로그램은 `VkInstance`를 통해 Vulkan API를 설정함으로써 시작합니다. 인스턴스는 여러분의 응용 프로그램과 사용할 API 확장(extension)을 기술(describe)함으로써 생성됩니다. 인스턴스 생성 이후에는, Vulkan을 지원하는 하드웨어들을 쿼리하고 사용할 하나 이상의 `VkPhysicalDevice`를 선택합니다. 적절한 장치(예를 들어 특정 그래픽 카드)를 선택하기 위해 VRAM 크기라던데 장치의 기능들도 쿼리할 수 있습니다.
+
+### 2단계 - 논리적 장치(logical device)와 큐 패밀리(queue family)
+
+사용할 적절한 하드웨어를 선택한 뒤에는, 논리적 장치인 VkDevice를 만들어야 합니다. 이를 통해 좀 더 상세하게 다중 뷰포트 렌더링을 할 것인지, 64 bit float을 사용할지와 같은 상세 VkPhysicalDeviceFeatures를 기술합니다. 또한 어떤 큐 패밀리를 사용할지 명시해야 합니다. 그리기 커맨드나 메모리 연산과 같은 대부분의 연산들은 Vulkan을 통해 수행되는데 이는 이러한 작업들을 VkQueue에 제출한 후 비동기적으로 실행됩니다. 큐는 큐 패밀리에 할당되는데, 각 큐 패밀리는 큐에 있는 특정 연산의 집합을 지원합니다. 예를 들어, 그래픽스를 위한 큐 패밀리, 계산을 위한 큐 패밀리, 메모리 전송을 위한 큐 패밀리가 있을 수 있습니다. 큐 패밀리의 가용 여부는 물리적 장치 선택의 구분 기준으로도 활용될 수 있습니다. Vulkan을 지원하지만 그래픽스 연산 기능을 전혀 지원하지 않는 장치가 있을 수도 있습니다. 하지만 오늘날 Vulkan을 지원하는 모든 그래픽 카드들은 일반적으로 우리가 필요로 하는 모든 큐 연산을 지원하고 있습니다.
+
+### 3단계 - 윈도우 표면(window surface)과 스왑 체인(swap chain)
+
+오프스크린 렌더링만을 하지 않는 한, 렌더링된 이미지를 표시할 윈도우가 필요합니다. 윈도우는 [GLFW](http://www.glfw.org/)와 [SDL](https://www.libsdl.org/) 같은 네이티브 플랫폼 API나 라이브러리를 사용해 만들 수 있습니다. 이 튜토리얼에서는 GLFW를 사용할 것이고, 자세한 내용은 다음 챕터에서 다루겠습니다.
+
+실제 윈도우에 렌더링을 하기 위해서는 두 가지 컴포넌트가 더 필요합니다. 윈도우 표면(VkSurfaceKHR)과 스왑 체인(VkSwapchainKHR)입니다. `KHR` 접미어는 이것들이 Vulkan 확장의 일부임을 의미합니다. Vulkan API는 완벽히 플랫폼 독립적이며, 이로 인해 우리는 윈도우 매니저와 상호작용하기 위해서 표준화된 WSI (Window System Interface)를 사용해야만 합니다. 표면(surface)은 렌더링을 수행할 윈도우에 대한 크로스 플랫폼적 추상화를 의미하고 일반적으로 네이티브 윈도우 핸들(예를 들어 윈도우의 경우에는 `HWND`)에 대한 참조를 제공하여 인스턴스화됩니다. 다행히 GLFW 라이브러리는 여러 플랫폼들에 대한 이러한 상세 사항을 처리해 주는 내장 함수를 제공하고 있습니다.
+
+스왑 체인은 렌더 타겟(render target)의 집합입니다. 스왑 체인의 기본 목적은 우리가 현재 렌더링을 수행하고 있는 이미지와 화면에 그려진 이미지를 달리하는 데 있습니다. 완전히 다 그려진 이미지만을 화면에 표시하기 위한 중요한 기능입니다. 프레임(frame)을 그리기 위해서는 먼저 스왑 체인에 렌더링을 수행할 대상 이미지를 요청해야 합니다. 프레임 그리기가 끝나면, 이미지는 스왑 체인에 반환되어 어느 시점에 화면에 그려지게 됩니다. 렌더 타겟의 개수와 화면에 표시되는 상태는 표시 모드(present mode)를 통해 기술됩니다. 일반적인 표시 모드로는 이중 버퍼링(vsync)과 삼중 버퍼링입니다. 스왑 체인 생성 챕터에서 자세히 살펴볼 것입니다.
+
+어떤 플랫폼들은 `VK_KHR_display`와 `VK_KHR_display_swapchain` 확장을 통해 윈도우 관리자(manager)와 상호작용하지 않고 곧바로 화면에 그릴 수 있도록 허용하기도 합니다. 이 확장들은 전체 스크린을 표시하는 표면을 만들 수 있고, 이를 이용하여 예를 들자면 여러분 스스로 윈도우 관리자를 구현하는 데 사용될 수 있습니다.
+
+### 4단계 - 이미지 뷰와 프레임버퍼(framebuffer)
+
+스왑 체인으로부터 얻은 이미지에 그리기(draw)를 하기 위해서는 이미지를 VkImageView와 VkFramebuffer로 래핑(wrap)해야 합니다. 이미지 뷰는 사용될 이미지의 특정 부분을 참조하고, 프레임버퍼는 색상, 깊이, 스텐실(stensil) 그리기의 대상이 되는 이미지 뷰를 참조합니다. 스왑 체인에 여러 이미지들이 있을 수 있기 떄문에, 그 각각의 이미지에 대해 미리 이미지 뷰와 프레임버퍼를 생성해 두고 그리기 시점에 적절한 것들을 선택해야 합니다.
+
+### 5단계 - 렌더 패스(pass)
+
+Vulkan에서의 렌더 패스는 렌더링 연상 과정에 사용될 이미지의 타입을 기술합니다. 그것이 어떻게 사용될지, 그 내용이 어떻게 취급될지와 같은 것들 말입니다. 삼각형 그리기 응용 프로그램에서는 우리는 Vulkan에게 단일 이미지를 색상 타겟으로 사용할 것이고, 그리기 연산이 수행되기 직전에 단일 색상으로 지울(clear)것을 명시할 것입니다. 렌더 패스는 이미지의 타입만 지정하고, VkFramebuffer가 실제로 특정 이미지를 그 슬롯(slot)에 바인딩(bind)합니다.
+
+### 6단계 - 그래픽스 파이프라인
+
+Vulkan에서의 그래픽스 파이프라인은 VkPipeline 객체를 만들어서 설정됩니다. 뷰포트 크기와 깊이 버퍼 연산과 같은 그래픽 카드의 상태 구성 방법과 VkShaderModule 객체를 통한 프로그램 가능한 상태와 같은 것들을 기술합니다. VkShaderModule은 셰이더의 바이트 코드로부터 생성됩니다. 드라이버는 파이프라인에서 어떤 렌더 타겟이 사용될지 알아야 하는데 이는 렌더 패스 참조를 통해 명시할 수 있습니다.
+
+Vulkan과 기존 API간의 가장 큰 차이점은 거의 모든 그래픽스 파이프라인의 구성이 미리 설정되어야 한다는 것입니다. 즉, 여러분이 사른 셰이더를 사용하거나 정점의 레이아웃(layout)을 살짝 바꾸려고 할 때에도 그래픽스 파이프라인 전체를 다시 생성해야 한다는 것입니다. 그 말은 렌더링 연산을 위한 여러 조합들에 대해 VkPipeline들을 미리 만들어놔야 한다는 뜻입니다. 몇 가지 기본적인 구성, 예를 들어 뷰포트 크기나 지움(clear) 색상과 같은 것들만 동적으로 바꿀 수 있습니다. 또한 모든 상태는 명시적으로 기술되어야 합니다. 예를 들어 색상 혼합(blend) 상태의 기본값(default) 같은 것은 제공되지 않습니다.
+
+좋은 소식은 이러한 작업들이 ahead-of-time 컴파일과 just-in-time 컴파일의 차이 같은 것이라, 드라이버가 미리 최적화 할 수 있는 여지가 많고, 런타임 성능이 보다 예측하기 쉽다는 것입니다. 왜냐하면 다른 그래픽스 파이프라인으로의 변경과 같은 큰 상태 변화가 매우 명시적으로 표현되기 때문이지요.
+
+### 7단계 - 커맨드 풀(pool)과 커맨드 버퍼
+
+앞서 이야기한 것처럼, 우리가 실행하고자 하는, 예를 들자면 그리기와 같은 Vulkan의 많은 연산들은 큐에 제출되어야 합니다. 이러한 연산들은 제출되기 전에 VkCommandBuffer에 먼저 기록되어야 합니다. 이러한 커맨드 버퍼는 `VkCommandPool`로부터 할당되고 이는 특정한 큐 패밀리와 연관(associate)되어 있습니다. 간단한 삼각형을 그리기 위해서는 아래와 같은 연산들을 커맨드 버퍼에 기록해야 합니다.
+
+- 렌더 패스 시작
+- 그래픽스 파이프라인 바인딩
+- 3개 정점 그리기
+- 렌더 패스 종료
+
+프레임버퍼의 이미지는 스왑 체인이 우리에게 전달해준 이미지에 의존하기 때문에, 커맨드 버퍼에 기록할 명령어는 모든 가능한 이미지에 대해 기록되어야 하고, 그리기 시점에 올바른 것이 선택되어야 합니다. 다른 방법으로는 매 프레임마다 커맨드 버퍼를 기록하는 것인데, 그리 효율적이지 않습니다.
+
+### 8단계 - 메인 루프(main loop)
+
+그리기 커맨드가 커맨드 버퍼에 기록되었으므로 메인 루프는 직관적입니다. 먼저 스왑 체인으로부터 vkAcquireNextImageKHR를 통해 이미지를 얻습니다. 그리고 해당 이미지에 적합한 커맨드 버퍼를 선택하고 vkQueueSubmit를 통해 실행합니다. 마지막으로 vkQueuePresentKHR를 통해 이미지를 스왑 체인에 반환하여 화면에 표시되게 합니다.
+
+큐에 제출된 연산들은 비동기적으로 실행됩니다. 따라서 세마포어와 같은 동기 객체를 사용하여 실행이 올바른 순서로 이루어지도록 해야 합니다. 그리기 커맨드 버퍼의 실행은 이미지 획득 이후에 되도록 해야 합니다. 그렇지 않으면 화면에 표시하기 위해 읽고 있는 이미지에 렌더링이 수행될 수도 있습니다. vkQueuePresentKHR의 호출은 렌더링이 끝날 때까지 기다려야 하는데 이를 위해서는 렌더링이 끝나면 신호를 보내는 두 번째 세마포어를 사용해야 할 것입니다.
+
+### 요약
+
+이 정신없는 소개를 통해 삼각형을 그리기 앞서 알아야 할 것들에 대한 기본적인 이해가 되었길 바랍니다. 실제 프로그램은 몇 가지 단계가 더 필요한데, 정점 버퍼를 할당한다던지, 유니폼(uniform) 버퍼를 만들고 텍스처 이미지를 업로드한다던다 하는 것이고, 이어지는 챕터에서 소개할 것입니다. 우선은 간단히 시작할 것인데 Vulkan의 학습 곡선이 이미 충분히 가파르기 때문입니다. 주의하실 것은 초기에 우리는 정점의 좌표를 정점 버퍼를 사용하는 대신 정점 셰이더에 직접 하드코딩 하는 편법을 쓸 것입니다. 이는 정점 버퍼를 사용하기 위해서는 우선 커맨드 버퍼에 익숙해져야 하기 때문입니다.
+
+요약하자면, 삼각형을 그리기 위해서 우리는:
+
+- VkInstance 생성
+- 지원하는 그래픽 카드 선택 (VkPhysicalDevice)
+- 그리기와 표시를 위해 VkDevice와 VkQueue 생성
+- 윈도우, 윈도우 표면 및 스왑 체인 생성
+- VkImageView로 스왑 체인 이미지 래핑
+- 렌더 타겟과 사용법을 명시하는 렌더 패스 생성
+- 렌더 패스를 위한 프레임버퍼 생성
+- 그래픽스 파이프라인 설정
+- 모든 후보 스왑 체인 이미지에 대해 커맨드 버퍼를 할당하고 그리기 커맨드를 기록
+- 이미지를 획득하고, 올바른 그리기 커맨드를 제출하고, 이미지를 스왑 체인에 반환하여 프레임 그리기
+
+단계가 많지만 개별 단계의 목적에 대해서는 이후 챕터에서 명확하고 단순하게 설명할 것입니다. 개별 단계와 전체 프로그램의 관계에 대해 헷갈리시면 이 챕터로 다시 돌아 오십시오.
+
+## API 컨셉
+
+이 챕터는 Vulkan API가 저수준에서 어떻게 구조화 되어있는지를 간략히 살펴보는 것으로 마치겠습니다.
+
+### 코드 작성 규칙(convention)
+
+모든 Vulkan 함수, 열거형과 구조체들은 `vulkan.h` 헤더에 정의되어 있고, 이는 LunarG가 개발한 [Vulkan SDK](https://lunarg.com/vulkan-sdk/)에 포함되어 있습니다. 다음 챕터에서 이 SDK를 설치할 것입니다.
+
+함수는 소문자 `vk` 접두어를 갖고, 열거형 및 구조체와 같은 타입은 `Vk` 접두어, 열거자 값은 `VK_` 접두어를 갖습니다. API는 함수에 매개변수 전달을 위해 구조체를 아주 많이 사용합니다. 예를 들어, 객체의 생성은 대개 아래와 같은 패턴을 따릅니다:
+
+```c++
+VkXXXCreateInfo createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO;
+createInfo.pNext = nullptr;
+createInfo.foo = ...;
+createInfo.bar = ...;
+
+VkXXX object;
+if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) {
+    std::cerr << "failed to create object" << std::endl;
+    return false;
+}
+```
+
+Vulkan의 많은 구조체들은 해당 구조체의 타입을 `sType` 멤버를 통해 명시적으로 명시하도록 되어 있습니다. `pNext` 멤버는 확장 구조를 가리킬 수 있도록 되어 있는데 이 튜토리얼에서는 항상 `nullptr`입니다. 객체를 생성하서나 소멸(destroy)하는 함수는 VkAllocationCallbacks 매개변수를 가지고 있어서 드라이버 메모리에 대한 커스텀 할당자(allocator)를 사용할 수 있도록 하는데 이 역시 이 튜토리얼에서는 항상 `nullptr`로 둘 것입니다.
+
+대부분의 함수는 VkResult를 반환하고 이는 `VK_SUCCESS`이거나 오류 코드입니다. 명세를 보면 각 함수가 반환할 수 있는 오류 코드와 그 의미가 적혀 있습니다.
+
+### 검증 레이어(validation layer)
+
+앞서 이야기한 것처럼, Vulkan은 고성능과 적은 드라이버 오버헤드를 위해 설계되었습니다. 따라서 기본적으로는 아주 제한적인 오류 체킹과 디버깅 기능만을 포함하고 있습니다. 코드를 잘못 작성하는 드라이버는 오류 코드를 반환하는 대신 그냥 크래쉬(crash)가 발생하거나, 더 나쁜 경우에는 여러분의 그래픽 카드에서는 제대로 동작하는 것처럼 보이지만 다른 그래픽 카드에서는 전혀 동작하지 않을겁니다.
+
+Vulkan은 꼼꼼한 오류 체크를 _validation layers_ 기능을 통해 제공합니다. 검증 레이어는 API와 그래픽 드라이버 사이에 삽입되는 코드로 함수 매개변수에 대한 추가적인 검증이나 메모리 관리 문제를 추적하는 데 사용됩니다. 좋은 점은 이러한 기능을 개발 과정에서 사용하고 릴리즈 할 때에는 완전히 사용하지 않도록 하여 오버헤드를 없앨 수 있다는 것입니다. 스스로 검증 레이어를 작성할 수도 있지만, LunarG가 만든 Vulkan SDK는 표준적인 검증 레이어를 제공하고, 이 튜토리얼에서는 그것을 사용할 것입니다. 여러분은 레이어에서 날아온 디버깅 메시지를 처기하기 위한 콜백 함수를 등록해야 합니다.
+
+Vulkan의 각 연산은 아주 명시적이고 검증 레이어는 꼼꼼하기 떄문에 화면이 검은 색 밖에 안나오는 경우에 OpenGL이나 Direct3D보다 그 원인을 찾기가 훨씬 쉽습니다!
+
+코드를 작성하기 전에 시작해야 할 남은 한 단계는 [개발 환경을 설정](!kr/Development_environment)하는 것입니다.
diff --git a/kr/02_Development_environment.md b/kr/02_Development_environment.md
new file mode 100644
index 00000000..8eac260f
--- /dev/null
+++ b/kr/02_Development_environment.md
@@ -0,0 +1,544 @@
+In this chapter we'll set up your environment for developing Vulkan applications
+and install some useful libraries. All of the tools we'll use, with the
+exception of the compiler, are compatible with Windows, Linux and MacOS, but the
+steps for installing them differ a bit, which is why they're described
+separately here.
+
+## Windows
+
+If you're developing for Windows, then I will assume that you are using Visual
+Studio to compile your code. For complete C++17 support, you need to use either
+Visual Studio 2017 or 2019. The steps outlined below were written for VS 2017.
+
+### Vulkan SDK
+
+The most important component you'll need for developing Vulkan applications is
+the SDK. It includes the headers, standard validation layers, debugging tools
+and a loader for the Vulkan functions. The loader looks up the functions in the
+driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that.
+
+The SDK can be downloaded from [the LunarG website](https://vulkan.lunarg.com/)
+using the buttons at the bottom of the page. You don't have to create an
+account, but it will give you access to some additional documentation that may
+be useful to you.
+
+![](/images/vulkan_sdk_download_buttons.png)
+
+Proceed through the installation and pay attention to the install location of
+the SDK. The first thing we'll do is verify that your graphics card and driver
+properly support Vulkan. Go to the directory where you installed the SDK, open
+the `Bin` directory and run the `vkcube.exe` demo. You should see the following:
+
+![](/images/cube_demo.png)
+
+If you receive an error message then ensure that your drivers are up-to-date,
+include the Vulkan runtime and that your graphics card is supported. See the
+[introduction chapter](!en/Introduction) for links to drivers from the major
+vendors.
+
+There is another program in this directory that will be useful for development. The `glslangValidator.exe` and `glslc.exe` programs will be used to compile shaders from the
+human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to
+bytecode. We'll cover this in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules)
+chapter. The `Bin` directory also contains the binaries of the Vulkan loader
+and the validation layers, while the `Lib` directory contains the libraries.
+
+Lastly, there's the `Include` directory that contains the Vulkan headers. Feel free to explore the other files, but we won't need them for this tutorial.
+
+### GLFW
+
+As mentioned before, Vulkan by itself is a platform agnostic API and does not
+include tools for creating a window to display the rendered results. To benefit
+from the cross-platform advantages of Vulkan and to avoid the horrors of Win32,
+we'll use the [GLFW library](http://www.glfw.org/) to create a window, which
+supports Windows, Linux and MacOS. There are other libraries available for this
+purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that
+it also abstracts away some of the other platform-specific things in Vulkan
+besides just window creation.
+
+You can find the latest release of GLFW on the [official website](http://www.glfw.org/download.html).
+In this tutorial we'll be using the 64-bit binaries, but you can of course also
+choose to build in 32 bit mode. In that case make sure to link with the Vulkan
+SDK binaries in the `Lib32` directory instead of `Lib`. After downloading it, extract the archive
+to a convenient location. I've chosen to create a `Libraries` directory in the
+Visual Studio directory under documents.
+
+![](/images/glfw_directory.png)
+
+### GLM
+
+Unlike DirectX 12, Vulkan does not include a library for linear algebra
+operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a
+nice library that is designed for use with graphics APIs and is also commonly
+used with OpenGL.
+
+GLM is a header-only library, so just download the [latest version](https://github.com/g-truc/glm/releases)
+and store it in a convenient location. You should have a directory structure
+similar to the following now:
+
+![](/images/library_directory.png)
+
+### Setting up Visual Studio
+
+Now that you've installed all of the dependencies we can set up a basic Visual
+Studio project for Vulkan and write a little bit of code to make sure that
+everything works.
+
+Start Visual Studio and create a new `Windows Desktop Wizard` project by entering a name and pressing `OK`.
+
+![](/images/vs_new_cpp_project.png)
+
+Make sure that `Console Application (.exe)` is selected as application type so that we have a place to print debug messages to, and check `Empty Project` to prevent Visual Studio from adding boilerplate code.
+
+![](/images/vs_application_settings.png)
+
+Press `OK` to create the project and add a C++ source file. You should
+already know how to do that, but the steps are included here for completeness.
+
+![](/images/vs_new_item.png)
+
+![](/images/vs_new_source_file.png)
+
+Now add the following code to the file. Don't worry about trying to
+understand it right now; we're just making sure that you can compile and run
+Vulkan applications. We'll start from scratch in the next chapter.
+
+```c++
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/vec4.hpp>
+#include <glm/mat4x4.hpp>
+
+#include <iostream>
+
+int main() {
+    glfwInit();
+
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr);
+
+    uint32_t extensionCount = 0;
+    vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
+
+    std::cout << extensionCount << " extensions supported\n";
+
+    glm::mat4 matrix;
+    glm::vec4 vec;
+    auto test = matrix * vec;
+
+    while(!glfwWindowShouldClose(window)) {
+        glfwPollEvents();
+    }
+
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+
+    return 0;
+}
+```
+
+Let's now configure the project to get rid of the errors. Open the project
+properties dialog and ensure that `All Configurations` is selected, because most
+of the settings apply to both `Debug` and `Release` mode.
+
+![](/images/vs_open_project_properties.png)
+
+![](/images/vs_all_configs.png)
+
+Go to `C++ -> General -> Additional Include Directories` and press `<Edit...>`
+in the dropdown box.
+
+![](/images/vs_cpp_general.png)
+
+Add the header directories for Vulkan, GLFW and GLM:
+
+![](/images/vs_include_dirs.png)
+
+Next, open the editor for library directories under `Linker -> General`:
+
+![](/images/vs_link_settings.png)
+
+And add the locations of the object files for Vulkan and GLFW:
+
+![](/images/vs_link_dirs.png)
+
+Go to `Linker -> Input` and press `<Edit...>` in the `Additional Dependencies`
+dropdown box.
+
+![](/images/vs_link_input.png)
+
+Enter the names of the Vulkan and GLFW object files:
+
+![](/images/vs_dependencies.png)
+
+And finally change the compiler to support C++17 features:
+
+![](/images/vs_cpp17.png)
+
+You can now close the project properties dialog. If you did everything right
+then you should no longer see any more errors being highlighted in the code.
+
+Finally, ensure that you are actually compiling in 64 bit mode:
+
+![](/images/vs_build_mode.png)
+
+Press `F5` to compile and run the project and you should see a command prompt
+and a window pop up like this:
+
+![](/images/vs_test_window.png)
+
+The number of extensions should be non-zero. Congratulations, you're all set for
+[playing with Vulkan](!en/Drawing_a_triangle/Setup/Base_code)!
+
+## Linux
+
+These instructions will be aimed at Ubuntu, Fedora and Arch Linux users, but you may be able to follow
+along by changing the package manager-specific commands to the ones that are appropriate for you. You should have a compiler that supports C++17 (GCC 7+ or Clang 5+). You'll also need `make`.
+
+### Vulkan Packages
+
+The most important components you'll need for developing Vulkan applications on Linux are the Vulkan loader, validation layers, and a couple of command-line utilities to test whether your machine is Vulkan-capable:
+
+* `sudo apt install vulkan-tools` or `sudo dnf install vulkan-tools`: Command-line utilities, most importantly `vulkaninfo` and `vkcube`. Run these to confirm your machine supports Vulkan.
+* `sudo apt install libvulkan-dev` or `sudo dnf install vulkan-loader-devel` : Installs Vulkan loader. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that.
+* `sudo apt install vulkan-validationlayers-dev spirv-tools` or `sudo dnf install mesa-vulkan-devel vulkan-validation-layers-devel`: Installs the standard validation layers and required SPIR-V tools. These are crucial when debugging Vulkan applications, and we'll discuss them in the upcoming chapter.
+
+On Arch Linux, you can run `sudo pacman -S vulkan-devel` to install all the
+required tools above.
+
+If installation was successful, you should be all set with the Vulkan portion. Remember to run
+ `vkcube` and ensure you see the following pop up in a window:
+
+![](/images/cube_demo_nowindow.png)
+
+If you receive an error message then ensure that your drivers are up-to-date,
+include the Vulkan runtime and that your graphics card is supported. See the
+[introduction chapter](!en/Introduction) for links to drivers from the major
+vendors.
+
+### X Window System and XFree86-VidModeExtension
+It is possible that these libraries are not on the system, if not, you can install them using the following commands:
+* `sudo apt install libxxf86vm-dev` or `dnf install libXxf86vm-devel`: Provides an interface to the XFree86-VidModeExtension.
+* `sudo apt install libxi-dev` or `dnf install libXi-devel`: Provides an X Window System client interface to the XINPUT extension.
+
+### GLFW
+
+As mentioned before, Vulkan by itself is a platform agnostic API and does not
+include tools for creation a window to display the rendered results. To benefit
+from the cross-platform advantages of Vulkan and to avoid the horrors of X11,
+we'll use the [GLFW library](http://www.glfw.org/) to create a window, which
+supports Windows, Linux and MacOS. There are other libraries available for this
+purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that
+it also abstracts away some of the other platform-specific things in Vulkan
+besides just window creation.
+
+We'll be installing GLFW from the following command:
+
+```bash
+sudo apt install libglfw3-dev
+```
+or
+```bash
+sudo dnf install glfw-devel
+```
+or
+```bash
+sudo pacman -S glfw-wayland # glfw-x11 for X11 users
+```
+
+### GLM
+
+Unlike DirectX 12, Vulkan does not include a library for linear algebra
+operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a
+nice library that is designed for use with graphics APIs and is also commonly
+used with OpenGL.
+
+It is a header-only library that can be installed from the `libglm-dev` or
+`glm-devel` package:
+
+```bash
+sudo apt install libglm-dev
+```
+or
+```bash
+sudo dnf install glm-devel
+```
+or
+```bash
+sudo pacman -S glm
+```
+
+### Shader Compiler
+
+We have just about all we need, except we'll want a program to compile shaders from the human-readable  [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to bytecode.
+
+Two popular shader compilers are Khronos Group's `glslangValidator` and Google's `glslc`. The latter has a familiar GCC- and Clang-like usage, so we'll go with that: on Ubuntu, download Google's [unofficial binaries](https://github.com/google/shaderc/blob/main/downloads.md) and copy `glslc` to your `/usr/local/bin`. Note you may need to `sudo` depending on your permissions. On Fedora use `sudo dnf install glslc`, while on Arch Linux run `sudo pacman -S shaderc`.  To test, run `glslc` and it should rightfully complain we didn't pass any shaders to compile:
+
+`glslc: error: no input files`
+
+We'll cover `glslc` in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) chapter.
+
+### Setting up a makefile project
+
+Now that you have installed all of the dependencies, we can set up a basic
+makefile project for Vulkan and write a little bit of code to make sure that
+everything works.
+
+Create a new directory at a convenient location with a name like `VulkanTest`.
+Create a source file called `main.cpp` and insert the following code. Don't
+worry about trying to understand it right now; we're just making sure that you
+can compile and run Vulkan applications. We'll start from scratch in the next
+chapter.
+
+```c++
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/vec4.hpp>
+#include <glm/mat4x4.hpp>
+
+#include <iostream>
+
+int main() {
+    glfwInit();
+
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr);
+
+    uint32_t extensionCount = 0;
+    vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
+
+    std::cout << extensionCount << " extensions supported\n";
+
+    glm::mat4 matrix;
+    glm::vec4 vec;
+    auto test = matrix * vec;
+
+    while(!glfwWindowShouldClose(window)) {
+        glfwPollEvents();
+    }
+
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+
+    return 0;
+}
+```
+
+Next, we'll write a makefile to compile and run this basic Vulkan code. Create a
+new empty file called `Makefile`. I will assume that you already have some basic
+experience with makefiles, like how variables and rules work. If not, you can
+get up to speed very quickly with [this tutorial](https://makefiletutorial.com/).
+
+We'll first define a couple of variables to simplify the remainder of the file.
+Define a `CFLAGS` variable that will specify the basic compiler flags:
+
+```make
+CFLAGS = -std=c++17 -O2
+```
+
+We're going to use modern C++ (`-std=c++17`), and we'll set optimization level to O2. We can remove -O2 to compile programs faster, but we should remember to place it back for release builds.
+
+Similarly, define the linker flags in a `LDFLAGS` variable:
+
+```make
+LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi
+```
+
+The flag `-lglfw` is for GLFW, `-lvulkan` links with the Vulkan function loader and the remaining flags are low-level system libraries that GLFW needs. The remaining flags are dependencies of GLFW itself: the threading and window management.
+
+It is possible that the `Xxf68vm` and `Xi` libraries are not yet installed on your system. You can find them in the following packages:
+
+```bash
+sudo apt install libxxf86vm-dev libxi-dev
+```
+or
+```bash
+sudo dnf install libXi-devel libXxf86vm-devel
+```
+or
+```bash
+sudo pacman -S libxi libxxf86vm
+```
+
+Specifying the rule to compile `VulkanTest` is straightforward now. Make sure to
+use tabs for indentation instead of spaces.
+
+```make
+VulkanTest: main.cpp
+	g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS)
+```
+
+Verify that this rule works by saving the makefile and running `make` in the
+directory with `main.cpp` and `Makefile`. This should result in a `VulkanTest`
+executable.
+
+We'll now define two more rules, `test` and `clean`, where the former will
+run the executable and the latter will remove a built executable:
+
+```make
+.PHONY: test clean
+
+test: VulkanTest
+	./VulkanTest
+
+clean:
+	rm -f VulkanTest
+```
+
+Running `make test` should show the program running successfully, and displaying the number of Vulkan extensions. The application should exit with the success return code (`0`) when you close the empty window. You should now have a complete makefile that resembles the following:
+
+```make
+CFLAGS = -std=c++17 -O2
+LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi
+
+VulkanTest: main.cpp
+	g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS)
+
+.PHONY: test clean
+
+test: VulkanTest
+	./VulkanTest
+
+clean:
+	rm -f VulkanTest
+```
+
+You can now use this directory as a template for your Vulkan projects. Make a copy, rename it to something like `HelloTriangle` and remove all of the code in `main.cpp`.
+
+You are now all set for [the real adventure](!en/Drawing_a_triangle/Setup/Base_code).
+
+## MacOS
+
+These instructions will assume you are using Xcode and the [Homebrew package manager](https://brew.sh/). Also, keep in mind that you will need at least MacOS version 10.11, and your device needs to support the [Metal API](https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs).
+
+### Vulkan SDK
+
+The most important component you'll need for developing Vulkan applications is the SDK. It includes the headers, standard validation layers, debugging tools and a loader for the Vulkan functions. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that.
+
+The SDK can be downloaded from [the LunarG website](https://vulkan.lunarg.com/) using the buttons at the bottom of the page. You don't have to create an account, but it will give you access to some additional documentation that may be useful to you.
+
+![](/images/vulkan_sdk_download_buttons.png)
+
+The SDK version for MacOS internally uses [MoltenVK](https://moltengl.com/). There is no native support for Vulkan on MacOS, so what MoltenVK does is actually act as a layer that translates Vulkan API calls to Apple's Metal graphics framework. With this you can take advantage of debugging and performance benefits of Apple's Metal framework.
+
+After downloading it, simply extract the contents to a folder of your choice (keep in mind you will need to reference it when creating your projects on Xcode). Inside the extracted folder, in the `Applications` folder you should have some executable files that will run a few demos using the SDK. Run the `vkcube` executable and you will see the following:
+
+![](/images/cube_demo_mac.png)
+
+### GLFW
+
+As mentioned before, Vulkan by itself is a platform agnostic API and does not include tools for creation a window to display the rendered results. We'll use the [GLFW library](http://www.glfw.org/) to create a window, which supports Windows, Linux and MacOS. There are other libraries available for this purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that it also abstracts away some of the other platform-specific things in Vulkan besides just window creation.
+
+To install GLFW on MacOS we will use the Homebrew package manager to get the `glfw` package:
+
+```bash
+brew install glfw
+```
+
+### GLM
+
+Vulkan does not include a library for linear algebra operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a nice library that is designed for use with graphics APIs and is also commonly used with OpenGL.
+
+It is a header-only library that can be installed from the `glm` package:
+
+```bash
+brew install glm
+```
+
+### Setting up Xcode
+
+Now that all the dependencies are installed we can set up a basic Xcode project for Vulkan. Most of the instructions here are essentially a lot of "plumbing" so we can get all the dependencies linked to the project. Also, keep in mind that during the following instructions whenever we mention the folder `vulkansdk` we are refering to the folder where you extracted the Vulkan SDK.
+
+Start Xcode and create a new Xcode project. On the window that will open select Application > Command Line Tool.
+
+![](/images/xcode_new_project.png)
+
+Select `Next`, write a name for the project and for `Language` select `C++`.
+
+![](/images/xcode_new_project_2.png)
+
+Press `Next` and the project should have been created. Now, let's change the code in the generated `main.cpp` file to the following code:
+
+```c++
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/vec4.hpp>
+#include <glm/mat4x4.hpp>
+
+#include <iostream>
+
+int main() {
+    glfwInit();
+
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr);
+
+    uint32_t extensionCount = 0;
+    vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
+
+    std::cout << extensionCount << " extensions supported\n";
+
+    glm::mat4 matrix;
+    glm::vec4 vec;
+    auto test = matrix * vec;
+
+    while(!glfwWindowShouldClose(window)) {
+        glfwPollEvents();
+    }
+
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+
+    return 0;
+}
+```
+
+Keep in mind you are not required to understand all this code is doing yet, we are just setting up some API calls to make sure everything is working.
+
+Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the *Project Navigator* panel select your project. Open the *Build Settings* tab and then:
+
+* Find the **Header Search Paths** field and add a link to `/usr/local/include` (this is where Homebrew installs headers, so the glm and glfw3 header files should be there) and a link to `vulkansdk/macOS/include` for the Vulkan headers.
+* Find the **Library Search Paths** field and add a link to `/usr/local/lib` (again, this is where Homebrew installs libraries, so the glm and glfw3 lib files should be there) and a link to `vulkansdk/macOS/lib`.
+
+It should look like so (obviously, paths will be different depending on where you placed on your files):
+
+![](/images/xcode_paths.png)
+
+Now, in the *Build Phases* tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding the dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks).
+
+* For glfw open the folder `/usr/local/lib` and there you will find a file name like `libglfw.3.x.dylib` ("x" is the library's version number, it might be different depending on when you downloaded the package from Homebrew). Simply drag that file to the Linked Frameworks and Libraries tab on Xcode.
+* For vulkan, go to `vulkansdk/macOS/lib`. Do the same for the both files `libvulkan.1.dylib` and `libvulkan.1.x.xx.dylib` (where "x" will be the version number of the the SDK you downloaded).
+
+After adding those libraries, in the same tab on **Copy Files** change `Destination` to "Frameworks", clear the subpath and deselect "Copy only when installing". Click on the "+" sign and add all those three frameworks here aswell.
+
+Your Xcode configuration should look like:
+
+![](/images/xcode_frameworks.png)
+
+The last thing you need to setup are a couple of environment variables. On Xcode toolbar go to `Product` > `Scheme` > `Edit Scheme...`, and in the `Arguments` tab add the two following environment variables:
+
+* VK_ICD_FILENAMES = `vulkansdk/macOS/share/vulkan/icd.d/MoltenVK_icd.json`
+* VK_LAYER_PATH = `vulkansdk/macOS/share/vulkan/explicit_layer.d`
+
+It should look like so:
+
+![](/images/xcode_variables.png)
+
+Finally, you should be all set! Now if you run the project (remembering to setting the build configuration to Debug or Release depending on the configuration you chose) you should see the following:
+
+![](/images/xcode_output.png)
+
+The number of extensions should be non-zero. The other logs are from the libraries, you might get different messages from those depending on your configuration.
+
+You are now all set for [the real thing](!en/Drawing_a_triangle/Setup/Base_code).
diff --git a/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md b/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md
new file mode 100644
index 00000000..df26c6ac
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md
@@ -0,0 +1,217 @@
+## General structure
+
+In the previous chapter you've created a Vulkan project with all of the proper
+configuration and tested it with the sample code. In this chapter we're starting
+from scratch with the following code:
+
+```c++
+#include <vulkan/vulkan.h>
+
+#include <iostream>
+#include <stdexcept>
+#include <cstdlib>
+
+class HelloTriangleApplication {
+public:
+    void run() {
+        initVulkan();
+        mainLoop();
+        cleanup();
+    }
+
+private:
+    void initVulkan() {
+
+    }
+
+    void mainLoop() {
+
+    }
+
+    void cleanup() {
+
+    }
+};
+
+int main() {
+    HelloTriangleApplication app;
+
+    try {
+        app.run();
+    } catch (const std::exception& e) {
+        std::cerr << e.what() << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
+```
+
+We first include the Vulkan header from the LunarG SDK, which provides the
+functions, structures and enumerations. The `stdexcept` and `iostream` headers
+are included for reporting and propagating errors. The `cstdlib`
+header provides the `EXIT_SUCCESS` and `EXIT_FAILURE` macros.
+
+The program itself is wrapped into a class where we'll store the Vulkan objects
+as private class members and add functions to initiate each of them, which will
+be called from the `initVulkan` function. Once everything has been prepared, we
+enter the main loop to start rendering frames. We'll fill in the `mainLoop`
+function to include a loop that iterates until the window is closed in a moment.
+Once the window is closed and `mainLoop` returns, we'll make sure to deallocate
+the resources we've used in the `cleanup` function.
+
+If any kind of fatal error occurs during execution then we'll throw a
+`std::runtime_error` exception with a descriptive message, which will propagate
+back to the `main` function and be printed to the command prompt. To handle 
+a variety of standard exception types as well, we catch the more general `std::exception`. One example of an error that we will deal with soon is finding 
+out that a certain required extension is not supported.
+
+Roughly every chapter that follows after this one will add one new function that
+will be called from `initVulkan` and one or more new Vulkan objects to the
+private class members that need to be freed at the end in `cleanup`.
+
+## Resource management
+
+Just like each chunk of memory allocated with `malloc` requires a call to
+`free`, every Vulkan object that we create needs to be explicitly destroyed when
+we no longer need it. In C++ it is possible to perform automatic resource 
+management using [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) 
+or smart pointers provided in the `<memory>` header. However, I've chosen to be
+explicit about allocation and deallocation of Vulkan objects in this tutorial.
+After all, Vulkan's niche is to be explicit about every operation to avoid
+mistakes, so it's good to be explicit about the lifetime of objects to learn how
+the API works.
+
+After following this tutorial, you could implement automatic resource management
+by writing C++ classes that acquire Vulkan objects in their constructor and
+release them in their destructor, or by providing a custom deleter to either
+`std::unique_ptr` or `std::shared_ptr`, depending on your ownership requirements. 
+RAII is the recommended model for larger Vulkan programs, but
+for learning purposes it's always good to know what's going on behind the
+scenes.
+
+Vulkan objects are either created directly with functions like `vkCreateXXX`, or
+allocated through another object with functions like `vkAllocateXXX`. After
+making sure that an object is no longer used anywhere, you need to destroy it
+with the counterparts `vkDestroyXXX` and `vkFreeXXX`. The parameters for these
+functions generally vary for different types of objects, but there is one
+parameter that they all share: `pAllocator`. This is an optional parameter that
+allows you to specify callbacks for a custom memory allocator. We will ignore
+this parameter in the tutorial and always pass `nullptr` as argument.
+
+## Integrating GLFW
+
+Vulkan works perfectly fine without creating a window if you want to use it for
+off-screen rendering, but it's a lot more exciting to actually show something!
+First replace the `#include <vulkan/vulkan.h>` line with
+
+```c++
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+```
+
+That way GLFW will include its own definitions and automatically load the Vulkan
+header with it. Add a `initWindow` function and add a call to it from the `run`
+function before the other calls. We'll use that function to initialize GLFW and
+create a window.
+
+```c++
+void run() {
+    initWindow();
+    initVulkan();
+    mainLoop();
+    cleanup();
+}
+
+private:
+    void initWindow() {
+
+    }
+```
+
+The very first call in `initWindow` should be `glfwInit()`, which initializes
+the GLFW library. Because GLFW was originally designed to create an OpenGL
+context, we need to tell it to not create an OpenGL context with a subsequent
+call:
+
+```c++
+glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+```
+
+Because handling resized windows takes special care that we'll look into later,
+disable it for now with another window hint call:
+
+```c++
+glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
+```
+
+All that's left now is creating the actual window. Add a `GLFWwindow* window;`
+private class member to store a reference to it and initialize the window with:
+
+```c++
+window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
+```
+
+The first three parameters specify the width, height and title of the window.
+The fourth parameter allows you to optionally specify a monitor to open the
+window on and the last parameter is only relevant to OpenGL.
+
+It's a good idea to use constants instead of hardcoded width and height numbers
+because we'll be referring to these values a couple of times in the future. I've
+added the following lines above the `HelloTriangleApplication` class definition:
+
+```c++
+const uint32_t WIDTH = 800;
+const uint32_t HEIGHT = 600;
+```
+
+and replaced the window creation call with
+
+```c++
+window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
+```
+
+You should now have a `initWindow` function that looks like this:
+
+```c++
+void initWindow() {
+    glfwInit();
+
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
+
+    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
+}
+```
+
+To keep the application running until either an error occurs or the window is
+closed, we need to add an event loop to the `mainLoop` function as follows:
+
+```c++
+void mainLoop() {
+    while (!glfwWindowShouldClose(window)) {
+        glfwPollEvents();
+    }
+}
+```
+
+This code should be fairly self-explanatory. It loops and checks for events like
+pressing the X button until the window has been closed by the user. This is also
+the loop where we'll later call a function to render a single frame.
+
+Once the window is closed, we need to clean up resources by destroying it and
+terminating GLFW itself. This will be our first `cleanup` code:
+
+```c++
+void cleanup() {
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+}
+```
+
+When you run the program now you should see a window titled `Vulkan` show up
+until the application is terminated by closing the window. Now that we have the
+skeleton for the Vulkan application, let's [create the first Vulkan object](!en/Drawing_a_triangle/Setup/Instance)!
+
+[C++ code](/code/00_base_code.cpp)
diff --git a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
new file mode 100644
index 00000000..d9744a1c
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
@@ -0,0 +1,221 @@
+## Creating an instance
+
+The very first thing you need to do is initialize the Vulkan library by creating
+an *instance*. The instance is the connection between your application and the
+Vulkan library and creating it involves specifying some details about your
+application to the driver.
+
+Start by adding a `createInstance` function and invoking it in the
+`initVulkan` function.
+
+```c++
+void initVulkan() {
+    createInstance();
+}
+```
+
+Additionally add a data member to hold the handle to the instance:
+
+```c++
+private:
+VkInstance instance;
+```
+
+Now, to create an instance we'll first have to fill in a struct with some
+information about our application. This data is technically optional, but it may
+provide some useful information to the driver in order to optimize our specific
+application (e.g. because it uses a well-known graphics engine with
+certain special behavior). This struct is called `VkApplicationInfo`:
+
+```c++
+void createInstance() {
+    VkApplicationInfo appInfo{};
+    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+    appInfo.pApplicationName = "Hello Triangle";
+    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+    appInfo.pEngineName = "No Engine";
+    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+    appInfo.apiVersion = VK_API_VERSION_1_0;
+}
+```
+
+As mentioned before, many structs in Vulkan require you to explicitly specify
+the type in the `sType` member. This is also one of the many structs with a
+`pNext` member that can point to extension information in the future. We're
+using value initialization here to leave it as `nullptr`.
+
+A lot of information in Vulkan is passed through structs instead of function
+parameters and we'll have to fill in one more struct to provide sufficient
+information for creating an instance. This next struct is not optional and tells
+the Vulkan driver which global extensions and validation layers we want to use.
+Global here means that they apply to the entire program and not a specific
+device, which will become clear in the next few chapters.
+
+```c++
+VkInstanceCreateInfo createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+createInfo.pApplicationInfo = &appInfo;
+```
+
+The first two parameters are straightforward. The next two layers specify the
+desired global extensions. As mentioned in the overview chapter, Vulkan is a
+platform agnostic API, which means that you need an extension to interface with
+the window system. GLFW has a handy built-in function that returns the
+extension(s) it needs to do that which we can pass to the struct:
+
+```c++
+uint32_t glfwExtensionCount = 0;
+const char** glfwExtensions;
+
+glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
+
+createInfo.enabledExtensionCount = glfwExtensionCount;
+createInfo.ppEnabledExtensionNames = glfwExtensions;
+```
+
+The last two members of the struct determine the global validation layers to
+enable. We'll talk about these more in-depth in the next chapter, so just leave
+these empty for now.
+
+```c++
+createInfo.enabledLayerCount = 0;
+```
+
+We've now specified everything Vulkan needs to create an instance and we can
+finally issue the `vkCreateInstance` call:
+
+```c++
+VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
+```
+
+As you'll see, the general pattern that object creation function parameters in
+Vulkan follow is:
+
+* Pointer to struct with creation info
+* Pointer to custom allocator callbacks, always `nullptr` in this tutorial
+* Pointer to the variable that stores the handle to the new object
+
+If everything went well then the handle to the instance was stored in the
+`VkInstance` class member. Nearly all Vulkan functions return a value of type
+`VkResult` that is either `VK_SUCCESS` or an error code. To check if the
+instance was created successfully, we don't need to store the result and can
+just use a check for the success value instead:
+
+```c++
+if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create instance!");
+}
+```
+
+Now run the program to make sure that the instance is created successfully.
+
+## Encountered VK_ERROR_INCOMPATIBLE_DRIVER:
+If using MacOS with the latest MoltenVK sdk, you may get `VK_ERROR_INCOMPATIBLE_DRIVER`
+returned from `vkCreateInstance`. According to the [Getting Start Notes](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html). Beginning with the 1.3.216 Vulkan SDK, the `VK_KHR_PORTABILITY_subset`
+extension is mandatory.
+
+To get over this error, first add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` bit
+to `VkInstanceCreateInfo` struct's flags, then add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME`
+to instance enabled extension list.
+
+Typically the code could be like this:
+```c++
+...
+
+std::vector<const char*> requiredExtensions;
+
+for(uint32_t i = 0; i < glfwExtensionCount; i++) {
+    requiredExtensions.emplace_back(glfwExtensions[i]);
+}
+
+requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
+
+createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+
+createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size();
+createInfo.ppEnabledExtensionNames = requiredExtensions.data();
+
+if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create instance!");
+}
+```
+
+## Checking for extension support
+
+If you look at the `vkCreateInstance` documentation then you'll see that one of
+the possible error codes is `VK_ERROR_EXTENSION_NOT_PRESENT`. We could simply
+specify the extensions we require and terminate if that error code comes back.
+That makes sense for essential extensions like the window system interface, but
+what if we want to check for optional functionality?
+
+To retrieve a list of supported extensions before creating an instance, there's
+the `vkEnumerateInstanceExtensionProperties` function. It takes a pointer to a
+variable that stores the number of extensions and an array of
+`VkExtensionProperties` to store details of the extensions. It also takes an
+optional first parameter that allows us to filter extensions by a specific
+validation layer, which we'll ignore for now.
+
+To allocate an array to hold the extension details we first need to know how
+many there are. You can request just the number of extensions by leaving the
+latter parameter empty:
+
+```c++
+uint32_t extensionCount = 0;
+vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
+```
+
+Now allocate an array to hold the extension details (`include <vector>`):
+
+```c++
+std::vector<VkExtensionProperties> extensions(extensionCount);
+```
+
+Finally we can query the extension details:
+
+```c++
+vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
+```
+
+Each `VkExtensionProperties` struct contains the name and version of an
+extension. We can list them with a simple for loop (`\t` is a tab for
+indentation):
+
+```c++
+std::cout << "available extensions:\n";
+
+for (const auto& extension : extensions) {
+    std::cout << '\t' << extension.extensionName << '\n';
+}
+```
+
+You can add this code to the `createInstance` function if you'd like to provide
+some details about the Vulkan support. As a challenge, try to create a function
+that checks if all of the extensions returned by
+`glfwGetRequiredInstanceExtensions` are included in the supported extensions
+list.
+
+## Cleaning up
+
+The `VkInstance` should be only destroyed right before the program exits. It can
+be destroyed in `cleanup` with the `vkDestroyInstance` function:
+
+```c++
+void cleanup() {
+    vkDestroyInstance(instance, nullptr);
+
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+}
+```
+
+The parameters for the `vkDestroyInstance` function are straightforward. As
+mentioned in the previous chapter, the allocation and deallocation functions
+in Vulkan have an optional allocator callback that we'll ignore by passing
+`nullptr` to it. All of the other Vulkan resources that we'll create in the
+following chapters should be cleaned up before the instance is destroyed.
+
+Before continuing with the more complex steps after instance creation, it's time
+to evaluate our debugging options by checking out [validation layers](!en/Drawing_a_triangle/Setup/Validation_layers).
+
+[C++ code](/code/01_instance_creation.cpp)
diff --git a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
new file mode 100644
index 00000000..569a0178
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
@@ -0,0 +1,458 @@
+## What are validation layers?
+
+The Vulkan API is designed around the idea of minimal driver overhead and one of
+the manifestations of that goal is that there is very limited error checking in
+the API by default. Even mistakes as simple as setting enumerations to incorrect
+values or passing null pointers to required parameters are generally not
+explicitly handled and will simply result in crashes or undefined behavior.
+Because Vulkan requires you to be very explicit about everything you're doing,
+it's easy to make many small mistakes like using a new GPU feature and
+forgetting to request it at logical device creation time.
+
+However, that doesn't mean that these checks can't be added to the API. Vulkan
+introduces an elegant system for this known as *validation layers*. Validation
+layers are optional components that hook into Vulkan function calls to apply
+additional operations. Common operations in validation layers are:
+
+* Checking the values of parameters against the specification to detect misuse
+* Tracking creation and destruction of objects to find resource leaks
+* Checking thread safety by tracking the threads that calls originate from
+* Logging every call and its parameters to the standard output
+* Tracing Vulkan calls for profiling and replaying
+
+Here's an example of what the implementation of a function in a diagnostics
+validation layer could look like:
+
+```c++
+VkResult vkCreateInstance(
+    const VkInstanceCreateInfo* pCreateInfo,
+    const VkAllocationCallbacks* pAllocator,
+    VkInstance* instance) {
+
+    if (pCreateInfo == nullptr || instance == nullptr) {
+        log("Null pointer passed to required parameter!");
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
+    return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
+}
+```
+
+These validation layers can be freely stacked to include all the debugging
+functionality that you're interested in. You can simply enable validation layers
+for debug builds and completely disable them for release builds, which gives you
+the best of both worlds!
+
+Vulkan does not come with any validation layers built-in, but the LunarG Vulkan
+SDK provides a nice set of layers that check for common errors. They're also
+completely [open source](https://github.com/KhronosGroup/Vulkan-ValidationLayers),
+so you can check which kind of mistakes they check for and contribute. Using the
+validation layers is the best way to avoid your application breaking on
+different drivers by accidentally relying on undefined behavior.
+
+Validation layers can only be used if they have been installed onto the system.
+For example, the LunarG validation layers are only available on PCs with the
+Vulkan SDK installed.
+
+There were formerly two different types of validation layers in Vulkan: instance
+and device specific. The idea was that instance layers would only check
+calls related to global Vulkan objects like instances, and device specific layers
+would only check calls related to a specific GPU. Device specific layers have now been
+deprecated, which means that instance validation layers apply to all Vulkan
+calls. The specification document still recommends that you enable validation
+layers at device level as well for compatibility, which is required by some
+implementations. We'll simply specify the same layers as the instance at logical
+device level, which we'll see [later on](!en/Drawing_a_triangle/Setup/Logical_device_and_queues).
+
+## Using validation layers
+
+In this section we'll see how to enable the standard diagnostics layers provided
+by the Vulkan SDK. Just like extensions, validation layers need to be enabled by
+specifying their name. All of the useful standard validation is bundled into a layer included in the SDK that is known as `VK_LAYER_KHRONOS_validation`.
+
+Let's first add two configuration variables to the program to specify the layers
+to enable and whether to enable them or not. I've chosen to base that value on
+whether the program is being compiled in debug mode or not. The `NDEBUG` macro
+is part of the C++ standard and means "not debug".
+
+```c++
+const uint32_t WIDTH = 800;
+const uint32_t HEIGHT = 600;
+
+const std::vector<const char*> validationLayers = {
+    "VK_LAYER_KHRONOS_validation"
+};
+
+#ifdef NDEBUG
+    const bool enableValidationLayers = false;
+#else
+    const bool enableValidationLayers = true;
+#endif
+```
+
+We'll add a new function `checkValidationLayerSupport` that checks if all of
+the requested layers are available. First list all of the available layers
+using the `vkEnumerateInstanceLayerProperties` function. Its usage is identical
+to that of `vkEnumerateInstanceExtensionProperties` which was discussed in the
+instance creation chapter.
+
+```c++
+bool checkValidationLayerSupport() {
+    uint32_t layerCount;
+    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
+
+    std::vector<VkLayerProperties> availableLayers(layerCount);
+    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
+
+    return false;
+}
+```
+
+Next, check if all of the layers in `validationLayers` exist in the
+`availableLayers` list. You may need to include `<cstring>` for `strcmp`.
+
+```c++
+for (const char* layerName : validationLayers) {
+    bool layerFound = false;
+
+    for (const auto& layerProperties : availableLayers) {
+        if (strcmp(layerName, layerProperties.layerName) == 0) {
+            layerFound = true;
+            break;
+        }
+    }
+
+    if (!layerFound) {
+        return false;
+    }
+}
+
+return true;
+```
+
+We can now use this function in `createInstance`:
+
+```c++
+void createInstance() {
+    if (enableValidationLayers && !checkValidationLayerSupport()) {
+        throw std::runtime_error("validation layers requested, but not available!");
+    }
+
+    ...
+}
+```
+
+Now run the program in debug mode and ensure that the error does not occur. If
+it does, then have a look at the FAQ.
+
+Finally, modify the `VkInstanceCreateInfo` struct instantiation to include the
+validation layer names if they are enabled:
+
+```c++
+if (enableValidationLayers) {
+    createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+    createInfo.ppEnabledLayerNames = validationLayers.data();
+} else {
+    createInfo.enabledLayerCount = 0;
+}
+```
+
+If the check was successful then `vkCreateInstance` should not ever return a
+`VK_ERROR_LAYER_NOT_PRESENT` error, but you should run the program to make sure.
+
+## Message callback
+
+The validation layers will print debug messages to the standard output by default, but we can also handle them ourselves by providing an explicit callback in our program. This will also allow you to decide which kind of messages you would like to see, because not all are necessarily (fatal) errors. If you don't want to do that right now then you may skip to the last section in this chapter.
+
+To set up a callback in the program to handle messages and the associated details, we have to set up a debug messenger with a callback using the `VK_EXT_debug_utils` extension.
+
+We'll first create a `getRequiredExtensions` function that will return the
+required list of extensions based on whether validation layers are enabled or
+not:
+
+```c++
+std::vector<const char*> getRequiredExtensions() {
+    uint32_t glfwExtensionCount = 0;
+    const char** glfwExtensions;
+    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
+
+    std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
+
+    if (enableValidationLayers) {
+        extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+    }
+
+    return extensions;
+}
+```
+
+The extensions specified by GLFW are always required, but the debug messenger
+extension is conditionally added. Note that I've used the
+`VK_EXT_DEBUG_UTILS_EXTENSION_NAME` macro here which is equal to the literal
+string "VK_EXT_debug_utils". Using this macro lets you avoid typos.
+
+We can now use this function in `createInstance`:
+
+```c++
+auto extensions = getRequiredExtensions();
+createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
+createInfo.ppEnabledExtensionNames = extensions.data();
+```
+
+Run the program to make sure you don't receive a
+`VK_ERROR_EXTENSION_NOT_PRESENT` error. We don't really need to check for the
+existence of this extension, because it should be implied by the availability of
+the validation layers.
+
+Now let's see what a debug callback function looks like. Add a new static member
+function called `debugCallback` with the `PFN_vkDebugUtilsMessengerCallbackEXT`
+prototype. The `VKAPI_ATTR` and `VKAPI_CALL` ensure that the function has the
+right signature for Vulkan to call it.
+
+```c++
+static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
+    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
+    VkDebugUtilsMessageTypeFlagsEXT messageType,
+    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
+    void* pUserData) {
+
+    std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
+
+    return VK_FALSE;
+}
+```
+
+The first parameter specifies the severity of the message, which is one of the following flags:
+
+* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Diagnostic message
+* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Informational message like the creation of a resource
+* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Message about behavior that is not necessarily an error, but very likely a bug in your application
+* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Message about behavior that is invalid and may cause crashes
+
+The values of this enumeration are set up in such a way that you can use a comparison operation to check if a message is equal or worse compared to some level of severity, for example:
+
+```c++
+if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
+    // Message is important enough to show
+}
+```
+
+The `messageType` parameter can have the following values:
+
+* `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT`: Some event has happened that is unrelated to the specification or performance
+* `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT`: Something has happened that violates the specification or indicates a possible mistake
+* `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT`: Potential non-optimal use of Vulkan
+
+The `pCallbackData` parameter refers to a `VkDebugUtilsMessengerCallbackDataEXT` struct containing the details of the message itself, with the most important members being:
+
+* `pMessage`: The debug message as a null-terminated string
+* `pObjects`: Array of Vulkan object handles related to the message
+* `objectCount`: Number of objects in array
+
+Finally, the `pUserData` parameter contains a pointer that was specified during the setup of the callback and allows you to pass your own data to it.
+
+The callback returns a boolean that indicates if the Vulkan call that triggered
+the validation layer message should be aborted. If the callback returns true,
+then the call is aborted with the `VK_ERROR_VALIDATION_FAILED_EXT` error. This
+is normally only used to test the validation layers themselves, so you should
+always return `VK_FALSE`.
+
+All that remains now is telling Vulkan about the callback function. Perhaps
+somewhat surprisingly, even the debug callback in Vulkan is managed with a
+handle that needs to be explicitly created and destroyed. Such a callback is part of a *debug messenger* and you can have as many of them as you want. Add a class member for
+this handle right under `instance`:
+
+```c++
+VkDebugUtilsMessengerEXT debugMessenger;
+```
+
+Now add a function `setupDebugMessenger` to be called from `initVulkan` right
+after `createInstance`:
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+}
+
+void setupDebugMessenger() {
+    if (!enableValidationLayers) return;
+
+}
+```
+
+We'll need to fill in a structure with details about the messenger and its callback:
+
+```c++
+VkDebugUtilsMessengerCreateInfoEXT createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+createInfo.pfnUserCallback = debugCallback;
+createInfo.pUserData = nullptr; // Optional
+```
+
+The `messageSeverity` field allows you to specify all the types of severities you would like your callback to be called for. I've specified all types except for `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT` here to receive notifications about possible problems while leaving out verbose general debug info.
+
+Similarly the `messageType` field lets you filter which types of messages your callback is notified about. I've simply enabled all types here. You can always disable some if they're not useful to you.
+
+Finally, the `pfnUserCallback` field specifies the pointer to the callback function. You can optionally pass a pointer to the `pUserData` field which will be passed along to the callback function via the `pUserData` parameter. You could use this to pass a pointer to the `HelloTriangleApplication` class, for example.
+
+Note that there are many more ways to configure validation layer messages and debug callbacks, but this is a good setup to get started with for this tutorial. See the [extension specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils) for more info about the possibilities.
+
+This struct should be passed to the `vkCreateDebugUtilsMessengerEXT` function to
+create the `VkDebugUtilsMessengerEXT` object. Unfortunately, because this
+function is an extension function, it is not automatically loaded. We have to
+look up its address ourselves using `vkGetInstanceProcAddr`. We're going to
+create our own proxy function that handles this in the background. I've added it
+right above the `HelloTriangleApplication` class definition.
+
+```c++
+VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
+    auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
+    if (func != nullptr) {
+        return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
+    } else {
+        return VK_ERROR_EXTENSION_NOT_PRESENT;
+    }
+}
+```
+
+The `vkGetInstanceProcAddr` function will return `nullptr` if the function
+couldn't be loaded. We can now call this function to create the extension
+object if it's available:
+
+```c++
+if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
+    throw std::runtime_error("failed to set up debug messenger!");
+}
+```
+
+The second to last parameter is again the optional allocator callback that we
+set to `nullptr`, other than that the parameters are fairly straightforward.
+Since the debug messenger is specific to our Vulkan instance and its layers, it
+needs to be explicitly specified as first argument. You will also see this
+pattern with other *child* objects later on.
+
+The `VkDebugUtilsMessengerEXT` object also needs to be cleaned up with a call to
+`vkDestroyDebugUtilsMessengerEXT`. Similarly to `vkCreateDebugUtilsMessengerEXT`
+the function needs to be explicitly loaded.
+
+Create another proxy function right below `CreateDebugUtilsMessengerEXT`:
+
+```c++
+void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
+    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
+    if (func != nullptr) {
+        func(instance, debugMessenger, pAllocator);
+    }
+}
+```
+
+Make sure that this function is either a static class function or a function
+outside the class. We can then call it in the `cleanup` function:
+
+```c++
+void cleanup() {
+    if (enableValidationLayers) {
+        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
+    }
+
+    vkDestroyInstance(instance, nullptr);
+
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+}
+```
+
+## Debugging instance creation and destruction
+
+Although we've now added debugging with validation layers to the program we're not covering everything quite yet. The `vkCreateDebugUtilsMessengerEXT` call requires a valid instance to have been created and `vkDestroyDebugUtilsMessengerEXT` must be called before the instance is destroyed. This currently leaves us unable to debug any issues in the `vkCreateInstance` and `vkDestroyInstance` calls.
+
+However, if you closely read the [extension documentation](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/appendices/VK_EXT_debug_utils.adoc#examples), you'll see that there is a way to create a separate debug utils messenger specifically for those two function calls. It requires you to simply pass a pointer to a `VkDebugUtilsMessengerCreateInfoEXT` struct in the `pNext` extension field of `VkInstanceCreateInfo`. First extract population of the messenger create info into a separate function:
+
+```c++
+void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
+    createInfo = {};
+    createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+    createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+    createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+    createInfo.pfnUserCallback = debugCallback;
+}
+
+...
+
+void setupDebugMessenger() {
+    if (!enableValidationLayers) return;
+
+    VkDebugUtilsMessengerCreateInfoEXT createInfo;
+    populateDebugMessengerCreateInfo(createInfo);
+
+    if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
+        throw std::runtime_error("failed to set up debug messenger!");
+    }
+}
+```
+
+We can now re-use this in the `createInstance` function:
+
+```c++
+void createInstance() {
+    ...
+
+    VkInstanceCreateInfo createInfo{};
+    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+    createInfo.pApplicationInfo = &appInfo;
+
+    ...
+
+    VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
+    if (enableValidationLayers) {
+        createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+        createInfo.ppEnabledLayerNames = validationLayers.data();
+
+        populateDebugMessengerCreateInfo(debugCreateInfo);
+        createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
+    } else {
+        createInfo.enabledLayerCount = 0;
+
+        createInfo.pNext = nullptr;
+    }
+
+    if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create instance!");
+    }
+}
+```
+
+The `debugCreateInfo` variable is placed outside the if statement to ensure that it is not destroyed before the `vkCreateInstance` call. By creating an additional debug messenger this way it will automatically be used during `vkCreateInstance` and `vkDestroyInstance` and cleaned up after that.
+
+## Testing
+
+Now let's intentionally make a mistake to see the validation layers in action. Temporarily remove the call to `DestroyDebugUtilsMessengerEXT` in the `cleanup` function and run your program. Once it exits you should see something like this:
+
+![](/images/validation_layer_test.png)
+
+>If you don't see any messages then [check your installation](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation).
+
+If you want to see which call triggered a message, you can add a breakpoint to the message callback and look at the stack trace.
+
+## Configuration
+
+There are a lot more settings for the behavior of validation layers than just
+the flags specified in the `VkDebugUtilsMessengerCreateInfoEXT` struct. Browse
+to the Vulkan SDK and go to the `Config` directory. There you will find a
+`vk_layer_settings.txt` file that explains how to configure the layers.
+
+To configure the layer settings for your own application, copy the file to the
+`Debug` and `Release` directories of your project and follow the instructions to
+set the desired behavior. However, for the remainder of this tutorial I'll
+assume that you're using the default settings.
+
+Throughout this tutorial I'll be making a couple of intentional mistakes to show
+you how helpful the validation layers are with catching them and to teach you
+how important it is to know exactly what you're doing with Vulkan. Now it's time
+to look at [Vulkan devices in the system](!en/Drawing_a_triangle/Setup/Physical_devices_and_queue_families).
+
+[C++ code](/code/02_validation_layers.cpp)
diff --git a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
new file mode 100644
index 00000000..5761b9bc
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
@@ -0,0 +1,364 @@
+## Selecting a physical device
+
+After initializing the Vulkan library through a VkInstance we need to look for
+and select a graphics card in the system that supports the features we need. In
+fact we can select any number of graphics cards and use them simultaneously, but
+in this tutorial we'll stick to the first graphics card that suits our needs.
+
+We'll add a function `pickPhysicalDevice` and add a call to it in the
+`initVulkan` function.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    pickPhysicalDevice();
+}
+
+void pickPhysicalDevice() {
+
+}
+```
+
+The graphics card that we'll end up selecting will be stored in a
+VkPhysicalDevice handle that is added as a new class member. This object will be
+implicitly destroyed when the VkInstance is destroyed, so we won't need to do
+anything new in the `cleanup` function.
+
+```c++
+VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
+```
+
+Listing the graphics cards is very similar to listing extensions and starts with
+querying just the number.
+
+```c++
+uint32_t deviceCount = 0;
+vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
+```
+
+If there are 0 devices with Vulkan support then there is no point going further.
+
+```c++
+if (deviceCount == 0) {
+    throw std::runtime_error("failed to find GPUs with Vulkan support!");
+}
+```
+
+Otherwise we can now allocate an array to hold all of the VkPhysicalDevice
+handles.
+
+```c++
+std::vector<VkPhysicalDevice> devices(deviceCount);
+vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
+```
+
+Now we need to evaluate each of them and check if they are suitable for the
+operations we want to perform, because not all graphics cards are created equal.
+For that we'll introduce a new function:
+
+```c++
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    return true;
+}
+```
+
+And we'll check if any of the physical devices meet the requirements that we'll
+add to that function.
+
+```c++
+for (const auto& device : devices) {
+    if (isDeviceSuitable(device)) {
+        physicalDevice = device;
+        break;
+    }
+}
+
+if (physicalDevice == VK_NULL_HANDLE) {
+    throw std::runtime_error("failed to find a suitable GPU!");
+}
+```
+
+The next section will introduce the first requirements that we'll check for in
+the `isDeviceSuitable` function. As we'll start using more Vulkan features in
+the later chapters we will also extend this function to include more checks.
+
+## Base device suitability checks
+
+To evaluate the suitability of a device we can start by querying for some
+details. Basic device properties like the name, type and supported Vulkan
+version can be queried using vkGetPhysicalDeviceProperties.
+
+```c++
+VkPhysicalDeviceProperties deviceProperties;
+vkGetPhysicalDeviceProperties(device, &deviceProperties);
+```
+
+The support for optional features like texture compression, 64 bit floats and
+multi viewport rendering (useful for VR) can be queried using
+vkGetPhysicalDeviceFeatures:
+
+```c++
+VkPhysicalDeviceFeatures deviceFeatures;
+vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
+```
+
+There are more details that can be queried from devices that we'll discuss later
+concerning device memory and queue families (see the next section).
+
+As an example, let's say we consider our application only usable for dedicated
+graphics cards that support geometry shaders. Then the `isDeviceSuitable`
+function would look like this:
+
+```c++
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    VkPhysicalDeviceProperties deviceProperties;
+    VkPhysicalDeviceFeatures deviceFeatures;
+    vkGetPhysicalDeviceProperties(device, &deviceProperties);
+    vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
+
+    return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
+           deviceFeatures.geometryShader;
+}
+```
+
+Instead of just checking if a device is suitable or not and going with the first
+one, you could also give each device a score and pick the highest one. That way
+you could favor a dedicated graphics card by giving it a higher score, but fall
+back to an integrated GPU if that's the only available one. You could implement
+something like that as follows:
+
+```c++
+#include <map>
+
+...
+
+void pickPhysicalDevice() {
+    ...
+
+    // Use an ordered map to automatically sort candidates by increasing score
+    std::multimap<int, VkPhysicalDevice> candidates;
+
+    for (const auto& device : devices) {
+        int score = rateDeviceSuitability(device);
+        candidates.insert(std::make_pair(score, device));
+    }
+
+    // Check if the best candidate is suitable at all
+    if (candidates.rbegin()->first > 0) {
+        physicalDevice = candidates.rbegin()->second;
+    } else {
+        throw std::runtime_error("failed to find a suitable GPU!");
+    }
+}
+
+int rateDeviceSuitability(VkPhysicalDevice device) {
+    ...
+
+    int score = 0;
+
+    // Discrete GPUs have a significant performance advantage
+    if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+        score += 1000;
+    }
+
+    // Maximum possible size of textures affects graphics quality
+    score += deviceProperties.limits.maxImageDimension2D;
+
+    // Application can't function without geometry shaders
+    if (!deviceFeatures.geometryShader) {
+        return 0;
+    }
+
+    return score;
+}
+```
+
+You don't need to implement all that for this tutorial, but it's to give you an
+idea of how you could design your device selection process. Of course you can
+also just display the names of the choices and allow the user to select.
+
+Because we're just starting out, Vulkan support is the only thing we need and
+therefore we'll settle for just any GPU:
+
+```c++
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    return true;
+}
+```
+
+In the next section we'll discuss the first real required feature to check for.
+
+## Queue families
+
+It has been briefly touched upon before that almost every operation in Vulkan,
+anything from drawing to uploading textures, requires commands to be submitted
+to a queue. There are different types of queues that originate from different
+*queue families* and each family of queues allows only a subset of commands. For
+example, there could be a queue family that only allows processing of compute
+commands or one that only allows memory transfer related commands.
+
+We need to check which queue families are supported by the device and which one
+of these supports the commands that we want to use. For that purpose we'll add a
+new function `findQueueFamilies` that looks for all the queue families we need.
+
+Right now we are only going to look for a queue that supports graphics commands,
+so the function could look like this:
+
+```c++
+uint32_t findQueueFamilies(VkPhysicalDevice device) {
+    // Logic to find graphics queue family
+}
+```
+
+However, in one of the next chapters we're already going to look for yet another
+queue, so it's better to prepare for that and bundle the indices into a struct:
+
+```c++
+struct QueueFamilyIndices {
+    uint32_t graphicsFamily;
+};
+
+QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
+    QueueFamilyIndices indices;
+    // Logic to find queue family indices to populate struct with
+    return indices;
+}
+```
+
+But what if a queue family is not available? We could throw an exception in
+`findQueueFamilies`, but this function is not really the right place to make
+decisions about device suitability. For example, we may *prefer* devices with a
+dedicated transfer queue family, but not require it. Therefore we need some way
+of indicating whether a particular queue family was found.
+
+It's not really possible to use a magic value to indicate the nonexistence of a
+queue family, since any value of `uint32_t` could in theory be a valid queue
+family index including `0`. Luckily C++17 introduced a data structure to
+distinguish between the case of a value existing or not:
+
+```c++
+#include <optional>
+
+...
+
+std::optional<uint32_t> graphicsFamily;
+
+std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // false
+
+graphicsFamily = 0;
+
+std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true
+```
+
+`std::optional` is a wrapper that contains no value until you assign something
+to it. At any point you can query if it contains a value or not by calling its
+`has_value()` member function. That means that we can change the logic to:
+
+```c++
+#include <optional>
+
+...
+
+struct QueueFamilyIndices {
+    std::optional<uint32_t> graphicsFamily;
+};
+
+QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
+    QueueFamilyIndices indices;
+    // Assign index to queue families that could be found
+    return indices;
+}
+```
+
+We can now begin to actually implement `findQueueFamilies`:
+
+```c++
+QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
+    QueueFamilyIndices indices;
+
+    ...
+
+    return indices;
+}
+```
+
+The process of retrieving the list of queue families is exactly what you expect
+and uses `vkGetPhysicalDeviceQueueFamilyProperties`:
+
+```c++
+uint32_t queueFamilyCount = 0;
+vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
+
+std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
+vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
+```
+
+The VkQueueFamilyProperties struct contains some details about the queue family,
+including the type of operations that are supported and the number of queues
+that can be created based on that family. We need to find at least one queue
+family that supports `VK_QUEUE_GRAPHICS_BIT`.
+
+```c++
+int i = 0;
+for (const auto& queueFamily : queueFamilies) {
+    if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+        indices.graphicsFamily = i;
+    }
+
+    i++;
+}
+```
+
+Now that we have this fancy queue family lookup function, we can use it as a
+check in the `isDeviceSuitable` function to ensure that the device can process
+the commands we want to use:
+
+```c++
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    QueueFamilyIndices indices = findQueueFamilies(device);
+
+    return indices.graphicsFamily.has_value();
+}
+```
+
+To make this a little bit more convenient, we'll also add a generic check to the
+struct itself:
+
+```c++
+struct QueueFamilyIndices {
+    std::optional<uint32_t> graphicsFamily;
+
+    bool isComplete() {
+        return graphicsFamily.has_value();
+    }
+};
+
+...
+
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    QueueFamilyIndices indices = findQueueFamilies(device);
+
+    return indices.isComplete();
+}
+```
+
+We can now also use this for an early exit from `findQueueFamilies`:
+
+```c++
+for (const auto& queueFamily : queueFamilies) {
+    ...
+
+    if (indices.isComplete()) {
+        break;
+    }
+
+    i++;
+}
+```
+
+Great, that's all we need for now to find the right physical device! The next
+step is to [create a logical device](!en/Drawing_a_triangle/Setup/Logical_device_and_queues)
+to interface with it.
+
+[C++ code](/code/03_physical_device_selection.cpp)
diff --git a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
new file mode 100644
index 00000000..f2677d08
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
@@ -0,0 +1,171 @@
+## Introduction
+
+After selecting a physical device to use we need to set up a *logical device* to
+interface with it. The logical device creation process is similar to the
+instance creation process and describes the features we want to use. We also
+need to specify which queues to create now that we've queried which queue
+families are available. You can even create multiple logical devices from the
+same physical device if you have varying requirements.
+
+Start by adding a new class member to store the logical device handle in.
+
+```c++
+VkDevice device;
+```
+
+Next, add a `createLogicalDevice` function that is called from `initVulkan`.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    pickPhysicalDevice();
+    createLogicalDevice();
+}
+
+void createLogicalDevice() {
+
+}
+```
+
+## Specifying the queues to be created
+
+The creation of a logical device involves specifying a bunch of details in
+structs again, of which the first one will be `VkDeviceQueueCreateInfo`. This
+structure describes the number of queues we want for a single queue family.
+Right now we're only interested in a queue with graphics capabilities.
+
+```c++
+QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
+
+VkDeviceQueueCreateInfo queueCreateInfo{};
+queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
+queueCreateInfo.queueCount = 1;
+```
+
+The currently available drivers will only allow you to create a small number of
+queues for each queue family and you don't really need more than one. That's
+because you can create all of the command buffers on multiple threads and then
+submit them all at once on the main thread with a single low-overhead call.
+
+Vulkan lets you assign priorities to queues to influence the scheduling of
+command buffer execution using floating point numbers between `0.0` and `1.0`.
+This is required even if there is only a single queue:
+
+```c++
+float queuePriority = 1.0f;
+queueCreateInfo.pQueuePriorities = &queuePriority;
+```
+
+## Specifying used device features
+
+The next information to specify is the set of device features that we'll be
+using. These are the features that we queried support for with
+`vkGetPhysicalDeviceFeatures` in the previous chapter, like geometry shaders.
+Right now we don't need anything special, so we can simply define it and leave
+everything to `VK_FALSE`. We'll come back to this structure once we're about to
+start doing more interesting things with Vulkan.
+
+```c++
+VkPhysicalDeviceFeatures deviceFeatures{};
+```
+
+## Creating the logical device
+
+With the previous two structures in place, we can start filling in the main
+`VkDeviceCreateInfo` structure.
+
+```c++
+VkDeviceCreateInfo createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+```
+
+First add pointers to the queue creation info and device features structs:
+
+```c++
+createInfo.pQueueCreateInfos = &queueCreateInfo;
+createInfo.queueCreateInfoCount = 1;
+
+createInfo.pEnabledFeatures = &deviceFeatures;
+```
+
+The remainder of the information bears a resemblance to the
+`VkInstanceCreateInfo` struct and requires you to specify extensions and
+validation layers. The difference is that these are device specific this time.
+
+An example of a device specific extension is `VK_KHR_swapchain`, which allows
+you to present rendered images from that device to windows. It is possible that
+there are Vulkan devices in the system that lack this ability, for example
+because they only support compute operations. We will come back to this
+extension in the swap chain chapter.
+
+Previous implementations of Vulkan made a distinction between instance and device specific validation layers, but this is [no longer the case](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). That means that the `enabledLayerCount` and `ppEnabledLayerNames` fields of `VkDeviceCreateInfo` are ignored by up-to-date implementations. However, it is still a good idea to set them anyway to be compatible with older implementations:
+
+```c++
+createInfo.enabledExtensionCount = 0;
+
+if (enableValidationLayers) {
+    createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+    createInfo.ppEnabledLayerNames = validationLayers.data();
+} else {
+    createInfo.enabledLayerCount = 0;
+}
+```
+
+We won't need any device specific extensions for now.
+
+That's it, we're now ready to instantiate the logical device with a call to the
+appropriately named `vkCreateDevice` function.
+
+```c++
+if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create logical device!");
+}
+```
+
+The parameters are the physical device to interface with, the queue and usage
+info we just specified, the optional allocation callbacks pointer and a pointer
+to a variable to store the logical device handle in. Similarly to the instance
+creation function, this call can return errors based on enabling non-existent
+extensions or specifying the desired usage of unsupported features.
+
+The device should be destroyed in `cleanup` with the `vkDestroyDevice` function:
+
+```c++
+void cleanup() {
+    vkDestroyDevice(device, nullptr);
+    ...
+}
+```
+
+Logical devices don't interact directly with instances, which is why it's not
+included as a parameter.
+
+## Retrieving queue handles
+
+The queues are automatically created along with the logical device, but we don't
+have a handle to interface with them yet. First add a class member to store a
+handle to the graphics queue:
+
+```c++
+VkQueue graphicsQueue;
+```
+
+Device queues are implicitly cleaned up when the device is destroyed, so we
+don't need to do anything in `cleanup`.
+
+We can use the `vkGetDeviceQueue` function to retrieve queue handles for each
+queue family. The parameters are the logical device, queue family, queue index
+and a pointer to the variable to store the queue handle in. Because we're only
+creating a single queue from this family, we'll simply use index `0`.
+
+```c++
+vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
+```
+
+With the logical device and queue handles we can now actually start using the
+graphics card to do things! In the next few chapters we'll set up the resources
+to present results to the window system.
+
+[C++ code](/code/04_logical_device.cpp)
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md b/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
new file mode 100644
index 00000000..966a8946
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
@@ -0,0 +1,233 @@
+Since Vulkan is a platform agnostic API, it can not interface directly with the
+window system on its own. To establish the connection between Vulkan and the
+window system to present results to the screen, we need to use the WSI (Window
+System Integration) extensions. In this chapter we'll discuss the first one,
+which is `VK_KHR_surface`. It exposes a `VkSurfaceKHR` object that represents an
+abstract type of surface to present rendered images to. The surface in our
+program will be backed by the window that we've already opened with GLFW.
+
+The `VK_KHR_surface` extension is an instance level extension and we've actually
+already enabled it, because it's included in the list returned by
+`glfwGetRequiredInstanceExtensions`. The list also includes some other WSI
+extensions that we'll use in the next couple of chapters.
+
+The window surface needs to be created right after the instance creation,
+because it can actually influence the physical device selection. The reason we
+postponed this is because window surfaces are part of the larger topic of
+render targets and presentation for which the explanation would have cluttered
+the basic setup. It should also be noted that window surfaces are an entirely
+optional component in Vulkan, if you just need off-screen rendering. Vulkan
+allows you to do that without hacks like creating an invisible window
+(necessary for OpenGL).
+
+## Window surface creation
+
+Start by adding a `surface` class member right below the debug callback.
+
+```c++
+VkSurfaceKHR surface;
+```
+
+Although the `VkSurfaceKHR` object and its usage is platform agnostic, its
+creation isn't because it depends on window system details. For example, it
+needs the `HWND` and `HMODULE` handles on Windows. Therefore there is a
+platform-specific addition to the extension, which on Windows is called
+`VK_KHR_win32_surface` and is also automatically included in the list from
+`glfwGetRequiredInstanceExtensions`.
+
+I will demonstrate how this platform specific extension can be used to create a
+surface on Windows, but we won't actually use it in this tutorial. It doesn't
+make any sense to use a library like GLFW and then proceed to use
+platform-specific code anyway. GLFW actually has `glfwCreateWindowSurface` that
+handles the platform differences for us. Still, it's good to see what it does
+behind the scenes before we start relying on it.
+
+To access native platform functions, you need to update the includes at the top:
+
+```c++
+#define VK_USE_PLATFORM_WIN32_KHR
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+#define GLFW_EXPOSE_NATIVE_WIN32
+#include <GLFW/glfw3native.h>
+```
+
+Because a window surface is a Vulkan object, it comes with a
+`VkWin32SurfaceCreateInfoKHR` struct that needs to be filled in. It has two
+important parameters: `hwnd` and `hinstance`. These are the handles to the
+window and the process.
+
+```c++
+VkWin32SurfaceCreateInfoKHR createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+createInfo.hwnd = glfwGetWin32Window(window);
+createInfo.hinstance = GetModuleHandle(nullptr);
+```
+
+The `glfwGetWin32Window` function is used to get the raw `HWND` from the GLFW
+window object. The `GetModuleHandle` call returns the `HINSTANCE` handle of the
+current process.
+
+After that the surface can be created with `vkCreateWin32SurfaceKHR`, which includes a parameter for the instance, surface creation details, custom allocators and the variable for the surface handle to be stored in. Technically this is a WSI extension function, but it is so commonly used that the standard Vulkan loader includes it, so unlike other extensions you don't need to explicitly load it.
+
+```c++
+if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create window surface!");
+}
+```
+
+The process is similar for other platforms like Linux, where
+`vkCreateXcbSurfaceKHR` takes an XCB connection and window as creation details
+with X11.
+
+The `glfwCreateWindowSurface` function performs exactly this operation with a
+different implementation for each platform. We'll now integrate it into our
+program. Add a function `createSurface` to be called from `initVulkan` right
+after instance creation and `setupDebugMessenger`.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+}
+
+void createSurface() {
+
+}
+```
+
+The GLFW call takes simple parameters instead of a struct which makes the
+implementation of the function very straightforward:
+
+```c++
+void createSurface() {
+    if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create window surface!");
+    }
+}
+```
+
+The parameters are the `VkInstance`, GLFW window pointer, custom allocators and
+pointer to `VkSurfaceKHR` variable. It simply passes through the `VkResult` from
+the relevant platform call. GLFW doesn't offer a special function for destroying
+a surface, but that can easily be done through the original API:
+
+```c++
+void cleanup() {
+        ...
+        vkDestroySurfaceKHR(instance, surface, nullptr);
+        vkDestroyInstance(instance, nullptr);
+        ...
+    }
+```
+
+Make sure that the surface is destroyed before the instance.
+
+## Querying for presentation support
+
+Although the Vulkan implementation may support window system integration, that
+does not mean that every device in the system supports it. Therefore we need to
+extend `isDeviceSuitable` to ensure that a device can present images to the
+surface we created. Since the presentation is a queue-specific feature, the
+problem is actually about finding a queue family that supports presenting to the
+surface we created.
+
+It's actually possible that the queue families supporting drawing commands and
+the ones supporting presentation do not overlap. Therefore we have to take into
+account that there could be a distinct presentation queue by modifying the
+`QueueFamilyIndices` structure:
+
+```c++
+struct QueueFamilyIndices {
+    std::optional<uint32_t> graphicsFamily;
+    std::optional<uint32_t> presentFamily;
+
+    bool isComplete() {
+        return graphicsFamily.has_value() && presentFamily.has_value();
+    }
+};
+```
+
+Next, we'll modify the `findQueueFamilies` function to look for a queue family
+that has the capability of presenting to our window surface. The function to
+check for that is `vkGetPhysicalDeviceSurfaceSupportKHR`, which takes the
+physical device, queue family index and surface as parameters. Add a call to it
+in the same loop as the `VK_QUEUE_GRAPHICS_BIT`:
+
+```c++
+VkBool32 presentSupport = false;
+vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
+```
+
+Then simply check the value of the boolean and store the presentation family
+queue index:
+
+```c++
+if (presentSupport) {
+    indices.presentFamily = i;
+}
+```
+
+Note that it's very likely that these end up being the same queue family after
+all, but throughout the program we will treat them as if they were separate
+queues for a uniform approach. Nevertheless, you could add logic to explicitly
+prefer a physical device that supports drawing and presentation in the same
+queue for improved performance.
+
+## Creating the presentation queue
+
+The one thing that remains is modifying the logical device creation procedure to
+create the presentation queue and retrieve the `VkQueue` handle. Add a member
+variable for the handle:
+
+```c++
+VkQueue presentQueue;
+```
+
+Next, we need to have multiple `VkDeviceQueueCreateInfo` structs to create a
+queue from both families. An elegant way to do that is to create a set of all
+unique queue families that are necessary for the required queues:
+
+```c++
+#include <set>
+
+...
+
+QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
+
+std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
+std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
+
+float queuePriority = 1.0f;
+for (uint32_t queueFamily : uniqueQueueFamilies) {
+    VkDeviceQueueCreateInfo queueCreateInfo{};
+    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+    queueCreateInfo.queueFamilyIndex = queueFamily;
+    queueCreateInfo.queueCount = 1;
+    queueCreateInfo.pQueuePriorities = &queuePriority;
+    queueCreateInfos.push_back(queueCreateInfo);
+}
+```
+
+And modify `VkDeviceCreateInfo` to point to the vector:
+
+```c++
+createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
+createInfo.pQueueCreateInfos = queueCreateInfos.data();
+```
+
+If the queue families are the same, then we only need to pass its index once.
+Finally, add a call to retrieve the queue handle:
+
+```c++
+vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
+```
+
+In case the queue families are the same, the two handles will most likely have
+the same value now. In the next chapter we're going to look at swap chains and
+how they give us the ability to present images to the surface.
+
+[C++ code](/code/05_window_surface.cpp)
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
new file mode 100644
index 00000000..ef1ceabd
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
@@ -0,0 +1,603 @@
+Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own the buffers we will render to before we visualize them on the screen. This infrastructure is
+known as the *swap chain* and must be created explicitly in Vulkan. The swap
+chain is essentially a queue of images that are waiting to be presented to the
+screen. Our application will acquire such an image to draw to it, and then
+return it to the queue. How exactly the queue works and the conditions for
+presenting an image from the queue depend on how the swap chain is set up, but
+the general purpose of the swap chain is to synchronize the presentation of
+images with the refresh rate of the screen.
+
+## Checking for swap chain support
+
+Not all graphics cards are capable of presenting images directly to a screen for
+various reasons, for example because they are designed for servers and don't
+have any display outputs. Secondly, since image presentation is heavily tied
+into the window system and the surfaces associated with windows, it is not
+actually part of the Vulkan core. You have to enable the `VK_KHR_swapchain`
+device extension after querying for its support.
+
+For that purpose we'll first extend the `isDeviceSuitable` function to check if
+this extension is supported. We've previously seen how to list the extensions
+that are supported by a `VkPhysicalDevice`, so doing that should be fairly
+straightforward. Note that the Vulkan header file provides a nice macro
+`VK_KHR_SWAPCHAIN_EXTENSION_NAME` that is defined as `VK_KHR_swapchain`. The
+advantage of using this macro is that the compiler will catch misspellings.
+
+First declare a list of required device extensions, similar to the list of
+validation layers to enable.
+
+```c++
+const std::vector<const char*> deviceExtensions = {
+    VK_KHR_SWAPCHAIN_EXTENSION_NAME
+};
+```
+
+Next, create a new function `checkDeviceExtensionSupport` that is called from
+`isDeviceSuitable` as an additional check:
+
+```c++
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    QueueFamilyIndices indices = findQueueFamilies(device);
+
+    bool extensionsSupported = checkDeviceExtensionSupport(device);
+
+    return indices.isComplete() && extensionsSupported;
+}
+
+bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
+    return true;
+}
+```
+
+Modify the body of the function to enumerate the extensions and check if all of
+the required extensions are amongst them.
+
+```c++
+bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
+    uint32_t extensionCount;
+    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
+
+    std::vector<VkExtensionProperties> availableExtensions(extensionCount);
+    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
+
+    std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
+
+    for (const auto& extension : availableExtensions) {
+        requiredExtensions.erase(extension.extensionName);
+    }
+
+    return requiredExtensions.empty();
+}
+```
+
+I've chosen to use a set of strings here to represent the unconfirmed required
+extensions. That way we can easily tick them off while enumerating the sequence
+of available extensions. Of course you can also use a nested loop like in
+`checkValidationLayerSupport`. The performance difference is irrelevant. Now run
+the code and verify that your graphics card is indeed capable of creating a
+swap chain. It should be noted that the availability of a presentation queue,
+as we checked in the previous chapter, implies that the swap chain extension
+must be supported. However, it's still good to be explicit about things, and
+the extension does have to be explicitly enabled.
+
+## Enabling device extensions
+
+Using a swapchain requires enabling the `VK_KHR_swapchain` extension first.
+Enabling the extension just requires a small change to the logical device
+creation structure:
+
+```c++
+createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
+createInfo.ppEnabledExtensionNames = deviceExtensions.data();
+```
+
+Make sure to replace the existing line `createInfo.enabledExtensionCount = 0;` when you do so.
+
+## Querying details of swap chain support
+
+Just checking if a swap chain is available is not sufficient, because it may not
+actually be compatible with our window surface. Creating a swap chain also
+involves a lot more settings than instance and device creation, so we need to
+query for some more details before we're able to proceed.
+
+There are basically three kinds of properties we need to check:
+
+* Basic surface capabilities (min/max number of images in swap chain, min/max
+width and height of images)
+* Surface formats (pixel format, color space)
+* Available presentation modes
+
+Similar to `findQueueFamilies`, we'll use a struct to pass these details around
+once they've been queried. The three aforementioned types of properties come in
+the form of the following structs and lists of structs:
+
+```c++
+struct SwapChainSupportDetails {
+    VkSurfaceCapabilitiesKHR capabilities;
+    std::vector<VkSurfaceFormatKHR> formats;
+    std::vector<VkPresentModeKHR> presentModes;
+};
+```
+
+We'll now create a new function `querySwapChainSupport` that will populate this
+struct.
+
+```c++
+SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
+    SwapChainSupportDetails details;
+
+    return details;
+}
+```
+
+This section covers how to query the structs that include this information. The
+meaning of these structs and exactly which data they contain is discussed in the
+next section.
+
+Let's start with the basic surface capabilities. These properties are simple to
+query and are returned into a single `VkSurfaceCapabilitiesKHR` struct.
+
+```c++
+vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
+```
+
+This function takes the specified `VkPhysicalDevice` and `VkSurfaceKHR` window
+surface into account when determining the supported capabilities. All of the
+support querying functions have these two as first parameters because they are
+the core components of the swap chain.
+
+The next step is about querying the supported surface formats. Because this is a
+list of structs, it follows the familiar ritual of 2 function calls:
+
+```c++
+uint32_t formatCount;
+vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
+
+if (formatCount != 0) {
+    details.formats.resize(formatCount);
+    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
+}
+```
+
+Make sure that the vector is resized to hold all the available formats. And
+finally, querying the supported presentation modes works exactly the same way
+with `vkGetPhysicalDeviceSurfacePresentModesKHR`:
+
+```c++
+uint32_t presentModeCount;
+vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
+
+if (presentModeCount != 0) {
+    details.presentModes.resize(presentModeCount);
+    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
+}
+```
+
+All of the details are in the struct now, so let's extend `isDeviceSuitable`
+once more to utilize this function to verify that swap chain support is
+adequate. Swap chain support is sufficient for this tutorial if there is at
+least one supported image format and one supported presentation mode given the
+window surface we have.
+
+```c++
+bool swapChainAdequate = false;
+if (extensionsSupported) {
+    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
+    swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
+}
+```
+
+It is important that we only try to query for swap chain support after verifying
+that the extension is available. The last line of the function changes to:
+
+```c++
+return indices.isComplete() && extensionsSupported && swapChainAdequate;
+```
+
+## Choosing the right settings for the swap chain
+
+If the `swapChainAdequate` conditions were met then the support is definitely
+sufficient, but there may still be many different modes of varying optimality.
+We'll now write a couple of functions to find the right settings for the best
+possible swap chain. There are three types of settings to determine:
+
+* Surface format (color depth)
+* Presentation mode (conditions for "swapping" images to the screen)
+* Swap extent (resolution of images in swap chain)
+
+For each of these settings we'll have an ideal value in mind that we'll go with
+if it's available and otherwise we'll create some logic to find the next best
+thing.
+
+### Surface format
+
+The function for this setting starts out like this. We'll later pass the
+`formats` member of the `SwapChainSupportDetails` struct as argument.
+
+```c++
+VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
+
+}
+```
+
+Each `VkSurfaceFormatKHR` entry contains a `format` and a `colorSpace` member. The
+`format` member specifies the color channels and types. For example,
+`VK_FORMAT_B8G8R8A8_SRGB` means that we store the B, G, R and alpha channels in
+that order with an 8 bit unsigned integer for a total of 32 bits per pixel. The
+`colorSpace` member indicates if the SRGB color space is supported or not using
+the `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR` flag. Note that this flag used to be
+called `VK_COLORSPACE_SRGB_NONLINEAR_KHR` in old versions of the specification.
+
+For the color space we'll use SRGB if it is available, because it [results in more accurate perceived colors](http://stackoverflow.com/questions/12524623/). It is also pretty much the standard color space for images, like the textures we'll use later on.
+Because of that we should also use an SRGB color format, of which one of the most common ones is `VK_FORMAT_B8G8R8A8_SRGB`.
+
+Let's go through the list and see if the preferred combination is available:
+
+```c++
+for (const auto& availableFormat : availableFormats) {
+    if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+        return availableFormat;
+    }
+}
+```
+
+If that also fails then we could start ranking the available formats based on
+how "good" they are, but in most cases it's okay to just settle with the first
+format that is specified.
+
+```c++
+VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
+    for (const auto& availableFormat : availableFormats) {
+        if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+            return availableFormat;
+        }
+    }
+
+    return availableFormats[0];
+}
+```
+
+### Presentation mode
+
+The presentation mode is arguably the most important setting for the swap chain,
+because it represents the actual conditions for showing images to the screen.
+There are four possible modes available in Vulkan:
+
+* `VK_PRESENT_MODE_IMMEDIATE_KHR`: Images submitted by your application are
+transferred to the screen right away, which may result in tearing.
+* `VK_PRESENT_MODE_FIFO_KHR`: The swap chain is a queue where the display takes
+an image from the front of the queue when the display is refreshed and the
+program inserts rendered images at the back of the queue. If the queue is full
+then the program has to wait. This is most similar to vertical sync as found in
+modern games. The moment that the display is refreshed is known as "vertical
+blank".
+* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`: This mode only differs from the previous
+one if the application is late and the queue was empty at the last vertical
+blank. Instead of waiting for the next vertical blank, the image is transferred
+right away when it finally arrives. This may result in visible tearing.
+* `VK_PRESENT_MODE_MAILBOX_KHR`: This is another variation of the second mode.
+Instead of blocking the application when the queue is full, the images that are
+already queued are simply replaced with the newer ones. This mode can be used to
+render frames as fast as possible while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked.
+
+Only the `VK_PRESENT_MODE_FIFO_KHR` mode is guaranteed to be available, so we'll
+again have to write a function that looks for the best mode that is available:
+
+```c++
+VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
+    return VK_PRESENT_MODE_FIFO_KHR;
+}
+```
+
+I personally think that `VK_PRESENT_MODE_MAILBOX_KHR` is a very nice trade-off if energy usage is not a concern. It allows us to avoid tearing while still maintaining a fairly low latency by rendering new images that are as up-to-date as possible right until the vertical blank. On mobile devices, where energy usage is more important, you will probably want to use `VK_PRESENT_MODE_FIFO_KHR` instead. Now, let's look through the list to see if `VK_PRESENT_MODE_MAILBOX_KHR` is available:
+
+```c++
+VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
+    for (const auto& availablePresentMode : availablePresentModes) {
+        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
+            return availablePresentMode;
+        }
+    }
+
+    return VK_PRESENT_MODE_FIFO_KHR;
+}
+```
+
+### Swap extent
+
+That leaves only one major property, for which we'll add one last function:
+
+```c++
+VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
+
+}
+```
+
+The swap extent is the resolution of the swap chain images and it's almost
+always exactly equal to the resolution of the window that we're drawing to _in
+pixels_ (more on that in a moment). The range of the possible resolutions is
+defined in the `VkSurfaceCapabilitiesKHR` structure. Vulkan tells us to match
+the resolution of the window by setting the width and height in the
+`currentExtent` member. However, some window managers do allow us to differ here
+and this is indicated by setting the width and height in `currentExtent` to a
+special value: the maximum value of `uint32_t`. In that case we'll pick the
+resolution that best matches the window within the `minImageExtent` and
+`maxImageExtent` bounds. But we must specify the resolution in the correct unit.
+
+GLFW uses two units when measuring sizes: pixels and
+[screen coordinates](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems).
+For example, the resolution `{WIDTH, HEIGHT}` that we specified earlier when
+creating the window is measured in screen coordinates. But Vulkan works with
+pixels, so the swap chain extent must be specified in pixels as well.
+Unfortunately, if you are using a high DPI display (like Apple's Retina
+display), screen coordinates don't correspond to pixels. Instead, due to the
+higher pixel density, the resolution of the window in pixel will be larger than
+the resolution in screen coordinates. So if Vulkan doesn't fix the swap extent
+for us, we can't just use the original `{WIDTH, HEIGHT}`. Instead, we must use
+`glfwGetFramebufferSize` to query the resolution of the window in pixel before
+matching it against the minimum and maximum image extent.
+
+```c++
+#include <cstdint> // Necessary for uint32_t
+#include <limits> // Necessary for std::numeric_limits
+#include <algorithm> // Necessary for std::clamp
+
+...
+
+VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
+    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
+        return capabilities.currentExtent;
+    } else {
+        int width, height;
+        glfwGetFramebufferSize(window, &width, &height);
+
+        VkExtent2D actualExtent = {
+            static_cast<uint32_t>(width),
+            static_cast<uint32_t>(height)
+        };
+
+        actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
+        actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
+
+        return actualExtent;
+    }
+}
+```
+
+The `clamp` function is used here to bound the values of `width` and `height` between the allowed minimum and maximum extents that are supported by the implementation.
+
+## Creating the swap chain
+
+Now that we have all of these helper functions assisting us with the choices we
+have to make at runtime, we finally have all the information that is needed to
+create a working swap chain.
+
+Create a `createSwapChain` function that starts out with the results of these
+calls and make sure to call it from `initVulkan` after logical device creation.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+}
+
+void createSwapChain() {
+    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
+
+    VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
+    VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
+    VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
+}
+```
+
+Aside from these properties we also have to decide how many images we would like to have in the swap chain. The implementation specifies the minimum number that it requires to function:
+
+```c++
+uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
+```
+
+However, simply sticking to this minimum means that we may sometimes have to wait on the driver to complete internal operations before we can acquire another image to render to. Therefore it is recommended to request at least one more image than the minimum:
+
+```c++
+uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
+```
+
+We should also make sure to not exceed the maximum number of images while doing this, where `0` is a special value that means that there is no maximum:
+
+```c++
+if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
+    imageCount = swapChainSupport.capabilities.maxImageCount;
+}
+```
+
+As is tradition with Vulkan objects, creating the swap chain object requires
+filling in a large structure. It starts out very familiarly:
+
+```c++
+VkSwapchainCreateInfoKHR createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+createInfo.surface = surface;
+```
+
+After specifying which surface the swap chain should be tied to, the details of
+the swap chain images are specified:
+
+```c++
+createInfo.minImageCount = imageCount;
+createInfo.imageFormat = surfaceFormat.format;
+createInfo.imageColorSpace = surfaceFormat.colorSpace;
+createInfo.imageExtent = extent;
+createInfo.imageArrayLayers = 1;
+createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+```
+
+The `imageArrayLayers` specifies the amount of layers each image consists of.
+This is always `1` unless you are developing a stereoscopic 3D application. The
+`imageUsage` bit field specifies what kind of operations we'll use the images in
+the swap chain for. In this tutorial we're going to render directly to them,
+which means that they're used as color attachment. It is also possible that
+you'll render images to a separate image first to perform operations like
+post-processing. In that case you may use a value like
+`VK_IMAGE_USAGE_TRANSFER_DST_BIT` instead and use a memory operation to transfer
+the rendered image to a swap chain image.
+
+```c++
+QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
+uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};
+
+if (indices.graphicsFamily != indices.presentFamily) {
+    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+    createInfo.queueFamilyIndexCount = 2;
+    createInfo.pQueueFamilyIndices = queueFamilyIndices;
+} else {
+    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    createInfo.queueFamilyIndexCount = 0; // Optional
+    createInfo.pQueueFamilyIndices = nullptr; // Optional
+}
+```
+
+Next, we need to specify how to handle swap chain images that will be used
+across multiple queue families. That will be the case in our application if the
+graphics queue family is different from the presentation queue. We'll be drawing
+on the images in the swap chain from the graphics queue and then submitting them
+on the presentation queue. There are two ways to handle images that are
+accessed from multiple queues:
+
+* `VK_SHARING_MODE_EXCLUSIVE`: An image is owned by one queue family at a time
+and ownership must be explicitly transferred before using it in another queue
+family. This option offers the best performance.
+* `VK_SHARING_MODE_CONCURRENT`: Images can be used across multiple queue
+families without explicit ownership transfers.
+
+If the queue families differ, then we'll be using the concurrent mode in this
+tutorial to avoid having to do the ownership chapters, because these involve
+some concepts that are better explained at a later time. Concurrent mode
+requires you to specify in advance between which queue families ownership will
+be shared using the `queueFamilyIndexCount` and `pQueueFamilyIndices`
+parameters. If the graphics queue family and presentation queue family are the
+same, which will be the case on most hardware, then we should stick to exclusive
+mode, because concurrent mode requires you to specify at least two distinct
+queue families.
+
+```c++
+createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
+```
+
+We can specify that a certain transform should be applied to images in the swap
+chain if it is supported (`supportedTransforms` in `capabilities`), like a 90
+degree clockwise rotation or horizontal flip. To specify that you do not want
+any transformation, simply specify the current transformation.
+
+```c++
+createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+```
+
+The `compositeAlpha` field specifies if the alpha channel should be used for
+blending with other windows in the window system. You'll almost always want to
+simply ignore the alpha channel, hence `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`.
+
+```c++
+createInfo.presentMode = presentMode;
+createInfo.clipped = VK_TRUE;
+```
+
+The `presentMode` member speaks for itself. If the `clipped` member is set to
+`VK_TRUE` then that means that we don't care about the color of pixels that are
+obscured, for example because another window is in front of them. Unless you
+really need to be able to read these pixels back and get predictable results,
+you'll get the best performance by enabling clipping.
+
+```c++
+createInfo.oldSwapchain = VK_NULL_HANDLE;
+```
+
+That leaves one last field, `oldSwapChain`. With Vulkan it's possible that your swap chain becomes invalid or unoptimized while your application is
+running, for example because the window was resized. In that case the swap chain
+actually needs to be recreated from scratch and a reference to the old one must
+be specified in this field. This is a complex topic that we'll learn more about
+in [a future chapter](!en/Drawing_a_triangle/Swap_chain_recreation). For now we'll
+assume that we'll only ever create one swap chain.
+
+Now add a class member to store the `VkSwapchainKHR` object:
+
+```c++
+VkSwapchainKHR swapChain;
+```
+
+Creating the swap chain is now as simple as calling `vkCreateSwapchainKHR`:
+
+```c++
+if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create swap chain!");
+}
+```
+
+The parameters are the logical device, swap chain creation info, optional custom
+allocators and a pointer to the variable to store the handle in. No surprises
+there. It should be cleaned up using `vkDestroySwapchainKHR` before the device:
+
+```c++
+void cleanup() {
+    vkDestroySwapchainKHR(device, swapChain, nullptr);
+    ...
+}
+```
+
+Now run the application to ensure that the swap chain is created successfully! If at this point you get an access violation error in `vkCreateSwapchainKHR` or see a message like `Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll`, then see the [FAQ entry](!en/FAQ) about the Steam overlay layer.
+
+Try removing the `createInfo.imageExtent = extent;` line with validation layers
+enabled. You'll see that one of the validation layers immediately catches the
+mistake and a helpful message is printed:
+
+![](/images/swap_chain_validation_layer.png)
+
+## Retrieving the swap chain images
+
+The swap chain has been created now, so all that remains is retrieving the
+handles of the `VkImage`s in it. We'll reference these during rendering
+operations in later chapters. Add a class member to store the handles:
+
+```c++
+std::vector<VkImage> swapChainImages;
+```
+
+The images were created by the implementation for the swap chain and they will
+be automatically cleaned up once the swap chain has been destroyed, therefore we
+don't need to add any cleanup code.
+
+I'm adding the code to retrieve the handles to the end of the `createSwapChain`
+function, right after the `vkCreateSwapchainKHR` call. Retrieving them is very
+similar to the other times where we retrieved an array of objects from Vulkan. Remember that we only specified a minimum number of images in the swap chain, so the implementation is allowed to create a swap chain with more. That's why we'll first query the final number of images with `vkGetSwapchainImagesKHR`, then resize the container and finally call it again
+to retrieve the handles.
+
+```c++
+vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
+swapChainImages.resize(imageCount);
+vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
+```
+
+One last thing, store the format and extent we've chosen for the swap chain
+images in member variables. We'll need them in future chapters.
+
+```c++
+VkSwapchainKHR swapChain;
+std::vector<VkImage> swapChainImages;
+VkFormat swapChainImageFormat;
+VkExtent2D swapChainExtent;
+
+...
+
+swapChainImageFormat = surfaceFormat.format;
+swapChainExtent = extent;
+```
+
+We now have a set of images that can be drawn onto and can be presented to the
+window. The next chapter will begin to cover how we can set up the images as
+render targets and then we start looking into the actual graphics pipeline and
+drawing commands!
+
+[C++ code](/code/06_swap_chain_creation.cpp)
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md b/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
new file mode 100644
index 00000000..5988468a
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
@@ -0,0 +1,127 @@
+To use any `VkImage`, including those in the swap chain, in the render pipeline
+we have to create a `VkImageView` object. An image view is quite literally a
+view into an image. It describes how to access the image and which part of the
+image to access, for example if it should be treated as a 2D texture depth
+texture without any mipmapping levels.
+
+In this chapter we'll write a `createImageViews` function that creates a basic
+image view for every image in the swap chain so that we can use them as color
+targets later on.
+
+First add a class member to store the image views in:
+
+```c++
+std::vector<VkImageView> swapChainImageViews;
+```
+
+Create the `createImageViews` function and call it right after swap chain
+creation.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+}
+
+void createImageViews() {
+
+}
+```
+
+The first thing we need to do is resize the list to fit all of the image views
+we'll be creating:
+
+```c++
+void createImageViews() {
+    swapChainImageViews.resize(swapChainImages.size());
+
+}
+```
+
+Next, set up the loop that iterates over all of the swap chain images.
+
+```c++
+for (size_t i = 0; i < swapChainImages.size(); i++) {
+
+}
+```
+
+The parameters for image view creation are specified in a
+`VkImageViewCreateInfo` structure. The first few parameters are straightforward.
+
+```c++
+VkImageViewCreateInfo createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+createInfo.image = swapChainImages[i];
+```
+
+The `viewType` and `format` fields specify how the image data should be
+interpreted. The `viewType` parameter allows you to treat images as 1D textures,
+2D textures, 3D textures and cube maps.
+
+```c++
+createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+createInfo.format = swapChainImageFormat;
+```
+
+The `components` field allows you to swizzle the color channels around. For
+example, you can map all of the channels to the red channel for a monochrome
+texture. You can also map constant values of `0` and `1` to a channel. In our
+case we'll stick to the default mapping.
+
+```c++
+createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+```
+
+The `subresourceRange` field describes what the image's purpose is and which
+part of the image should be accessed. Our images will be used as color targets
+without any mipmapping levels or multiple layers.
+
+```c++
+createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+createInfo.subresourceRange.baseMipLevel = 0;
+createInfo.subresourceRange.levelCount = 1;
+createInfo.subresourceRange.baseArrayLayer = 0;
+createInfo.subresourceRange.layerCount = 1;
+```
+
+If you were working on a stereographic 3D application, then you would create a
+swap chain with multiple layers. You could then create multiple image views for
+each image representing the views for the left and right eyes by accessing
+different layers.
+
+Creating the image view is now a matter of calling `vkCreateImageView`:
+
+```c++
+if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create image views!");
+}
+```
+
+Unlike images, the image views were explicitly created by us, so we need to add
+a similar loop to destroy them again at the end of the program:
+
+```c++
+void cleanup() {
+    for (auto imageView : swapChainImageViews) {
+        vkDestroyImageView(device, imageView, nullptr);
+    }
+
+    ...
+}
+```
+
+An image view is sufficient to start using an image as a texture, but it's not
+quite ready to be used as a render target just yet. That requires one more step
+of indirection, known as a framebuffer. But first we'll have to set up the
+graphics pipeline.
+
+[C++ code](/code/07_image_views.cpp)
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
new file mode 100644
index 00000000..9ee7f739
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
@@ -0,0 +1,99 @@
+Over the course of the next few chapters we'll be setting up a graphics pipeline
+that is configured to draw our first triangle. The graphics pipeline is the
+sequence of operations that take the vertices and textures of your meshes all
+the way to the pixels in the render targets. A simplified overview is displayed
+below:
+
+![](/images/vulkan_simplified_pipeline.svg)
+
+The *input assembler* collects the raw vertex data from the buffers you specify
+and may also use an index buffer to repeat certain elements without having to
+duplicate the vertex data itself.
+
+The *vertex shader* is run for every vertex and generally applies
+transformations to turn vertex positions from model space to screen space. It
+also passes per-vertex data down the pipeline.
+
+The *tessellation shaders* allow you to subdivide geometry based on certain
+rules to increase the mesh quality. This is often used to make surfaces like
+brick walls and staircases look less flat when they are nearby.
+
+The *geometry shader* is run on every primitive (triangle, line, point) and can
+discard it or output more primitives than came in. This is similar to the
+tessellation shader, but much more flexible. However, it is not used much in
+today's applications because the performance is not that good on most graphics
+cards except for Intel's integrated GPUs.
+
+The *rasterization* stage discretizes the primitives into *fragments*. These are
+the pixel elements that they fill on the framebuffer. Any fragments that fall
+outside the screen are discarded and the attributes outputted by the vertex
+shader are interpolated across the fragments, as shown in the figure. Usually
+the fragments that are behind other primitive fragments are also discarded here
+because of depth testing.
+
+The *fragment shader* is invoked for every fragment that survives and determines
+which framebuffer(s) the fragments are written to and with which color and depth
+values. It can do this using the interpolated data from the vertex shader, which
+can include things like texture coordinates and normals for lighting.
+
+The *color blending* stage applies operations to mix different fragments that
+map to the same pixel in the framebuffer. Fragments can simply overwrite each
+other, add up or be mixed based upon transparency.
+
+Stages with a green color are known as *fixed-function* stages. These stages
+allow you to tweak their operations using parameters, but the way they work is
+predefined.
+
+Stages with an orange color on the other hand are `programmable`, which means
+that you can upload your own code to the graphics card to apply exactly the
+operations you want. This allows you to use fragment shaders, for example, to
+implement anything from texturing and lighting to ray tracers. These programs
+run on many GPU cores simultaneously to process many objects, like vertices and
+fragments in parallel.
+
+If you've used older APIs like OpenGL and Direct3D before, then you'll be used
+to being able to change any pipeline settings at will with calls like
+`glBlendFunc` and `OMSetBlendState`. The graphics pipeline in Vulkan is almost
+completely immutable, so you must recreate the pipeline from scratch if you want
+to change shaders, bind different framebuffers or change the blend function. The
+disadvantage is that you'll have to create a number of pipelines that represent
+all of the different combinations of states you want to use in your rendering
+operations. However, because all of the operations you'll be doing in the
+pipeline are known in advance, the driver can optimize for it much better.
+
+Some of the programmable stages are optional based on what you intend to do. For
+example, the tessellation and geometry stages can be disabled if you are just
+drawing simple geometry. If you are only interested in depth values then you can
+disable the fragment shader stage, which is useful for [shadow map](https://en.wikipedia.org/wiki/Shadow_mapping)
+generation.
+
+In the next chapter we'll first create the two programmable stages required to
+put a triangle onto the screen: the vertex shader and fragment shader. The
+fixed-function configuration like blending mode, viewport, rasterization will be
+set up in the chapter after that. The final part of setting up the graphics
+pipeline in Vulkan involves the specification of input and output framebuffers.
+
+Create a `createGraphicsPipeline` function that is called right after
+`createImageViews` in `initVulkan`. We'll work on this function throughout the
+following chapters.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createGraphicsPipeline();
+}
+
+...
+
+void createGraphicsPipeline() {
+
+}
+```
+
+[C++ code](/code/08_graphics_pipeline.cpp)
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
new file mode 100644
index 00000000..20aee6b5
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
@@ -0,0 +1,467 @@
+Unlike earlier APIs, shader code in Vulkan has to be specified in a bytecode
+format as opposed to human-readable syntax like [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)
+and [HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language). This
+bytecode format is called [SPIR-V](https://www.khronos.org/spir) and is designed
+to be used with both Vulkan and OpenCL (both Khronos APIs). It is a format that
+can be used to write graphics and compute shaders, but we will focus on shaders
+used in Vulkan's graphics pipelines in this tutorial.
+
+The advantage of using a bytecode format is that the compilers written by GPU
+vendors to turn shader code into native code are significantly less complex. The
+past has shown that with human-readable syntax like GLSL, some GPU vendors were
+rather flexible with their interpretation of the standard. If you happen to
+write non-trivial shaders with a GPU from one of these vendors, then you'd risk
+other vendor's drivers rejecting your code due to syntax errors, or worse, your
+shader running differently because of compiler bugs. With a straightforward
+bytecode format like SPIR-V that will hopefully be avoided.
+
+However, that does not mean that we need to write this bytecode by hand. Khronos
+has released their own vendor-independent compiler that compiles GLSL to SPIR-V.
+This compiler is designed to verify that your shader code is fully standards
+compliant and produces one SPIR-V binary that you can ship with your program.
+You can also include this compiler as a library to produce SPIR-V at runtime,
+but we won't be doing that in this tutorial. Although we can use this compiler directly via `glslangValidator.exe`, we will be using `glslc.exe` by Google instead. The advantage of `glslc` is that it uses the same parameter format as well-known compilers like GCC and Clang and includes some extra functionality like *includes*. Both of them are already included in the Vulkan SDK, so you don't need to download anything extra.
+
+GLSL is a shading language with a C-style syntax. Programs written in it have a
+`main` function that is invoked for every object. Instead of using parameters
+for input and a return value as output, GLSL uses global variables to handle
+input and output. The language includes many features to aid in graphics
+programming, like built-in vector and matrix primitives. Functions for
+operations like cross products, matrix-vector products and reflections around a
+vector are included. The vector type is called `vec` with a number indicating
+the amount of elements. For example, a 3D position would be stored in a `vec3`.
+It is possible to access single components through members like `.x`, but it's
+also possible to create a new vector from multiple components at the same time.
+For example, the expression `vec3(1.0, 2.0, 3.0).xy` would result in `vec2`. The
+constructors of vectors can also take combinations of vector objects and scalar
+values. For example, a `vec3` can be constructed with
+`vec3(vec2(1.0, 2.0), 3.0)`.
+
+As the previous chapter mentioned, we need to write a vertex shader and a
+fragment shader to get a triangle on the screen. The next two sections will
+cover the GLSL code of each of those and after that I'll show you how to produce
+two SPIR-V binaries and load them into the program.
+
+## Vertex shader
+
+The vertex shader processes each incoming vertex. It takes its attributes, like
+world position, color, normal and texture coordinates as input. The output is
+the final position in clip coordinates and the attributes that need to be passed
+on to the fragment shader, like color and texture coordinates. These values will
+then be interpolated over the fragments by the rasterizer to produce a smooth
+gradient.
+
+A *clip coordinate* is a four dimensional vector from the vertex shader that is
+subsequently turned into a *normalized device coordinate* by dividing the whole
+vector by its last component. These normalized device coordinates are
+[homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates)
+that map the framebuffer to a [-1, 1] by [-1, 1] coordinate system that looks
+like the following:
+
+![](/images/normalized_device_coordinates.svg)
+
+You should already be familiar with these if you have dabbled in computer
+graphics before. If you have used OpenGL before, then you'll notice that the
+sign of the Y coordinates is now flipped. The Z coordinate now uses the same
+range as it does in Direct3D, from 0 to 1.
+
+For our first triangle we won't be applying any transformations, we'll just
+specify the positions of the three vertices directly as normalized device
+coordinates to create the following shape:
+
+![](/images/triangle_coordinates.svg)
+
+We can directly output normalized device coordinates by outputting them as clip
+coordinates from the vertex shader with the last component set to `1`. That way
+the division to transform clip coordinates to normalized device coordinates will
+not change anything.
+
+Normally these coordinates would be stored in a vertex buffer, but creating a
+vertex buffer in Vulkan and filling it with data is not trivial. Therefore I've
+decided to postpone that until after we've had the satisfaction of seeing a
+triangle pop up on the screen. We're going to do something a little unorthodox
+in the meanwhile: include the coordinates directly inside the vertex shader. The
+code looks like this:
+
+```glsl
+#version 450
+
+vec2 positions[3] = vec2[](
+    vec2(0.0, -0.5),
+    vec2(0.5, 0.5),
+    vec2(-0.5, 0.5)
+);
+
+void main() {
+    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+}
+```
+
+The `main` function is invoked for every vertex. The built-in `gl_VertexIndex`
+variable contains the index of the current vertex. This is usually an index into
+the vertex buffer, but in our case it will be an index into a hardcoded array
+of vertex data. The position of each vertex is accessed from the constant array
+in the shader and combined with dummy `z` and `w` components to produce a
+position in clip coordinates. The built-in variable `gl_Position` functions as
+the output.
+
+## Fragment shader
+
+The triangle that is formed by the positions from the vertex shader fills an
+area on the screen with fragments. The fragment shader is invoked on these
+fragments to produce a color and depth for the framebuffer (or framebuffers). A
+simple fragment shader that outputs the color red for the entire triangle looks
+like this:
+
+```glsl
+#version 450
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+    outColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
+```
+
+The `main` function is called for every fragment just like the vertex shader
+`main` function is called for every vertex. Colors in GLSL are 4-component
+vectors with the R, G, B and alpha channels within the [0, 1] range. Unlike
+`gl_Position` in the vertex shader, there is no built-in variable to output a
+color for the current fragment. You have to specify your own output variable for
+each framebuffer where the `layout(location = 0)` modifier specifies the index
+of the framebuffer. The color red is written to this `outColor` variable that is
+linked to the first (and only) framebuffer at index `0`.
+
+## Per-vertex colors
+
+Making the entire triangle red is not very interesting, wouldn't something like
+the following look a lot nicer?
+
+![](/images/triangle_coordinates_colors.png)
+
+We have to make a couple of changes to both shaders to accomplish this. First
+off, we need to specify a distinct color for each of the three vertices. The
+vertex shader should now include an array with colors just like it does for
+positions:
+
+```glsl
+vec3 colors[3] = vec3[](
+    vec3(1.0, 0.0, 0.0),
+    vec3(0.0, 1.0, 0.0),
+    vec3(0.0, 0.0, 1.0)
+);
+```
+
+Now we just need to pass these per-vertex colors to the fragment shader so it
+can output their interpolated values to the framebuffer. Add an output for color
+to the vertex shader and write to it in the `main` function:
+
+```glsl
+layout(location = 0) out vec3 fragColor;
+
+void main() {
+    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+    fragColor = colors[gl_VertexIndex];
+}
+```
+
+Next, we need to add a matching input in the fragment shader:
+
+```glsl
+layout(location = 0) in vec3 fragColor;
+
+void main() {
+    outColor = vec4(fragColor, 1.0);
+}
+```
+
+The input variable does not necessarily have to use the same name, they will be
+linked together using the indexes specified by the `location` directives. The
+`main` function has been modified to output the color along with an alpha value.
+As shown in the image above, the values for `fragColor` will be automatically
+interpolated for the fragments between the three vertices, resulting in a smooth
+gradient.
+
+## Compiling the shaders
+
+Create a directory called `shaders` in the root directory of your project and
+store the vertex shader in a file called `shader.vert` and the fragment shader
+in a file called `shader.frag` in that directory. GLSL shaders don't have an
+official extension, but these two are commonly used to distinguish them.
+
+The contents of `shader.vert` should be:
+
+```glsl
+#version 450
+
+layout(location = 0) out vec3 fragColor;
+
+vec2 positions[3] = vec2[](
+    vec2(0.0, -0.5),
+    vec2(0.5, 0.5),
+    vec2(-0.5, 0.5)
+);
+
+vec3 colors[3] = vec3[](
+    vec3(1.0, 0.0, 0.0),
+    vec3(0.0, 1.0, 0.0),
+    vec3(0.0, 0.0, 1.0)
+);
+
+void main() {
+    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+    fragColor = colors[gl_VertexIndex];
+}
+```
+
+And the contents of `shader.frag` should be:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+    outColor = vec4(fragColor, 1.0);
+}
+```
+
+We're now going to compile these into SPIR-V bytecode using the
+`glslc` program.
+
+**Windows**
+
+Create a `compile.bat` file with the following contents:
+
+```bash
+C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.vert -o vert.spv
+C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.frag -o frag.spv
+pause
+```
+
+Replace the path to `glslc.exe` with the path to where you installed
+the Vulkan SDK. Double click the file to run it.
+
+**Linux**
+
+Create a `compile.sh` file with the following contents:
+
+```bash
+/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv
+/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv
+```
+
+Replace the path to `glslc` with the path to where you installed the
+Vulkan SDK. Make the script executable with `chmod +x compile.sh` and run it.
+
+**End of platform-specific instructions**
+
+These two commands tell the compiler to read the GLSL source file and output a SPIR-V bytecode file using the `-o` (output) flag.
+
+If your shader contains a syntax error then the compiler will tell you the line
+number and problem, as you would expect. Try leaving out a semicolon for example
+and run the compile script again. Also try running the compiler without any
+arguments to see what kinds of flags it supports. It can, for example, also
+output the bytecode into a human-readable format so you can see exactly what
+your shader is doing and any optimizations that have been applied at this stage.
+
+Compiling shaders on the commandline is one of the most straightforward options and it's the one that we'll use in this tutorial, but it's also possible to compile shaders directly from your own code. The Vulkan SDK includes [libshaderc](https://github.com/google/shaderc), which is a library to compile GLSL code to SPIR-V from within your program.
+
+## Loading a shader
+
+Now that we have a way of producing SPIR-V shaders, it's time to load them into
+our program to plug them into the graphics pipeline at some point. We'll first
+write a simple helper function to load the binary data from the files.
+
+```c++
+#include <fstream>
+
+...
+
+static std::vector<char> readFile(const std::string& filename) {
+    std::ifstream file(filename, std::ios::ate | std::ios::binary);
+
+    if (!file.is_open()) {
+        throw std::runtime_error("failed to open file!");
+    }
+}
+```
+
+The `readFile` function will read all of the bytes from the specified file and
+return them in a byte array managed by `std::vector`. We start by opening the
+file with two flags:
+
+* `ate`: Start reading at the end of the file
+* `binary`: Read the file as binary file (avoid text transformations)
+
+The advantage of starting to read at the end of the file is that we can use the
+read position to determine the size of the file and allocate a buffer:
+
+```c++
+size_t fileSize = (size_t) file.tellg();
+std::vector<char> buffer(fileSize);
+```
+
+After that, we can seek back to the beginning of the file and read all of the
+bytes at once:
+
+```c++
+file.seekg(0);
+file.read(buffer.data(), fileSize);
+```
+
+And finally close the file and return the bytes:
+
+```c++
+file.close();
+
+return buffer;
+```
+
+We'll now call this function from `createGraphicsPipeline` to load the bytecode
+of the two shaders:
+
+```c++
+void createGraphicsPipeline() {
+    auto vertShaderCode = readFile("shaders/vert.spv");
+    auto fragShaderCode = readFile("shaders/frag.spv");
+}
+```
+
+Make sure that the shaders are loaded correctly by printing the size of the
+buffers and checking if they match the actual file size in bytes. Note that the code doesn't need to be null terminated since it's binary code and we will later be explicit about its size.
+
+## Creating shader modules
+
+Before we can pass the code to the pipeline, we have to wrap it in a
+`VkShaderModule` object. Let's create a helper function `createShaderModule` to
+do that.
+
+```c++
+VkShaderModule createShaderModule(const std::vector<char>& code) {
+
+}
+```
+
+The function will take a buffer with the bytecode as parameter and create a
+`VkShaderModule` from it.
+
+Creating a shader module is simple, we only need to specify a pointer to the
+buffer with the bytecode and the length of it. This information is specified in
+a `VkShaderModuleCreateInfo` structure. The one catch is that the size of the
+bytecode is specified in bytes, but the bytecode pointer is a `uint32_t` pointer
+rather than a `char` pointer. Therefore we will need to cast the pointer with
+`reinterpret_cast` as shown below. When you perform a cast like this, you also
+need to ensure that the data satisfies the alignment requirements of `uint32_t`.
+Lucky for us, the data is stored in an `std::vector` where the default allocator
+already ensures that the data satisfies the worst case alignment requirements.
+
+```c++
+VkShaderModuleCreateInfo createInfo{};
+createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+createInfo.codeSize = code.size();
+createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
+```
+
+The `VkShaderModule` can then be created with a call to `vkCreateShaderModule`:
+
+```c++
+VkShaderModule shaderModule;
+if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create shader module!");
+}
+```
+
+The parameters are the same as those in previous object creation functions: the
+logical device, pointer to create info structure, optional pointer to custom
+allocators and handle output variable. The buffer with the code can be freed
+immediately after creating the shader module. Don't forget to return the created
+shader module:
+
+```c++
+return shaderModule;
+```
+
+Shader modules are just a thin wrapper around the shader bytecode that we've previously loaded from a file and the functions defined in it. The compilation and linking of the SPIR-V bytecode to machine code for execution by the GPU doesn't happen until the graphics pipeline is created. That means that we're allowed to destroy the shader modules again as soon as pipeline creation is finished, which is why we'll make them local variables in the `createGraphicsPipeline` function instead of class members:
+
+```c++
+void createGraphicsPipeline() {
+    auto vertShaderCode = readFile("shaders/vert.spv");
+    auto fragShaderCode = readFile("shaders/frag.spv");
+
+    VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
+    VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
+```
+
+The cleanup should then happen at the end of the function by adding two calls to `vkDestroyShaderModule`. All of the remaining code in this chapter will be inserted before these lines.
+
+```c++
+    ...
+    vkDestroyShaderModule(device, fragShaderModule, nullptr);
+    vkDestroyShaderModule(device, vertShaderModule, nullptr);
+}
+```
+
+## Shader stage creation
+
+To actually use the shaders we'll need to assign them to a specific pipeline stage through `VkPipelineShaderStageCreateInfo` structures as part of the actual pipeline creation process.
+
+We'll start by filling in the structure for the vertex shader, again in the
+`createGraphicsPipeline` function.
+
+```c++
+VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
+vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
+```
+
+The first step, besides the obligatory `sType` member, is telling Vulkan in
+which pipeline stage the shader is going to be used. There is an enum value for
+each of the programmable stages described in the previous chapter.
+
+```c++
+vertShaderStageInfo.module = vertShaderModule;
+vertShaderStageInfo.pName = "main";
+```
+
+The next two members specify the shader module containing the code, and the
+function to invoke, known as the *entrypoint*. That means that it's possible to combine multiple fragment
+shaders into a single shader module and use different entry points to
+differentiate between their behaviors. In this case we'll stick to the standard
+`main`, however.
+
+There is one more (optional) member, `pSpecializationInfo`, which we won't be
+using here, but is worth discussing. It allows you to specify values for shader
+constants. You can use a single shader module where its behavior can be
+configured at pipeline creation by specifying different values for the constants
+used in it. This is more efficient than configuring the shader using variables
+at render time, because the compiler can do optimizations like eliminating `if`
+statements that depend on these values. If you don't have any constants like
+that, then you can set the member to `nullptr`, which our struct initialization
+does automatically.
+
+Modifying the structure to suit the fragment shader is easy:
+
+```c++
+VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
+fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+fragShaderStageInfo.module = fragShaderModule;
+fragShaderStageInfo.pName = "main";
+```
+
+Finish by defining an array that contains these two structs, which we'll later
+use to reference them in the actual pipeline creation step.
+
+```c++
+VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
+```
+
+That's all there is to describing the programmable stages of the pipeline. In
+the next chapter we'll look at the fixed-function stages.
+
+[C++ code](/code/09_shader_modules.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
new file mode 100644
index 00000000..a80f72ae
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
@@ -0,0 +1,439 @@
+
+The older graphics APIs provided default state for most of the stages of the
+graphics pipeline. In Vulkan you have to be explicit about most pipeline states as
+it'll be baked into an immutable pipeline state object. In this chapter we'll fill 
+in all of the structures to configure these fixed-function operations.
+
+## Dynamic state
+
+While *most* of the pipeline state needs to be baked into the pipeline state, 
+a limited amount of the state *can* actually be changed without recreating the 
+pipeline at draw time. Examples are the size of the viewport, line width 
+and blend constants. If you want to use dynamic state and keep these properties out, 
+then you'll have to fill in a `VkPipelineDynamicStateCreateInfo` structure like this:
+
+```c++
+std::vector<VkDynamicState> dynamicStates = {
+    VK_DYNAMIC_STATE_VIEWPORT,
+    VK_DYNAMIC_STATE_SCISSOR
+};
+
+VkPipelineDynamicStateCreateInfo dynamicState{};
+dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
+dynamicState.pDynamicStates = dynamicStates.data();
+```
+
+This will cause the configuration of these values to be ignored and you will be
+able (and required) to specify the data at drawing time. This results in a more flexible
+setup and is very common for things like viewport and scissor state, which would
+result in a more complex setup when being baked into the pipeline state.
+
+## Vertex input
+
+The `VkPipelineVertexInputStateCreateInfo` structure describes the format of the
+vertex data that will be passed to the vertex shader. It describes this in
+roughly two ways:
+
+* Bindings: spacing between data and whether the data is per-vertex or
+per-instance (see [instancing](https://en.wikipedia.org/wiki/Geometry_instancing))
+* Attribute descriptions: type of the attributes passed to the vertex shader,
+which binding to load them from and at which offset
+
+Because we're hard coding the vertex data directly in the vertex shader, we'll
+fill in this structure to specify that there is no vertex data to load for now.
+We'll get back to it in the vertex buffer chapter.
+
+```c++
+VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
+vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+vertexInputInfo.vertexBindingDescriptionCount = 0;
+vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional
+vertexInputInfo.vertexAttributeDescriptionCount = 0;
+vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional
+```
+
+The `pVertexBindingDescriptions` and `pVertexAttributeDescriptions` members
+point to an array of structs that describe the aforementioned details for
+loading vertex data. Add this structure to the `createGraphicsPipeline` function
+right after the `shaderStages` array.
+
+## Input assembly
+
+The `VkPipelineInputAssemblyStateCreateInfo` struct describes two things: what
+kind of geometry will be drawn from the vertices and if primitive restart should
+be enabled. The former is specified in the `topology` member and can have values
+like:
+
+* `VK_PRIMITIVE_TOPOLOGY_POINT_LIST`: points from vertices
+* `VK_PRIMITIVE_TOPOLOGY_LINE_LIST`: line from every 2 vertices without reuse
+* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP`: the end vertex of every line is used as
+start vertex for the next line
+* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST`: triangle from every 3 vertices without
+reuse
+* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP `: the second and third vertex of every
+triangle are used as first two vertices of the next triangle
+
+Normally, the vertices are loaded from the vertex buffer by index in sequential
+order, but with an *element buffer* you can specify the indices to use yourself.
+This allows you to perform optimizations like reusing vertices. If you set the
+`primitiveRestartEnable`  member to `VK_TRUE`, then it's possible to break up
+lines and triangles in the `_STRIP` topology modes by using a special index of
+`0xFFFF` or `0xFFFFFFFF`.
+
+We intend to draw triangles throughout this tutorial, so we'll stick to the
+following data for the structure:
+
+```c++
+VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
+inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+inputAssembly.primitiveRestartEnable = VK_FALSE;
+```
+
+## Viewports and scissors
+
+A viewport basically describes the region of the framebuffer that the output
+will be rendered to. This will almost always be `(0, 0)` to `(width, height)`
+and in this tutorial that will also be the case.
+
+```c++
+VkViewport viewport{};
+viewport.x = 0.0f;
+viewport.y = 0.0f;
+viewport.width = (float) swapChainExtent.width;
+viewport.height = (float) swapChainExtent.height;
+viewport.minDepth = 0.0f;
+viewport.maxDepth = 1.0f;
+```
+
+Remember that the size of the swap chain and its images may differ from the
+`WIDTH` and `HEIGHT` of the window. The swap chain images will be used as
+framebuffers later on, so we should stick to their size.
+
+The `minDepth` and `maxDepth` values specify the range of depth values to use
+for the framebuffer. These values must be within the `[0.0f, 1.0f]` range, but
+`minDepth` may be higher than `maxDepth`. If you aren't doing anything special,
+then you should stick to the standard values of `0.0f` and `1.0f`.
+
+While viewports define the transformation from the image to the framebuffer,
+scissor rectangles define in which regions pixels will actually be stored. Any
+pixels outside the scissor rectangles will be discarded by the rasterizer. They
+function like a filter rather than a transformation. The difference is
+illustrated below. Note that the left scissor rectangle is just one of the many
+possibilities that would result in that image, as long as it's larger than the
+viewport.
+
+![](/images/viewports_scissors.png)
+
+So if we wanted to draw to the entire framebuffer, we would specify a scissor rectangle that covers it entirely:
+
+```c++
+VkRect2D scissor{};
+scissor.offset = {0, 0};
+scissor.extent = swapChainExtent;
+```
+
+Viewport(s) and scissor rectangle(s) can either be specified as a static part of the pipeline or as a [dynamic state](#dynamic-state) set in the command buffer. While the former is more in line with the other states it's often convenient to make viewport and scissor state dynamic as it gives you a lot more flexibility. This is very common and all implementations can handle this dynamic state without a performance penalty.
+
+When opting for dynamic viewport(s) and scissor rectangle(s) you need to enable the respective dynamic states for the pipeline:
+
+```c++
+std::vector<VkDynamicState> dynamicStates = {
+    VK_DYNAMIC_STATE_VIEWPORT,
+    VK_DYNAMIC_STATE_SCISSOR
+};
+
+VkPipelineDynamicStateCreateInfo dynamicState{};
+dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
+dynamicState.pDynamicStates = dynamicStates.data();
+```
+
+And then you only need to specify their count at pipeline creation time:
+
+```c++
+VkPipelineViewportStateCreateInfo viewportState{};
+viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+viewportState.viewportCount = 1;
+viewportState.scissorCount = 1;
+```
+
+The actual viewport(s) and scissor rectangle(s) will then later be set up at drawing time.
+
+With dynamic state it's even possible to specify different viewports and or scissor rectangles within a single command buffer.
+
+Without dynamic state, the viewport and scissor rectangle need to be set in the pipeline using the `VkPipelineViewportStateCreateInfo` struct. This makes the viewport and scissor rectangle for this pipeline immutable.
+Any changes required to these values would require a new pipeline to be created with the new values.
+
+```c++
+VkPipelineViewportStateCreateInfo viewportState{};
+viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+viewportState.viewportCount = 1;
+viewportState.pViewports = &viewport;
+viewportState.scissorCount = 1;
+viewportState.pScissors = &scissor;
+```
+
+Independent of how you set them, it's is possible to use multiple viewports and scissor rectangles on some graphics cards, so the structure members reference an array of them. Using multiple requires enabling a GPU feature (see logical device creation).
+
+## Rasterizer
+
+The rasterizer takes the geometry that is shaped by the vertices from the vertex
+shader and turns it into fragments to be colored by the fragment shader. It also
+performs [depth testing](https://en.wikipedia.org/wiki/Z-buffering),
+[face culling](https://en.wikipedia.org/wiki/Back-face_culling) and the scissor
+test, and it can be configured to output fragments that fill entire polygons or
+just the edges (wireframe rendering). All this is configured using the
+`VkPipelineRasterizationStateCreateInfo` structure.
+
+```c++
+VkPipelineRasterizationStateCreateInfo rasterizer{};
+rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+rasterizer.depthClampEnable = VK_FALSE;
+```
+
+If `depthClampEnable` is set to `VK_TRUE`, then fragments that are beyond the
+near and far planes are clamped to them as opposed to discarding them. This is
+useful in some special cases like shadow maps. Using this requires enabling a
+GPU feature.
+
+```c++
+rasterizer.rasterizerDiscardEnable = VK_FALSE;
+```
+
+If `rasterizerDiscardEnable` is set to `VK_TRUE`, then geometry never passes
+through the rasterizer stage. This basically disables any output to the
+framebuffer.
+
+```c++
+rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
+```
+
+The `polygonMode` determines how fragments are generated for geometry. The
+following modes are available:
+
+* `VK_POLYGON_MODE_FILL`: fill the area of the polygon with fragments
+* `VK_POLYGON_MODE_LINE`: polygon edges are drawn as lines
+* `VK_POLYGON_MODE_POINT`: polygon vertices are drawn as points
+
+Using any mode other than fill requires enabling a GPU feature.
+
+```c++
+rasterizer.lineWidth = 1.0f;
+```
+
+The `lineWidth` member is straightforward, it describes the thickness of lines
+in terms of number of fragments. The maximum line width that is supported
+depends on the hardware and any line thicker than `1.0f` requires you to enable
+the `wideLines` GPU feature.
+
+```c++
+rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
+rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
+```
+
+The `cullMode` variable determines the type of face culling to use. You can
+disable culling, cull the front faces, cull the back faces or both. The
+`frontFace` variable specifies the vertex order for faces to be considered
+front-facing and can be clockwise or counterclockwise.
+
+```c++
+rasterizer.depthBiasEnable = VK_FALSE;
+rasterizer.depthBiasConstantFactor = 0.0f; // Optional
+rasterizer.depthBiasClamp = 0.0f; // Optional
+rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
+```
+
+The rasterizer can alter the depth values by adding a constant value or biasing
+them based on a fragment's slope. This is sometimes used for shadow mapping, but
+we won't be using it. Just set `depthBiasEnable` to `VK_FALSE`.
+
+## Multisampling
+
+The `VkPipelineMultisampleStateCreateInfo` struct configures multisampling,
+which is one of the ways to perform [anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing).
+It works by combining the fragment shader results of multiple polygons that
+rasterize to the same pixel. This mainly occurs along edges, which is also where
+the most noticeable aliasing artifacts occur. Because it doesn't need to run the
+fragment shader multiple times if only one polygon maps to a pixel, it is
+significantly less expensive than simply rendering to a higher resolution and
+then downscaling. Enabling it requires enabling a GPU feature.
+
+```c++
+VkPipelineMultisampleStateCreateInfo multisampling{};
+multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+multisampling.sampleShadingEnable = VK_FALSE;
+multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+multisampling.minSampleShading = 1.0f; // Optional
+multisampling.pSampleMask = nullptr; // Optional
+multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
+multisampling.alphaToOneEnable = VK_FALSE; // Optional
+```
+
+We'll revisit multisampling in later chapter, for now let's keep it disabled.
+
+## Depth and stencil testing
+
+If you are using a depth and/or stencil buffer, then you also need to configure
+the depth and stencil tests using `VkPipelineDepthStencilStateCreateInfo`. We
+don't have one right now, so we can simply pass a `nullptr` instead of a pointer
+to such a struct. We'll get back to it in the depth buffering chapter.
+
+## Color blending
+
+After a fragment shader has returned a color, it needs to be combined with the
+color that is already in the framebuffer. This transformation is known as color
+blending and there are two ways to do it:
+
+* Mix the old and new value to produce a final color
+* Combine the old and new value using a bitwise operation
+
+There are two types of structs to configure color blending. The first struct,
+`VkPipelineColorBlendAttachmentState` contains the configuration per attached
+framebuffer and the second struct, `VkPipelineColorBlendStateCreateInfo`
+contains the *global* color blending settings. In our case we only have one
+framebuffer:
+
+```c++
+VkPipelineColorBlendAttachmentState colorBlendAttachment{};
+colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+colorBlendAttachment.blendEnable = VK_FALSE;
+colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
+colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
+colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
+colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
+colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
+colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
+```
+
+This per-framebuffer struct allows you to configure the first way of color
+blending. The operations that will be performed are best demonstrated using the
+following pseudocode:
+
+```c++
+if (blendEnable) {
+    finalColor.rgb = (srcColorBlendFactor * newColor.rgb) <colorBlendOp> (dstColorBlendFactor * oldColor.rgb);
+    finalColor.a = (srcAlphaBlendFactor * newColor.a) <alphaBlendOp> (dstAlphaBlendFactor * oldColor.a);
+} else {
+    finalColor = newColor;
+}
+
+finalColor = finalColor & colorWriteMask;
+```
+
+If `blendEnable` is set to `VK_FALSE`, then the new color from the fragment
+shader is passed through unmodified. Otherwise, the two mixing operations are
+performed to compute a new color. The resulting color is AND'd with the
+`colorWriteMask` to determine which channels are actually passed through.
+
+The most common way to use color blending is to implement alpha blending, where
+we want the new color to be blended with the old color based on its opacity. The
+`finalColor` should then be computed as follows:
+
+```c++
+finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;
+finalColor.a = newAlpha.a;
+```
+
+This can be accomplished with the following parameters:
+
+```c++
+colorBlendAttachment.blendEnable = VK_TRUE;
+colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
+colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
+```
+
+You can find all of the possible operations in the `VkBlendFactor` and
+`VkBlendOp` enumerations in the specification.
+
+The second structure references the array of structures for all of the
+framebuffers and allows you to set blend constants that you can use as blend
+factors in the aforementioned calculations.
+
+```c++
+VkPipelineColorBlendStateCreateInfo colorBlending{};
+colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+colorBlending.logicOpEnable = VK_FALSE;
+colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
+colorBlending.attachmentCount = 1;
+colorBlending.pAttachments = &colorBlendAttachment;
+colorBlending.blendConstants[0] = 0.0f; // Optional
+colorBlending.blendConstants[1] = 0.0f; // Optional
+colorBlending.blendConstants[2] = 0.0f; // Optional
+colorBlending.blendConstants[3] = 0.0f; // Optional
+```
+
+If you want to use the second method of blending (bitwise combination), then you
+should set `logicOpEnable` to `VK_TRUE`. The bitwise operation can then be
+specified in the `logicOp` field. Note that this will automatically disable the
+first method, as if you had set `blendEnable` to `VK_FALSE` for every
+attached framebuffer! The `colorWriteMask` will also be used in this mode to
+determine which channels in the framebuffer will actually be affected. It is
+also possible to disable both modes, as we've done here, in which case the
+fragment colors will be written to the framebuffer unmodified.
+
+## Pipeline layout
+
+You can use `uniform` values in shaders, which are globals similar to dynamic
+state variables that can be changed at drawing time to alter the behavior of
+your shaders without having to recreate them. They are commonly used to pass the
+transformation matrix to the vertex shader, or to create texture samplers in the
+fragment shader.
+
+These uniform values need to be specified during pipeline creation by creating a
+`VkPipelineLayout` object. Even though we won't be using them until a future
+chapter, we are still required to create an empty pipeline layout.
+
+Create a class member to hold this object, because we'll refer to it from other
+functions at a later point in time:
+
+```c++
+VkPipelineLayout pipelineLayout;
+```
+
+And then create the object in the `createGraphicsPipeline` function:
+
+```c++
+VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
+pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+pipelineLayoutInfo.setLayoutCount = 0; // Optional
+pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
+pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
+pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional
+
+if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create pipeline layout!");
+}
+```
+
+The structure also specifies *push constants*, which are another way of passing
+dynamic values to shaders that we may get into in a future chapter. The pipeline
+layout will be referenced throughout the program's lifetime, so it should be
+destroyed at the end:
+
+```c++
+void cleanup() {
+    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+    ...
+}
+```
+
+## Conclusion
+
+That's it for all of the fixed-function state! It's a lot of work to set all of
+this up from scratch, but the advantage is that we're now nearly fully aware of
+everything that is going on in the graphics pipeline! This reduces the chance of
+running into unexpected behavior because the default state of certain components
+is not what you expect.
+
+There is however one more object to create before we can finally create the
+graphics pipeline and that is a [render pass](!en/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes).
+
+[C++ code](/code/10_fixed_functions.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
new file mode 100644
index 00000000..a635d32f
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
@@ -0,0 +1,215 @@
+## Setup
+
+Before we can finish creating the pipeline, we need to tell Vulkan about the
+framebuffer attachments that will be used while rendering. We need to specify
+how many color and depth buffers there will be, how many samples to use for each
+of them and how their contents should be handled throughout the rendering
+operations. All of this information is wrapped in a *render pass* object, for
+which we'll create a new `createRenderPass` function. Call this function from
+`initVulkan` before `createGraphicsPipeline`.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createRenderPass();
+    createGraphicsPipeline();
+}
+
+...
+
+void createRenderPass() {
+
+}
+```
+
+## Attachment description
+
+In our case we'll have just a single color buffer attachment represented by one
+of the images from the swap chain.
+
+```c++
+void createRenderPass() {
+    VkAttachmentDescription colorAttachment{};
+    colorAttachment.format = swapChainImageFormat;
+    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
+}
+```
+
+The `format` of the color attachment should match the format of the swap chain
+images, and we're not doing anything with multisampling yet, so we'll stick to 1
+sample.
+
+```c++
+colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+```
+
+The `loadOp` and `storeOp` determine what to do with the data in the attachment
+before rendering and after rendering. We have the following choices for
+`loadOp`:
+
+* `VK_ATTACHMENT_LOAD_OP_LOAD`: Preserve the existing contents of the attachment
+* `VK_ATTACHMENT_LOAD_OP_CLEAR`: Clear the values to a constant at the start
+* `VK_ATTACHMENT_LOAD_OP_DONT_CARE`: Existing contents are undefined; we don't
+care about them
+
+In our case we're going to use the clear operation to clear the framebuffer to
+black before drawing a new frame. There are only two possibilities for the
+`storeOp`:
+
+* `VK_ATTACHMENT_STORE_OP_STORE`: Rendered contents will be stored in memory and
+can be read later
+* `VK_ATTACHMENT_STORE_OP_DONT_CARE`: Contents of the framebuffer will be
+undefined after the rendering operation
+
+We're interested in seeing the rendered triangle on the screen, so we're going
+with the store operation here.
+
+```c++
+colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+```
+
+The `loadOp` and `storeOp` apply to color and depth data, and `stencilLoadOp` /
+`stencilStoreOp` apply to stencil data. Our application won't do anything with
+the stencil buffer, so the results of loading and storing are irrelevant.
+
+```c++
+colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+```
+
+Textures and framebuffers in Vulkan are represented by `VkImage` objects with a
+certain pixel format, however the layout of the pixels in memory can change
+based on what you're trying to do with an image.
+
+Some of the most common layouts are:
+
+* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Images used as color attachment
+* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Images to be presented in the swap chain
+* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Images to be used as destination for a
+memory copy operation
+
+We'll discuss this topic in more depth in the texturing chapter, but what's
+important to know right now is that images need to be transitioned to specific
+layouts that are suitable for the operation that they're going to be involved in
+next.
+
+The `initialLayout` specifies which layout the image will have before the render
+pass begins. The `finalLayout` specifies the layout to automatically transition
+to when the render pass finishes. Using `VK_IMAGE_LAYOUT_UNDEFINED` for
+`initialLayout` means that we don't care what previous layout the image was in.
+The caveat of this special value is that the contents of the image are not
+guaranteed to be preserved, but that doesn't matter since we're going to clear
+it anyway. We want the image to be ready for presentation using the swap chain
+after rendering, which is why we use `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` as
+`finalLayout`.
+
+## Subpasses and attachment references
+
+A single render pass can consist of multiple subpasses. Subpasses are subsequent
+rendering operations that depend on the contents of framebuffers in previous
+passes, for example a sequence of post-processing effects that are applied one
+after another. If you group these rendering operations into one render pass,
+then Vulkan is able to reorder the operations and conserve memory bandwidth for
+possibly better performance. For our very first triangle, however, we'll stick
+to a single subpass.
+
+Every subpass references one or more of the attachments that we've described
+using the structure in the previous sections. These references are themselves
+`VkAttachmentReference` structs that look like this:
+
+```c++
+VkAttachmentReference colorAttachmentRef{};
+colorAttachmentRef.attachment = 0;
+colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+```
+
+The `attachment` parameter specifies which attachment to reference by its index
+in the attachment descriptions array. Our array consists of a single
+`VkAttachmentDescription`, so its index is `0`. The `layout` specifies which
+layout we would like the attachment to have during a subpass that uses this
+reference. Vulkan will automatically transition the attachment to this layout
+when the subpass is started. We intend to use the attachment to function as a
+color buffer and the `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` layout will give
+us the best performance, as its name implies.
+
+The subpass is described using a `VkSubpassDescription` structure:
+
+```c++
+VkSubpassDescription subpass{};
+subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+```
+
+Vulkan may also support compute subpasses in the future, so we have to be
+explicit about this being a graphics subpass. Next, we specify the reference to
+the color attachment:
+
+```c++
+subpass.colorAttachmentCount = 1;
+subpass.pColorAttachments = &colorAttachmentRef;
+```
+
+The index of the attachment in this array is directly referenced from the
+fragment shader with the `layout(location = 0) out vec4 outColor` directive!
+
+The following other types of attachments can be referenced by a subpass:
+
+* `pInputAttachments`: Attachments that are read from a shader
+* `pResolveAttachments`: Attachments used for multisampling color attachments
+* `pDepthStencilAttachment`: Attachment for depth and stencil data
+* `pPreserveAttachments`: Attachments that are not used by this subpass, but for
+which the data must be preserved
+
+## Render pass
+
+Now that the attachment and a basic subpass referencing it have been described,
+we can create the render pass itself. Create a new class member variable to hold
+the `VkRenderPass` object right above the `pipelineLayout` variable:
+
+```c++
+VkRenderPass renderPass;
+VkPipelineLayout pipelineLayout;
+```
+
+The render pass object can then be created by filling in the
+`VkRenderPassCreateInfo` structure with an array of attachments and subpasses.
+The `VkAttachmentReference` objects reference attachments using the indices of
+this array.
+
+```c++
+VkRenderPassCreateInfo renderPassInfo{};
+renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+renderPassInfo.attachmentCount = 1;
+renderPassInfo.pAttachments = &colorAttachment;
+renderPassInfo.subpassCount = 1;
+renderPassInfo.pSubpasses = &subpass;
+
+if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create render pass!");
+}
+```
+
+Just like the pipeline layout, the render pass will be referenced throughout the
+program, so it should only be cleaned up at the end:
+
+```c++
+void cleanup() {
+    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+    vkDestroyRenderPass(device, renderPass, nullptr);
+    ...
+}
+```
+
+That was a lot of work, but in the next chapter it all comes together to finally
+create the graphics pipeline object!
+
+[C++ code](/code/11_render_passes.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
new file mode 100644
index 00000000..4a16585e
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
@@ -0,0 +1,122 @@
+We can now combine all of the structures and objects from the previous chapters
+to create the graphics pipeline! Here's the types of objects we have now, as a
+quick recap:
+
+* Shader stages: the shader modules that define the functionality of the
+programmable stages of the graphics pipeline
+* Fixed-function state: all of the structures that define the fixed-function
+stages of the pipeline, like input assembly, rasterizer, viewport and color
+blending
+* Pipeline layout: the uniform and push values referenced by the shader that can
+be updated at draw time
+* Render pass: the attachments referenced by the pipeline stages and their usage
+
+All of these combined fully define the functionality of the graphics pipeline,
+so we can now begin filling in the `VkGraphicsPipelineCreateInfo` structure at
+the end of the `createGraphicsPipeline` function. But before the calls to 
+`vkDestroyShaderModule` because these are still to be used during the creation.
+
+```c++
+VkGraphicsPipelineCreateInfo pipelineInfo{};
+pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+pipelineInfo.stageCount = 2;
+pipelineInfo.pStages = shaderStages;
+```
+
+We start by referencing the array of `VkPipelineShaderStageCreateInfo` structs.
+
+```c++
+pipelineInfo.pVertexInputState = &vertexInputInfo;
+pipelineInfo.pInputAssemblyState = &inputAssembly;
+pipelineInfo.pViewportState = &viewportState;
+pipelineInfo.pRasterizationState = &rasterizer;
+pipelineInfo.pMultisampleState = &multisampling;
+pipelineInfo.pDepthStencilState = nullptr; // Optional
+pipelineInfo.pColorBlendState = &colorBlending;
+pipelineInfo.pDynamicState = &dynamicState;
+```
+
+Then we reference all of the structures describing the fixed-function stage.
+
+```c++
+pipelineInfo.layout = pipelineLayout;
+```
+
+After that comes the pipeline layout, which is a Vulkan handle rather than a
+struct pointer.
+
+```c++
+pipelineInfo.renderPass = renderPass;
+pipelineInfo.subpass = 0;
+```
+
+And finally we have the reference to the render pass and the index of the sub
+pass where this graphics pipeline will be used. It is also possible to use other
+render passes with this pipeline instead of this specific instance, but they
+have to be *compatible* with `renderPass`. The requirements for compatibility
+are described [here](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility),
+but we won't be using that feature in this tutorial.
+
+```c++
+pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
+pipelineInfo.basePipelineIndex = -1; // Optional
+```
+
+There are actually two more parameters: `basePipelineHandle` and
+`basePipelineIndex`. Vulkan allows you to create a new graphics pipeline by
+deriving from an existing pipeline. The idea of pipeline derivatives is that it
+is less expensive to set up pipelines when they have much functionality in
+common with an existing pipeline and switching between pipelines from the same
+parent can also be done quicker. You can either specify the handle of an
+existing pipeline with `basePipelineHandle` or reference another pipeline that
+is about to be created by index with `basePipelineIndex`. Right now there is
+only a single pipeline, so we'll simply specify a null handle and an invalid
+index. These values are only used if the `VK_PIPELINE_CREATE_DERIVATIVE_BIT`
+flag is also specified in the `flags` field of `VkGraphicsPipelineCreateInfo`.
+
+Now prepare for the final step by creating a class member to hold the
+`VkPipeline` object:
+
+```c++
+VkPipeline graphicsPipeline;
+```
+
+And finally create the graphics pipeline:
+
+```c++
+if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create graphics pipeline!");
+}
+```
+
+The `vkCreateGraphicsPipelines` function actually has more parameters than the
+usual object creation functions in Vulkan. It is designed to take multiple
+`VkGraphicsPipelineCreateInfo` objects and create multiple `VkPipeline` objects
+in a single call.
+
+The second parameter, for which we've passed the `VK_NULL_HANDLE` argument,
+references an optional `VkPipelineCache` object. A pipeline cache can be used to
+store and reuse data relevant to pipeline creation across multiple calls to
+`vkCreateGraphicsPipelines` and even across program executions if the cache is
+stored to a file. This makes it possible to significantly speed up pipeline
+creation at a later time. We'll get into this in the pipeline cache chapter.
+
+The graphics pipeline is required for all common drawing operations, so it
+should also only be destroyed at the end of the program:
+
+```c++
+void cleanup() {
+    vkDestroyPipeline(device, graphicsPipeline, nullptr);
+    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+    ...
+}
+```
+
+Now run your program to confirm that all this hard work has resulted in a
+successful pipeline creation! We are already getting quite close to seeing
+something pop up on the screen. In the next couple of chapters we'll set up the
+actual framebuffers from the swap chain images and prepare the drawing commands.
+
+[C++ code](/code/12_graphics_pipeline_complete.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
new file mode 100644
index 00000000..bf7f84a7
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
@@ -0,0 +1,107 @@
+We've talked a lot about framebuffers in the past few chapters and we've set up
+the render pass to expect a single framebuffer with the same format as the swap
+chain images, but we haven't actually created any yet.
+
+The attachments specified during render pass creation are bound by wrapping them
+into a `VkFramebuffer` object. A framebuffer object references all of the
+`VkImageView` objects that represent the attachments. In our case that will be
+only a single one: the color attachment. However, the image that we have to use
+for the attachment depends on which image the swap chain returns when we retrieve one
+for presentation. That means that we have to create a framebuffer for all of the
+images in the swap chain and use the one that corresponds to the retrieved image
+at drawing time.
+
+To that end, create another `std::vector` class member to hold the framebuffers:
+
+```c++
+std::vector<VkFramebuffer> swapChainFramebuffers;
+```
+
+We'll create the objects for this array in a new function `createFramebuffers`
+that is called from `initVulkan` right after creating the graphics pipeline:
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createRenderPass();
+    createGraphicsPipeline();
+    createFramebuffers();
+}
+
+...
+
+void createFramebuffers() {
+
+}
+```
+
+Start by resizing the container to hold all of the framebuffers:
+
+```c++
+void createFramebuffers() {
+    swapChainFramebuffers.resize(swapChainImageViews.size());
+}
+```
+
+We'll then iterate through the image views and create framebuffers from them:
+
+```c++
+for (size_t i = 0; i < swapChainImageViews.size(); i++) {
+    VkImageView attachments[] = {
+        swapChainImageViews[i]
+    };
+
+    VkFramebufferCreateInfo framebufferInfo{};
+    framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+    framebufferInfo.renderPass = renderPass;
+    framebufferInfo.attachmentCount = 1;
+    framebufferInfo.pAttachments = attachments;
+    framebufferInfo.width = swapChainExtent.width;
+    framebufferInfo.height = swapChainExtent.height;
+    framebufferInfo.layers = 1;
+
+    if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create framebuffer!");
+    }
+}
+```
+
+As you can see, creation of framebuffers is quite straightforward. We first need
+to specify with which `renderPass` the framebuffer needs to be compatible. You
+can only use a framebuffer with the render passes that it is compatible with,
+which roughly means that they use the same number and type of attachments.
+
+The `attachmentCount` and `pAttachments` parameters specify the `VkImageView`
+objects that should be bound to the respective attachment descriptions in
+the render pass `pAttachment` array.
+
+The `width` and `height` parameters are self-explanatory and `layers` refers to
+the number of layers in image arrays. Our swap chain images are single images,
+so the number of layers is `1`.
+
+We should delete the framebuffers before the image views and render pass that
+they are based on, but only after we've finished rendering:
+
+```c++
+void cleanup() {
+    for (auto framebuffer : swapChainFramebuffers) {
+        vkDestroyFramebuffer(device, framebuffer, nullptr);
+    }
+
+    ...
+}
+```
+
+We've now reached the milestone where we have all of the objects that are
+required for rendering. In the next chapter we're going to write the first
+actual drawing commands.
+
+[C++ code](/code/13_framebuffers.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md b/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
new file mode 100644
index 00000000..61a40b4f
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
@@ -0,0 +1,344 @@
+Commands in Vulkan, like drawing operations and memory transfers, are not
+executed directly using function calls. You have to record all of the operations
+you want to perform in command buffer objects. The advantage of this is that when
+we are ready to tell the Vulkan what we want to do, all of the commands are
+submitted together and Vulkan can more efficiently process the commands since all
+of them are available together. In addition, this allows command recording to
+happen in multiple threads if so desired.
+
+## Command pools
+
+We have to create a command pool before we can create command buffers. Command
+pools manage the memory that is used to store the buffers and command buffers
+are allocated from them. Add a new class member to store a `VkCommandPool`:
+
+```c++
+VkCommandPool commandPool;
+```
+
+Then create a new function `createCommandPool` and call it from `initVulkan`
+after the framebuffers were created.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createRenderPass();
+    createGraphicsPipeline();
+    createFramebuffers();
+    createCommandPool();
+}
+
+...
+
+void createCommandPool() {
+
+}
+```
+
+Command pool creation only takes two parameters:
+
+```c++
+QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
+
+VkCommandPoolCreateInfo poolInfo{};
+poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
+```
+
+There are two possible flags for command pools:
+
+* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT`: Hint that command buffers are
+rerecorded with new commands very often (may change memory allocation behavior)
+* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`: Allow command buffers to be
+rerecorded individually, without this flag they all have to be reset together
+
+We will be recording a command buffer every frame, so we want to be able to
+reset and rerecord over it. Thus, we need to set the
+`VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag bit for our command pool.
+
+Command buffers are executed by submitting them on one of the device queues,
+like the graphics and presentation queues we retrieved. Each command pool can
+only allocate command buffers that are submitted on a single type of queue.
+We're going to record commands for drawing, which is why we've chosen the
+graphics queue family.
+
+
+```c++
+if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create command pool!");
+}
+```
+
+Finish creating the command pool using the `vkCreateCommandPool` function. It
+doesn't have any special parameters. Commands will be used throughout the
+program to draw things on the screen, so the pool should only be destroyed at
+the end:
+
+```c++
+void cleanup() {
+    vkDestroyCommandPool(device, commandPool, nullptr);
+
+    ...
+}
+```
+
+## Command buffer allocation
+
+We can now start allocating command buffers.
+
+Create a `VkCommandBuffer` object as a class member. Command buffers
+will be automatically freed when their command pool is destroyed, so we don't
+need explicit cleanup.
+
+```c++
+VkCommandBuffer commandBuffer;
+```
+
+We'll now start working on a `createCommandBuffer` function to allocate a single
+command buffer from the command pool.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createRenderPass();
+    createGraphicsPipeline();
+    createFramebuffers();
+    createCommandPool();
+    createCommandBuffer();
+}
+
+...
+
+void createCommandBuffer() {
+
+}
+```
+
+Command buffers are allocated with the `vkAllocateCommandBuffers` function,
+which takes a `VkCommandBufferAllocateInfo` struct as parameter that specifies
+the command pool and number of buffers to allocate:
+
+```c++
+VkCommandBufferAllocateInfo allocInfo{};
+allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+allocInfo.commandPool = commandPool;
+allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+allocInfo.commandBufferCount = 1;
+
+if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
+    throw std::runtime_error("failed to allocate command buffers!");
+}
+```
+
+The `level` parameter specifies if the allocated command buffers are primary or
+secondary command buffers.
+
+* `VK_COMMAND_BUFFER_LEVEL_PRIMARY`: Can be submitted to a queue for execution,
+but cannot be called from other command buffers.
+* `VK_COMMAND_BUFFER_LEVEL_SECONDARY`: Cannot be submitted directly, but can be
+called from primary command buffers.
+
+We won't make use of the secondary command buffer functionality here, but you
+can imagine that it's helpful to reuse common operations from primary command
+buffers.
+
+Since we are only allocating one command buffer, the `commandBufferCount` parameter
+is just one.
+
+## Command buffer recording
+
+We'll now start working on the `recordCommandBuffer` function that writes the
+commands we want to execute into a command buffer. The `VkCommandBuffer` used
+will be passed in as a parameter, as well as the index of the current swapchain
+image we want to write to.
+
+```c++
+void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
+
+}
+```
+
+We always begin recording a command buffer by calling `vkBeginCommandBuffer`
+with a small `VkCommandBufferBeginInfo` structure as argument that specifies
+some details about the usage of this specific command buffer.
+
+```c++
+VkCommandBufferBeginInfo beginInfo{};
+beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+beginInfo.flags = 0; // Optional
+beginInfo.pInheritanceInfo = nullptr; // Optional
+
+if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
+    throw std::runtime_error("failed to begin recording command buffer!");
+}
+```
+
+The `flags` parameter specifies how we're going to use the command buffer. The
+following values are available:
+
+* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`: The command buffer will be
+rerecorded right after executing it once.
+* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT`: This is a secondary
+command buffer that will be entirely within a single render pass.
+* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT`: The command buffer can be
+resubmitted while it is also already pending execution.
+
+None of these flags are applicable for us right now.
+
+The `pInheritanceInfo` parameter is only relevant for secondary command buffers.
+It specifies which state to inherit from the calling primary command buffers.
+
+If the command buffer was already recorded once, then a call to
+`vkBeginCommandBuffer` will implicitly reset it. It's not possible to append
+commands to a buffer at a later time.
+
+## Starting a render pass
+
+Drawing starts by beginning the render pass with `vkCmdBeginRenderPass`. The
+render pass is configured using some parameters in a `VkRenderPassBeginInfo`
+struct.
+
+```c++
+VkRenderPassBeginInfo renderPassInfo{};
+renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+renderPassInfo.renderPass = renderPass;
+renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
+```
+
+The first parameters are the render pass itself and the attachments to bind. We
+created a framebuffer for each swap chain image where it is specified as a color
+attachment. Thus we need to bind the framebuffer for the swapchain image we want
+to draw to. Using the imageIndex parameter which was passed in, we can pick the
+right framebuffer for the current swapchain image.
+
+```c++
+renderPassInfo.renderArea.offset = {0, 0};
+renderPassInfo.renderArea.extent = swapChainExtent;
+```
+
+The next two parameters define the size of the render area. The render area
+defines where shader loads and stores will take place. The pixels outside this
+region will have undefined values. It should match the size of the attachments
+for best performance.
+
+```c++
+VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
+renderPassInfo.clearValueCount = 1;
+renderPassInfo.pClearValues = &clearColor;
+```
+
+The last two parameters define the clear values to use for
+`VK_ATTACHMENT_LOAD_OP_CLEAR`, which we used as load operation for the color
+attachment. I've defined the clear color to simply be black with 100% opacity.
+
+```c++
+vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
+```
+
+The render pass can now begin. All of the functions that record commands can be
+recognized by their `vkCmd` prefix. They all return `void`, so there will be no
+error handling until we've finished recording.
+
+The first parameter for every command is always the command buffer to record the
+command to. The second parameter specifies the details of the render pass we've
+just provided. The final parameter controls how the drawing commands within the
+render pass will be provided. It can have one of two values:
+
+* `VK_SUBPASS_CONTENTS_INLINE`: The render pass commands will be embedded in
+the primary command buffer itself and no secondary command buffers will be
+executed.
+* `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS`: The render pass commands will
+be executed from secondary command buffers.
+
+We will not be using secondary command buffers, so we'll go with the first
+option.
+
+## Basic drawing commands
+
+We can now bind the graphics pipeline:
+
+```c++
+vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+```
+
+The second parameter specifies if the pipeline object is a graphics or compute
+pipeline. We've now told Vulkan which operations to execute in the graphics
+pipeline and which attachment to use in the fragment shader.
+
+As noted in the [fixed functions chapter](../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state), 
+we did specify viewport and scissor state for this pipeline to be dynamic.
+So we need to set them in the command buffer before issuing our draw command:
+
+```c++
+VkViewport viewport{};
+viewport.x = 0.0f;
+viewport.y = 0.0f;
+viewport.width = static_cast<float>(swapChainExtent.width);
+viewport.height = static_cast<float>(swapChainExtent.height);
+viewport.minDepth = 0.0f;
+viewport.maxDepth = 1.0f;
+vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
+
+VkRect2D scissor{};
+scissor.offset = {0, 0};
+scissor.extent = swapChainExtent;
+vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
+```
+
+Now we are ready to issue the draw command for the triangle:
+
+```c++
+vkCmdDraw(commandBuffer, 3, 1, 0, 0);
+```
+
+The actual `vkCmdDraw` function is a bit anticlimactic, but it's so simple
+because of all the information we specified in advance. It has the following
+parameters, aside from the command buffer:
+
+* `vertexCount`: Even though we don't have a vertex buffer, we technically still
+have 3 vertices to draw.
+* `instanceCount`: Used for instanced rendering, use `1` if you're not doing
+that.
+* `firstVertex`: Used as an offset into the vertex buffer, defines the lowest
+value of `gl_VertexIndex`.
+* `firstInstance`: Used as an offset for instanced rendering, defines the lowest
+value of `gl_InstanceIndex`.
+
+## Finishing up
+
+The render pass can now be ended:
+
+```c++
+vkCmdEndRenderPass(commandBuffer);
+```
+
+And we've finished recording the command buffer:
+
+```c++
+if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
+    throw std::runtime_error("failed to record command buffer!");
+}
+```
+
+
+
+In the next chapter we'll write the code for the main loop, which will acquire
+an image from the swap chain, record and execute a command buffer, then return the
+finished image to the swap chain.
+
+[C++ code](/code/14_command_buffers.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
new file mode 100644
index 00000000..233c059d
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
@@ -0,0 +1,577 @@
+
+This is the chapter where everything is going to come together. We're going to
+write the `drawFrame` function that will be called from the main loop to put the
+triangle on the screen. Let's start by creating the function and call it from
+`mainLoop`:
+
+```c++
+void mainLoop() {
+    while (!glfwWindowShouldClose(window)) {
+        glfwPollEvents();
+        drawFrame();
+    }
+}
+
+...
+
+void drawFrame() {
+
+}
+```
+
+## Outline of a frame
+
+At a high level, rendering a frame in Vulkan consists of a common set of steps:
+
+* Wait for the previous frame to finish
+* Acquire an image from the swap chain
+* Record a command buffer which draws the scene onto that image
+* Submit the recorded command buffer
+* Present the swap chain image
+
+While we will expand the drawing function in later chapters, for now this is the
+core of our render loop.
+
+<!-- Add an image that shows an outline of the frame -->
+
+## Synchronization
+
+<!-- Maybe add images for showing synchronization -->
+
+A core design philosophy in Vulkan is that synchronization of execution on
+the GPU is explicit. The order of operations is up to us to define using various
+synchronization primitives which tell the driver the order we want things to run
+in. This means that many Vulkan API calls which start executing work on the GPU
+are asynchronous, the functions will return before the operation has finished.
+
+In this chapter there are a number of events that we need to order explicitly
+because they happen on the GPU, such as:
+
+* Acquire an image from the swap chain
+* Execute commands that draw onto the acquired image
+* Present that image to the screen for presentation, returning it to the swapchain
+
+Each of these events is set in motion using a single function call, but are all
+executed asynchronously. The function calls will return before the operations
+are actually finished and the order of execution is also undefined. That is
+unfortunate, because each of the operations depends on the previous one
+finishing. Thus we need to explore which primitives we can use to achieve
+the desired ordering.
+
+### Semaphores
+
+A semaphore is used to add order between queue operations. Queue operations
+refer to the work we submit to a queue, either in a command buffer or from
+within a function as we will see later. Examples of queues are the graphics
+queue and the presentation queue. Semaphores are used both to order work inside
+the same queue and between different queues.
+
+There happens to be two kinds of semaphores in Vulkan, binary and timeline.
+Because only binary semaphores will be used in this tutorial, we will not
+discuss timeline semaphores. Further mention of the term semaphore exclusively
+refers to binary semaphores.
+
+A semaphore is either unsignaled or signaled. It begins life as unsignaled. The
+way we use a semaphore to order queue operations is by providing the same
+semaphore as a 'signal' semaphore in one queue operation and as a 'wait'
+semaphore in another queue operation. For example, lets say we have semaphore S
+and queue operations A and B that we want to execute in order. What we tell
+Vulkan is that operation A will 'signal' semaphore S when it finishes executing,
+and operation B will 'wait' on semaphore S before it begins executing. When
+operation A finishes, semaphore S will be signaled, while operation B wont
+start until S is signaled. After operation B begins executing, semaphore S
+is automatically reset back to being unsignaled, allowing it to be used again.
+
+Pseudo-code of what was just described:
+```
+VkCommandBuffer A, B = ... // record command buffers
+VkSemaphore S = ... // create a semaphore
+
+// enqueue A, signal S when done - starts executing immediately
+vkQueueSubmit(work: A, signal: S, wait: None)
+
+// enqueue B, wait on S to start
+vkQueueSubmit(work: B, signal: None, wait: S)
+```
+
+Note that in this code snippet, both calls to `vkQueueSubmit()` return
+immediately - the waiting only happens on the GPU. The CPU continues running
+without blocking. To make the CPU wait, we need a different synchronization
+primitive, which we will now describe.
+
+### Fences
+
+A fence has a similar purpose, in that it is used to synchronize execution, but
+it is for ordering the execution on the CPU, otherwise known as the host.
+Simply put, if the host needs to know when the GPU has finished something, we
+use a fence.
+
+Similar to semaphores, fences are either in a signaled or unsignaled state.
+Whenever we submit work to execute, we can attach a fence to that work. When
+the work is finished, the fence will be signaled. Then we can make the host
+wait for the fence to be signaled, guaranteeing that the work has finished
+before the host continues.
+
+A concrete example is taking a screenshot. Say we have already done the
+necessary work on the GPU. Now need to transfer the image from the GPU over
+to the host and then save the memory to a file. We have command buffer A which
+executes the transfer and fence F. We submit command buffer A with fence F,
+then immediately tell the host to wait for F to signal. This causes the host to
+block until command buffer A finishes execution. Thus we are safe to let the
+host save the file to disk, as the memory transfer has completed.
+
+Pseudo-code for what was described:
+```
+VkCommandBuffer A = ... // record command buffer with the transfer
+VkFence F = ... // create the fence
+
+// enqueue A, start work immediately, signal F when done
+vkQueueSubmit(work: A, fence: F)
+
+vkWaitForFence(F) // blocks execution until A has finished executing
+
+save_screenshot_to_disk() // can't run until the transfer has finished
+```
+
+Unlike the semaphore example, this example *does* block host execution. This
+means the host won't do anything except wait until execution has finished. For
+this case, we had to make sure the transfer was complete before we could save
+the screenshot to disk.
+
+In general, it is preferable to not block the host unless necessary. We want to
+feed the GPU and the host with useful work to do. Waiting on fences to signal
+is not useful work. Thus we prefer semaphores, or other synchronization
+primitives not yet covered, to synchronize our work.
+
+Fences must be reset manually to put them back into the unsignaled state. This
+is because fences are used to control the execution of the host, and so the
+host gets to decide when to reset the fence. Contrast this to semaphores which
+are used to order work on the GPU without the host being involved.
+
+In summary, semaphores are used to specify the execution order of operations on
+the GPU while fences are used to keep the CPU and GPU in sync with each-other.
+
+### What to choose?
+
+We have two synchronization primitives to use and conveniently two places to
+apply synchronization: Swapchain operations and waiting for the previous frame
+to finish. We want to use semaphores for swapchain operations because they
+happen on the GPU, thus we don't want to make the host wait around if we can
+help it. For waiting on the previous frame to finish, we want to use fences
+for the opposite reason, because we need the host to wait. This is so we don't
+draw more than one frame at a time. Because we re-record the command buffer
+every frame, we cannot record the next frame's work to the command buffer
+until the current frame has finished executing, as we don't want to overwrite
+the current contents of the command buffer while the GPU is using it.
+
+## Creating the synchronization objects
+
+We'll need one semaphore to signal that an image has been acquired from the
+swapchain and is ready for rendering, another one to signal that rendering has
+finished and presentation can happen, and a fence to make sure only one frame
+is rendering at a time.
+
+Create three class members to store these semaphore objects and fence object:
+
+```c++
+VkSemaphore imageAvailableSemaphore;
+VkSemaphore renderFinishedSemaphore;
+VkFence inFlightFence;
+```
+
+To create the semaphores, we'll add the last `create` function for this part of
+the tutorial: `createSyncObjects`:
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createRenderPass();
+    createGraphicsPipeline();
+    createFramebuffers();
+    createCommandPool();
+    createCommandBuffer();
+    createSyncObjects();
+}
+
+...
+
+void createSyncObjects() {
+
+}
+```
+
+Creating semaphores requires filling in the `VkSemaphoreCreateInfo`, but in the
+current version of the API it doesn't actually have any required fields besides
+`sType`:
+
+```c++
+void createSyncObjects() {
+    VkSemaphoreCreateInfo semaphoreInfo{};
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+}
+```
+
+Future versions of the Vulkan API or extensions may add functionality for the
+`flags` and `pNext` parameters like it does for the other structures.
+
+Creating a fence requires filling in the `VkFenceCreateInfo`:
+
+```c++
+VkFenceCreateInfo fenceInfo{};
+fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+```
+
+Creating the semaphores and fence follows the familiar pattern with
+`vkCreateSemaphore` & `vkCreateFence`:
+
+```c++
+if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
+    vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS ||
+    vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create semaphores!");
+}
+```
+
+The semaphores and fence should be cleaned up at the end of the program, when
+all commands have finished and no more synchronization is necessary:
+
+```c++
+void cleanup() {
+    vkDestroySemaphore(device, imageAvailableSemaphore, nullptr);
+    vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
+    vkDestroyFence(device, inFlightFence, nullptr);
+```
+
+Onto the main drawing function!
+
+## Waiting for the previous frame
+
+At the start of the frame, we want to wait until the previous frame has
+finished, so that the command buffer and semaphores are available to use. To do
+that, we call `vkWaitForFences`:
+
+```c++
+void drawFrame() {
+    vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
+}
+```
+
+The `vkWaitForFences` function takes an array of fences and waits on the host
+for either any or all of the fences to be signaled before returning. The
+`VK_TRUE` we pass here indicates that we want to wait for all fences, but in
+the case of a single one it doesn't matter. This function also has a timeout
+parameter that we set to the maximum value of a 64 bit unsigned integer,
+`UINT64_MAX`, which effectively disables the timeout.
+
+After waiting, we need to manually reset the fence to the unsignaled state with
+the `vkResetFences` call:
+```c++
+    vkResetFences(device, 1, &inFlightFence);
+```
+
+Before we can proceed, there is a slight hiccup in our design. On the first
+frame we call `drawFrame()`, which immediately waits on `inFlightFence` to
+be signaled. `inFlightFence` is only signaled after a frame has finished
+rendering, yet since this is the first frame, there are no previous frames in
+which to signal the fence! Thus `vkWaitForFences()` blocks indefinitely,
+waiting on something which will never happen.
+
+Of the many solutions to this dilemma, there is a clever workaround built into
+the API. Create the fence in the signaled state, so that the first call to
+`vkWaitForFences()` returns immediately since the fence is already signaled.
+
+To do this, we add the `VK_FENCE_CREATE_SIGNALED_BIT` flag to the `VkFenceCreateInfo`:
+
+```c++
+void createSyncObjects() {
+    ...
+
+    VkFenceCreateInfo fenceInfo{};
+    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+    ...
+}
+```
+
+## Acquiring an image from the swap chain
+
+The next thing we need to do in the `drawFrame` function is acquire an image
+from the swap chain. Recall that the swap chain is an extension feature, so we
+must use a function with the `vk*KHR` naming convention:
+
+```c++
+void drawFrame() {
+    ...
+
+    uint32_t imageIndex;
+    vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
+}
+```
+
+The first two parameters of `vkAcquireNextImageKHR` are the logical device and
+the swap chain from which we wish to acquire an image. The third parameter
+specifies a timeout in nanoseconds for an image to become available. Using the
+maximum value of a 64 bit unsigned integer means we effectively disable the
+timeout.
+
+The next two parameters specify synchronization objects that are to be signaled
+when the presentation engine is finished using the image. That's the point in
+time where we can start drawing to it. It is possible to specify a semaphore,
+fence or both. We're going to use our `imageAvailableSemaphore` for that purpose
+here.
+
+The last parameter specifies a variable to output the index of the swap chain
+image that has become available. The index refers to the `VkImage` in our
+`swapChainImages` array. We're going to use that index to pick the `VkFrameBuffer`.
+
+## Recording the command buffer
+
+With the imageIndex specifying the swap chain image to use in hand, we can now
+record the command buffer. First, we call `vkResetCommandBuffer` on the command
+buffer to make sure it is able to be recorded.
+
+```c++
+vkResetCommandBuffer(commandBuffer, 0);
+```
+
+The second parameter of `vkResetCommandBuffer` is a `VkCommandBufferResetFlagBits`
+flag. Since we don't want to do anything special, we leave it as 0.
+
+Now call the function `recordCommandBuffer` to record the commands we want.
+
+```c++
+recordCommandBuffer(commandBuffer, imageIndex);
+```
+
+With a fully recorded command buffer, we can now submit it.
+
+## Submitting the command buffer
+
+Queue submission and synchronization is configured through parameters in the
+`VkSubmitInfo` structure.
+
+```c++
+VkSubmitInfo submitInfo{};
+submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+
+VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
+VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
+submitInfo.waitSemaphoreCount = 1;
+submitInfo.pWaitSemaphores = waitSemaphores;
+submitInfo.pWaitDstStageMask = waitStages;
+```
+
+The first three parameters specify which semaphores to wait on before execution
+begins and in which stage(s) of the pipeline to wait. We want to wait with
+writing colors to the image until it's available, so we're specifying the stage
+of the graphics pipeline that writes to the color attachment. That means that
+theoretically the implementation can already start executing our vertex shader
+and such while the image is not yet available. Each entry in the `waitStages`
+array corresponds to the semaphore with the same index in `pWaitSemaphores`.
+
+```c++
+submitInfo.commandBufferCount = 1;
+submitInfo.pCommandBuffers = &commandBuffer;
+```
+
+The next two parameters specify which command buffers to actually submit for
+execution. We simply submit the single command buffer we have.
+
+```c++
+VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
+submitInfo.signalSemaphoreCount = 1;
+submitInfo.pSignalSemaphores = signalSemaphores;
+```
+
+The `signalSemaphoreCount` and `pSignalSemaphores` parameters specify which
+semaphores to signal once the command buffer(s) have finished execution. In our
+case we're using the `renderFinishedSemaphore` for that purpose.
+
+```c++
+if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
+    throw std::runtime_error("failed to submit draw command buffer!");
+}
+```
+
+We can now submit the command buffer to the graphics queue using
+`vkQueueSubmit`. The function takes an array of `VkSubmitInfo` structures as
+argument for efficiency when the workload is much larger. The last parameter
+references an optional fence that will be signaled when the command buffers
+finish execution. This allows us to know when it is safe for the command
+buffer to be reused, thus we want to give it `inFlightFence`. Now on the next
+frame, the CPU will wait for this command buffer to finish executing before it
+records new commands into it.
+
+## Subpass dependencies
+
+Remember that the subpasses in a render pass automatically take care of image
+layout transitions. These transitions are controlled by *subpass dependencies*,
+which specify memory and execution dependencies between subpasses. We have only
+a single subpass right now, but the operations right before and right after this
+subpass also count as implicit "subpasses".
+
+There are two built-in dependencies that take care of the transition at the
+start of the render pass and at the end of the render pass, but the former does
+not occur at the right time. It assumes that the transition occurs at the start
+of the pipeline, but we haven't acquired the image yet at that point! There are
+two ways to deal with this problem. We could change the `waitStages` for the
+`imageAvailableSemaphore` to `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` to ensure that
+the render passes don't begin until the image is available, or we can make the
+render pass wait for the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage.
+I've decided to go with the second option here, because it's a good excuse to
+have a look at subpass dependencies and how they work.
+
+Subpass dependencies are specified in `VkSubpassDependency` structs. Go to the
+`createRenderPass` function and add one:
+
+```c++
+VkSubpassDependency dependency{};
+dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+dependency.dstSubpass = 0;
+```
+
+The first two fields specify the indices of the dependency and the dependent
+subpass. The special value `VK_SUBPASS_EXTERNAL` refers to the implicit subpass
+before or after the render pass depending on whether it is specified in
+`srcSubpass` or `dstSubpass`. The index `0` refers to our subpass, which is the
+first and only one. The `dstSubpass` must always be higher than `srcSubpass` to
+prevent cycles in the dependency graph (unless one of the subpasses is
+`VK_SUBPASS_EXTERNAL`).
+
+```c++
+dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+dependency.srcAccessMask = 0;
+```
+
+The next two fields specify the operations to wait on and the stages in which
+these operations occur. We need to wait for the swap chain to finish reading
+from the image before we can access it. This can be accomplished by waiting on
+the color attachment output stage itself.
+
+```c++
+dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+```
+
+The operations that should wait on this are in the color attachment stage and
+involve the writing of the color attachment. These settings will
+prevent the transition from happening until it's actually necessary (and
+allowed): when we want to start writing colors to it.
+
+```c++
+renderPassInfo.dependencyCount = 1;
+renderPassInfo.pDependencies = &dependency;
+```
+
+The `VkRenderPassCreateInfo` struct has two fields to specify an array of
+dependencies.
+
+## Presentation
+
+The last step of drawing a frame is submitting the result back to the swap chain
+to have it eventually show up on the screen. Presentation is configured through
+a `VkPresentInfoKHR` structure at the end of the `drawFrame` function.
+
+```c++
+VkPresentInfoKHR presentInfo{};
+presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+
+presentInfo.waitSemaphoreCount = 1;
+presentInfo.pWaitSemaphores = signalSemaphores;
+```
+
+The first two parameters specify which semaphores to wait on before presentation
+can happen, just like `VkSubmitInfo`. Since we want to wait on the command buffer
+to finish execution, thus our triangle being drawn, we take the semaphores
+which will be signalled and wait on them, thus we use `signalSemaphores`.
+
+
+```c++
+VkSwapchainKHR swapChains[] = {swapChain};
+presentInfo.swapchainCount = 1;
+presentInfo.pSwapchains = swapChains;
+presentInfo.pImageIndices = &imageIndex;
+```
+
+The next two parameters specify the swap chains to present images to and the
+index of the image for each swap chain. This will almost always be a single one.
+
+```c++
+presentInfo.pResults = nullptr; // Optional
+```
+
+There is one last optional parameter called `pResults`. It allows you to specify
+an array of `VkResult` values to check for every individual swap chain if
+presentation was successful. It's not necessary if you're only using a single
+swap chain, because you can simply use the return value of the present function.
+
+```c++
+vkQueuePresentKHR(presentQueue, &presentInfo);
+```
+
+The `vkQueuePresentKHR` function submits the request to present an image to the
+swap chain. We'll add error handling for both `vkAcquireNextImageKHR` and
+`vkQueuePresentKHR` in the next chapter, because their failure does not
+necessarily mean that the program should terminate, unlike the functions we've
+seen so far.
+
+If you did everything correctly up to this point, then you should now see
+something resembling the following when you run your program:
+
+![](/images/triangle.png)
+
+>This colored triangle may look a bit different from the one you're used to seeing in graphics tutorials. That's because this tutorial lets the shader interpolate in linear color space and converts to sRGB color space afterwards. See [this blog post](https://medium.com/@heypete/hello-triangle-meet-swift-and-wide-color-6f9e246616d9) for a discussion of the difference.
+
+Yay! Unfortunately, you'll see that when validation layers are enabled, the
+program crashes as soon as you close it. The messages printed to the terminal
+from `debugCallback` tell us why:
+
+![](/images/semaphore_in_use.png)
+
+Remember that all of the operations in `drawFrame` are asynchronous. That means
+that when we exit the loop in `mainLoop`, drawing and presentation operations
+may still be going on. Cleaning up resources while that is happening is a bad
+idea.
+
+To fix that problem, we should wait for the logical device to finish operations
+before exiting `mainLoop` and destroying the window:
+
+```c++
+void mainLoop() {
+    while (!glfwWindowShouldClose(window)) {
+        glfwPollEvents();
+        drawFrame();
+    }
+
+    vkDeviceWaitIdle(device);
+}
+```
+
+You can also wait for operations in a specific command queue to be finished with
+`vkQueueWaitIdle`. These functions can be used as a very rudimentary way to
+perform synchronization. You'll see that the program now exits without problems
+when closing the window.
+
+## Conclusion
+
+A little over 900 lines of code later, we've finally gotten to the stage of seeing
+something pop up on the screen! Bootstrapping a Vulkan program is definitely a
+lot of work, but the take-away message is that Vulkan gives you an immense
+amount of control through its explicitness. I recommend you to take some time
+now to reread the code and build a mental model of the purpose of all of the
+Vulkan objects in the program and how they relate to each other. We'll be
+building on top of that knowledge to extend the functionality of the program
+from this point on.
+
+The next chapter will expand the render loop to handle multiple frames in flight.
+
+[C++ code](/code/15_hello_triangle.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
new file mode 100644
index 00000000..e2345e31
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
@@ -0,0 +1,176 @@
+## Frames in flight
+
+Right now our render loop has one glaring flaw. We are required to wait on the
+previous frame to finish before we can start rendering the next which results
+in unnecessary idling of the host. 
+
+<!-- insert diagram showing our current render loop and the 'multi frame in flight' render loop -->
+
+The way to fix this is to allow multiple frames to be *in-flight* at once, that 
+is to say, allow the rendering of one frame to not interfere with the recording
+of the next. How do we do this? Any resource that is accessed and modified
+during rendering must be duplicated. Thus, we need multiple command buffers,
+semaphores, and fences. In later chapters we will also add multiple instances
+of other resources, so we will see this concept reappear.
+
+Start by adding a constant at the top of the program that defines how many
+frames should be processed concurrently:
+
+```c++
+const int MAX_FRAMES_IN_FLIGHT = 2;
+```
+
+We choose the number 2 because we don't want the CPU to get *too* far ahead of
+the GPU. With 2 frames in flight, the CPU and the GPU can be working on their
+own tasks at the same time. If the CPU finishes early, it will wait till the
+GPU finishes rendering before submitting more work. With 3 or more frames in
+flight, the CPU could get ahead of the GPU, adding frames of latency.
+Generally, extra latency isn't desired. But giving the application control over
+the number of frames in flight is another example of Vulkan being explicit.
+
+Each frame should have its own command buffer, set of semaphores, and fence.
+Rename and then change them to be `std::vector`s of the objects:
+
+```c++
+std::vector<VkCommandBuffer> commandBuffers;
+
+...
+
+std::vector<VkSemaphore> imageAvailableSemaphores;
+std::vector<VkSemaphore> renderFinishedSemaphores;
+std::vector<VkFence> inFlightFences;
+```
+
+Then we need to create multiple command buffers. Rename `createCommandBuffer`
+to `createCommandBuffers`. Next we need to resize the command buffers vector
+to the size of `MAX_FRAMES_IN_FLIGHT`, alter the `VkCommandBufferAllocateInfo`
+to contain that many command buffers, and then change the destination to our
+vector of command buffers:
+
+```c++
+void createCommandBuffers() {
+    commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
+    ...
+    allocInfo.commandBufferCount = (uint32_t) commandBuffers.size();
+
+    if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
+        throw std::runtime_error("failed to allocate command buffers!");
+    }
+}
+```
+
+The `createSyncObjects` function should be changed to create all of the objects:
+
+```c++
+void createSyncObjects() {
+    imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+    renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+    inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
+
+    VkSemaphoreCreateInfo semaphoreInfo{};
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+    VkFenceCreateInfo fenceInfo{};
+    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+        if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS ||
+            vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS ||
+            vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
+
+            throw std::runtime_error("failed to create synchronization objects for a frame!");
+        }
+    }
+}
+```
+
+Similarly, they should also all be cleaned up:
+
+```c++
+void cleanup() {
+    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+        vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
+        vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
+        vkDestroyFence(device, inFlightFences[i], nullptr);
+    }
+
+    ...
+}
+```
+
+Remember, because command buffers are freed for us when we free the command
+pool, there is nothing extra to do for command buffer cleanup.
+
+To use the right objects every frame, we need to keep track of the current
+frame. We will use a frame index for that purpose:
+
+```c++
+uint32_t currentFrame = 0;
+```
+
+The `drawFrame` function can now be modified to use the right objects:
+
+```c++
+void drawFrame() {
+    vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+    vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+    vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
+
+    ...
+
+    vkResetCommandBuffer(commandBuffers[currentFrame],  0);
+    recordCommandBuffer(commandBuffers[currentFrame], imageIndex);
+
+    ...
+
+    submitInfo.pCommandBuffers = &commandBuffers[currentFrame];
+
+    ...
+
+    VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
+
+    ...
+
+    VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]};
+
+    ...
+
+    if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
+}
+```
+
+Of course, we shouldn't forget to advance to the next frame every time:
+
+```c++
+void drawFrame() {
+    ...
+
+    currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+}
+```
+
+By using the modulo (%) operator, we ensure that the frame index loops around
+after every `MAX_FRAMES_IN_FLIGHT` enqueued frames.
+
+<!-- Possibly use swapchain-image-count for renderFinished semaphores, as it can't
+be known with a fence whether the semaphore is ready for re-use. -->
+
+We've now implemented all the needed synchronization to ensure that there are
+no more than `MAX_FRAMES_IN_FLIGHT` frames of work enqueued and that these
+frames are not stepping over eachother. Note that it is fine for other parts of
+the code, like the final cleanup, to rely on more rough synchronization like
+`vkDeviceWaitIdle`. You should decide on which approach to use based on
+performance requirements.
+
+To learn more about synchronization through examples, have a look at [this extensive overview](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present) by Khronos.
+
+
+In the next chapter we'll deal with one more small thing that is required for a
+well-behaved Vulkan program.
+
+
+[C++ code](/code/16_frames_in_flight.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
new file mode 100644
index 00000000..5da07458
--- /dev/null
+++ b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
@@ -0,0 +1,280 @@
+## Introduction
+
+The application we have now successfully draws a triangle, but there are some
+circumstances that it isn't handling properly yet. It is possible for the window
+surface to change such that the swap chain is no longer compatible with it. One
+of the reasons that could cause this to happen is the size of the window
+changing. We have to catch these events and recreate the swap chain.
+
+## Recreating the swap chain
+
+Create a new `recreateSwapChain` function that calls `createSwapChain` and all
+of the creation functions for the objects that depend on the swap chain or the
+window size.
+
+```c++
+void recreateSwapChain() {
+    vkDeviceWaitIdle(device);
+
+    createSwapChain();
+    createImageViews();
+    createFramebuffers();
+}
+```
+
+We first call `vkDeviceWaitIdle`, because just like in the last chapter, we
+shouldn't touch resources that may still be in use. Obviously, we'll have to recreate 
+the swap chain itself. The image views need to be recreated because they are based 
+directly on the swap chain images. Finally, the framebuffers directly depend on the 
+swap chain images, and thus must be recreated as well.
+
+To make sure that the old versions of these objects are cleaned up before
+recreating them, we should move some of the cleanup code to a separate function
+that we can call from the `recreateSwapChain` function. Let's call it
+`cleanupSwapChain`:
+
+```c++
+void cleanupSwapChain() {
+
+}
+
+void recreateSwapChain() {
+    vkDeviceWaitIdle(device);
+
+    cleanupSwapChain();
+
+    createSwapChain();
+    createImageViews();
+    createFramebuffers();
+}
+```
+
+Note that we don't recreate the renderpass here for simplicity. In theory it can be possible for the swap chain image format to change during an applications' lifetime, e.g. when moving a window from an standard range to an high dynamic range monitor. This may require the application to recreate the renderpass to make sure the change between dynamic ranges is properly reflected.
+
+We'll move the cleanup code of all objects that are recreated as part of a swap
+chain refresh from `cleanup` to `cleanupSwapChain`:
+
+```c++
+void cleanupSwapChain() {
+    for (size_t i = 0; i < swapChainFramebuffers.size(); i++) {
+        vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr);
+    }
+
+    for (size_t i = 0; i < swapChainImageViews.size(); i++) {
+        vkDestroyImageView(device, swapChainImageViews[i], nullptr);
+    }
+
+    vkDestroySwapchainKHR(device, swapChain, nullptr);
+}
+
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyPipeline(device, graphicsPipeline, nullptr);
+    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+
+    vkDestroyRenderPass(device, renderPass, nullptr);
+
+    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+        vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
+        vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
+        vkDestroyFence(device, inFlightFences[i], nullptr);
+    }
+
+    vkDestroyCommandPool(device, commandPool, nullptr);
+
+    vkDestroyDevice(device, nullptr);
+
+    if (enableValidationLayers) {
+        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
+    }
+
+    vkDestroySurfaceKHR(instance, surface, nullptr);
+    vkDestroyInstance(instance, nullptr);
+
+    glfwDestroyWindow(window);
+
+    glfwTerminate();
+}
+```
+
+Note that in `chooseSwapExtent` we already query the new window resolution to
+make sure that the swap chain images have the (new) right size, so there's no
+need to modify `chooseSwapExtent` (remember that we already had to use
+`glfwGetFramebufferSize` to get the resolution of the surface in pixels when
+creating the swap chain).
+
+That's all it takes to recreate the swap chain! However, the disadvantage of
+this approach is that we need to stop all rendering before creating the new swap
+chain. It is possible to create a new swap chain while drawing commands on an
+image from the old swap chain are still in-flight. You need to pass the previous
+swap chain to the `oldSwapChain` field in the `VkSwapchainCreateInfoKHR` struct
+and destroy the old swap chain as soon as you've finished using it.
+
+## Suboptimal or out-of-date swap chain
+
+Now we just need to figure out when swap chain recreation is necessary and call
+our new `recreateSwapChain` function. Luckily, Vulkan will usually just tell us that the swap chain is no longer adequate during presentation. The `vkAcquireNextImageKHR` and
+`vkQueuePresentKHR` functions can return the following special values to
+indicate this.
+
+* `VK_ERROR_OUT_OF_DATE_KHR`: The swap chain has become incompatible with the
+surface and can no longer be used for rendering. Usually happens after a window resize.
+* `VK_SUBOPTIMAL_KHR`: The swap chain can still be used to successfully present
+to the surface, but the surface properties are no longer matched exactly.
+
+```c++
+VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
+
+if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+    recreateSwapChain();
+    return;
+} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+    throw std::runtime_error("failed to acquire swap chain image!");
+}
+```
+
+If the swap chain turns out to be out of date when attempting to acquire an
+image, then it is no longer possible to present to it. Therefore we should
+immediately recreate the swap chain and try again in the next `drawFrame` call.
+
+You could also decide to do that if the swap chain is suboptimal, but I've
+chosen to proceed anyway in that case because we've already acquired an image.
+Both `VK_SUCCESS` and `VK_SUBOPTIMAL_KHR` are considered "success" return codes.
+
+```c++
+result = vkQueuePresentKHR(presentQueue, &presentInfo);
+
+if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
+    recreateSwapChain();
+} else if (result != VK_SUCCESS) {
+    throw std::runtime_error("failed to present swap chain image!");
+}
+
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+The `vkQueuePresentKHR` function returns the same values with the same meaning.
+In this case we will also recreate the swap chain if it is suboptimal, because
+we want the best possible result.
+
+## Fixing a deadlock
+
+If we try to run the code now, it is possible to encounter a deadlock.
+Debugging the code, we find that the application reaches `vkWaitForFences` but
+never continues past it. This is because when `vkAcquireNextImageKHR` returns
+`VK_ERROR_OUT_OF_DATE_KHR`, we recreate the swapchain and then return from
+`drawFrame`. But before that happens, the current frame's fence was waited upon
+and reset. Since we return immediately, no work is submitted for execution and
+the fence will never be signaled, causing `vkWaitForFences` to halt forever.
+
+There is a simple fix thankfully. Delay resetting the fence until after we
+know for sure we will be submitting work with it. Thus, if we return early, the
+fence is still signaled and `vkWaitForFences` wont deadlock the next time we
+use the same fence object.
+
+The beginning of `drawFrame` should now look like this:
+```c++
+vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+
+uint32_t imageIndex;
+VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
+
+if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+    recreateSwapChain();
+    return;
+} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+    throw std::runtime_error("failed to acquire swap chain image!");
+}
+
+// Only reset the fence if we are submitting work
+vkResetFences(device, 1, &inFlightFences[currentFrame]);
+```
+
+## Handling resizes explicitly
+
+Although many drivers and platforms trigger `VK_ERROR_OUT_OF_DATE_KHR` automatically after a window resize, it is not guaranteed to happen. That's why we'll add some extra code to also handle resizes explicitly. First add a new member variable that flags that a resize has happened:
+
+```c++
+std::vector<VkFence> inFlightFences;
+
+bool framebufferResized = false;
+```
+
+The `drawFrame` function should then be modified to also check for this flag:
+
+```c++
+if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
+    framebufferResized = false;
+    recreateSwapChain();
+} else if (result != VK_SUCCESS) {
+    ...
+}
+```
+
+It is important to do this after `vkQueuePresentKHR` to ensure that the semaphores are in a consistent state, otherwise a signaled semaphore may never be properly waited upon. Now to actually detect resizes we can use the `glfwSetFramebufferSizeCallback` function in the GLFW framework to set up a callback:
+
+```c++
+void initWindow() {
+    glfwInit();
+
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+
+    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
+    glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
+}
+
+static void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
+
+}
+```
+
+The reason that we're creating a `static` function as a callback is because GLFW does not know how to properly call a member function with the right `this` pointer to our `HelloTriangleApplication` instance.
+
+However, we do get a reference to the `GLFWwindow` in the callback and there is another GLFW function that allows you to store an arbitrary pointer inside of it: `glfwSetWindowUserPointer`:
+
+```c++
+window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
+glfwSetWindowUserPointer(window, this);
+glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
+```
+
+This value can now be retrieved from within the callback with `glfwGetWindowUserPointer` to properly set the flag:
+
+```c++
+static void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
+    auto app = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(window));
+    app->framebufferResized = true;
+}
+```
+
+Now try to run the program and resize the window to see if the framebuffer is indeed resized properly with the window.
+
+## Handling minimization
+
+There is another case where a swap chain may become out of date and that is a special kind of window resizing: window minimization. This case is special because it will result in a frame buffer size of `0`. In this tutorial we will handle that by pausing until the window is in the foreground again by extending the `recreateSwapChain` function:
+
+```c++
+void recreateSwapChain() {
+    int width = 0, height = 0;
+    glfwGetFramebufferSize(window, &width, &height);
+    while (width == 0 || height == 0) {
+        glfwGetFramebufferSize(window, &width, &height);
+        glfwWaitEvents();
+    }
+
+    vkDeviceWaitIdle(device);
+
+    ...
+}
+```
+
+The initial call to `glfwGetFramebufferSize` handles the case where the size is already correct and `glfwWaitEvents` would have nothing to wait on.
+
+Congratulations, you've now finished your very first well-behaved Vulkan
+program! In the next chapter we're going to get rid of the hardcoded vertices in
+the vertex shader and actually use a vertex buffer.
+
+[C++ code](/code/17_swap_chain_recreation.cpp) /
+[Vertex shader](/code/09_shader_base.vert) /
+[Fragment shader](/code/09_shader_base.frag)
diff --git a/kr/04_Vertex_buffers/00_Vertex_input_description.md b/kr/04_Vertex_buffers/00_Vertex_input_description.md
new file mode 100644
index 00000000..e7da3e4f
--- /dev/null
+++ b/kr/04_Vertex_buffers/00_Vertex_input_description.md
@@ -0,0 +1,225 @@
+## Introduction
+
+In the next few chapters, we're going to replace the hardcoded vertex data in
+the vertex shader with a vertex buffer in memory. We'll start with the easiest
+approach of creating a CPU visible buffer and using `memcpy` to copy the vertex
+data into it directly, and after that we'll see how to use a staging buffer to
+copy the vertex data to high performance memory.
+
+## Vertex shader
+
+First change the vertex shader to no longer include the vertex data in the
+shader code itself. The vertex shader takes input from a vertex buffer using the
+`in` keyword.
+
+```glsl
+#version 450
+
+layout(location = 0) in vec2 inPosition;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+void main() {
+    gl_Position = vec4(inPosition, 0.0, 1.0);
+    fragColor = inColor;
+}
+```
+
+The `inPosition` and `inColor` variables are *vertex attributes*. They're
+properties that are specified per-vertex in the vertex buffer, just like we
+manually specified a position and color per vertex using the two arrays. Make
+sure to recompile the vertex shader!
+
+Just like `fragColor`, the `layout(location = x)` annotations assign indices to
+the inputs that we can later use to reference them. It is important to know that
+some types, like `dvec3` 64 bit vectors, use multiple *slots*. That means that
+the index after it must be at least 2 higher:
+
+```glsl
+layout(location = 0) in dvec3 inPosition;
+layout(location = 2) in vec3 inColor;
+```
+
+You can find more info about the layout qualifier in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)).
+
+## Vertex data
+
+We're moving the vertex data from the shader code to an array in the code of our
+program. Start by including the GLM library, which provides us with linear
+algebra related types like vectors and matrices. We're going to use these types
+to specify the position and color vectors.
+
+```c++
+#include <glm/glm.hpp>
+```
+
+Create a new structure called `Vertex` with the two attributes that we're going
+to use in the vertex shader inside it:
+
+```c++
+struct Vertex {
+    glm::vec2 pos;
+    glm::vec3 color;
+};
+```
+
+GLM conveniently provides us with C++ types that exactly match the vector types
+used in the shader language.
+
+```c++
+const std::vector<Vertex> vertices = {
+    {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
+    {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
+    {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
+};
+```
+
+Now use the `Vertex` structure to specify an array of vertex data. We're using
+exactly the same position and color values as before, but now they're combined
+into one array of vertices. This is known as *interleaving* vertex attributes.
+
+## Binding descriptions
+
+The next step is to tell Vulkan how to pass this data format to the vertex
+shader once it's been uploaded into GPU memory. There are two types of
+structures needed to convey this information.
+
+The first structure is `VkVertexInputBindingDescription` and we'll add a member
+function to the `Vertex` struct to populate it with the right data.
+
+```c++
+struct Vertex {
+    glm::vec2 pos;
+    glm::vec3 color;
+
+    static VkVertexInputBindingDescription getBindingDescription() {
+        VkVertexInputBindingDescription bindingDescription{};
+
+        return bindingDescription;
+    }
+};
+```
+
+A vertex binding describes at which rate to load data from memory throughout the
+vertices. It specifies the number of bytes between data entries and whether to
+move to the next data entry after each vertex or after each instance.
+
+```c++
+VkVertexInputBindingDescription bindingDescription{};
+bindingDescription.binding = 0;
+bindingDescription.stride = sizeof(Vertex);
+bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+```
+
+All of our per-vertex data is packed together in one array, so we're only going
+to have one binding. The `binding` parameter specifies the index of the binding
+in the array of bindings. The `stride` parameter specifies the number of bytes
+from one entry to the next, and the `inputRate` parameter can have one of the
+following values:
+
+* `VK_VERTEX_INPUT_RATE_VERTEX`: Move to the next data entry after each vertex
+* `VK_VERTEX_INPUT_RATE_INSTANCE`: Move to the next data entry after each
+instance
+
+We're not going to use instanced rendering, so we'll stick to per-vertex data.
+
+## Attribute descriptions
+
+The second structure that describes how to handle vertex input is
+`VkVertexInputAttributeDescription`. We're going to add another helper function
+to `Vertex` to fill in these structs.
+
+```c++
+#include <array>
+
+...
+
+static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
+    std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
+
+    return attributeDescriptions;
+}
+```
+
+As the function prototype indicates, there are going to be two of these
+structures. An attribute description struct describes how to extract a vertex
+attribute from a chunk of vertex data originating from a binding description. We
+have two attributes, position and color, so we need two attribute description
+structs.
+
+```c++
+attributeDescriptions[0].binding = 0;
+attributeDescriptions[0].location = 0;
+attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
+attributeDescriptions[0].offset = offsetof(Vertex, pos);
+```
+
+The `binding` parameter tells Vulkan from which binding the per-vertex data
+comes. The `location` parameter references the `location` directive of the
+input in the vertex shader. The input in the vertex shader with location `0` is
+the position, which has two 32-bit float components.
+
+The `format` parameter describes the type of data for the attribute. A bit
+confusingly, the formats are specified using the same enumeration as color
+formats. The following shader types and formats are commonly used together:
+
+* `float`: `VK_FORMAT_R32_SFLOAT`
+* `vec2`: `VK_FORMAT_R32G32_SFLOAT`
+* `vec3`: `VK_FORMAT_R32G32B32_SFLOAT`
+* `vec4`: `VK_FORMAT_R32G32B32A32_SFLOAT`
+
+As you can see, you should use the format where the amount of color channels
+matches the number of components in the shader data type. It is allowed to use
+more channels than the number of components in the shader, but they will be
+silently discarded. If the number of channels is lower than the number of
+components, then the BGA components will use default values of `(0, 0, 1)`. The
+color type (`SFLOAT`, `UINT`, `SINT`) and bit width should also match the type
+of the shader input. See the following examples:
+
+* `ivec2`: `VK_FORMAT_R32G32_SINT`, a 2-component vector of 32-bit signed
+integers
+* `uvec4`: `VK_FORMAT_R32G32B32A32_UINT`, a 4-component vector of 32-bit
+unsigned integers
+* `double`: `VK_FORMAT_R64_SFLOAT`, a double-precision (64-bit) float
+
+The `format` parameter implicitly defines the byte size of attribute data and
+the `offset` parameter specifies the number of bytes since the start of the
+per-vertex data to read from. The binding is loading one `Vertex` at a time and
+the position attribute (`pos`) is at an offset of `0` bytes from the beginning
+of this struct. This is automatically calculated using the `offsetof` macro.
+
+```c++
+attributeDescriptions[1].binding = 0;
+attributeDescriptions[1].location = 1;
+attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
+attributeDescriptions[1].offset = offsetof(Vertex, color);
+```
+
+The color attribute is described in much the same way.
+
+## Pipeline vertex input
+
+We now need to set up the graphics pipeline to accept vertex data in this format
+by referencing the structures in `createGraphicsPipeline`. Find the
+`vertexInputInfo` struct and modify it to reference the two descriptions:
+
+```c++
+auto bindingDescription = Vertex::getBindingDescription();
+auto attributeDescriptions = Vertex::getAttributeDescriptions();
+
+vertexInputInfo.vertexBindingDescriptionCount = 1;
+vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
+vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
+vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
+```
+
+The pipeline is now ready to accept vertex data in the format of the `vertices`
+container and pass it on to our vertex shader. If you run the program now with
+validation layers enabled, you'll see that it complains that there is no vertex
+buffer bound to the binding. The next step is to create a vertex buffer and move
+the vertex data to it so the GPU is able to access it.
+
+[C++ code](/code/18_vertex_input.cpp) /
+[Vertex shader](/code/18_shader_vertexbuffer.vert) /
+[Fragment shader](/code/18_shader_vertexbuffer.frag)
diff --git a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
new file mode 100644
index 00000000..77122c50
--- /dev/null
+++ b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
@@ -0,0 +1,342 @@
+## Introduction
+
+Buffers in Vulkan are regions of memory used for storing arbitrary data that can
+be read by the graphics card. They can be used to store vertex data, which we'll
+do in this chapter, but they can also be used for many other purposes that we'll
+explore in future chapters. Unlike the Vulkan objects we've been dealing with so
+far, buffers do not automatically allocate memory for themselves. The work from
+the previous chapters has shown that the Vulkan API puts the programmer in
+control of almost everything and memory management is one of those things.
+
+## Buffer creation
+
+Create a new function `createVertexBuffer` and call it from `initVulkan` right
+before `createCommandBuffers`.
+
+```c++
+void initVulkan() {
+    createInstance();
+    setupDebugMessenger();
+    createSurface();
+    pickPhysicalDevice();
+    createLogicalDevice();
+    createSwapChain();
+    createImageViews();
+    createRenderPass();
+    createGraphicsPipeline();
+    createFramebuffers();
+    createCommandPool();
+    createVertexBuffer();
+    createCommandBuffers();
+    createSyncObjects();
+}
+
+...
+
+void createVertexBuffer() {
+
+}
+```
+
+Creating a buffer requires us to fill a `VkBufferCreateInfo` structure.
+
+```c++
+VkBufferCreateInfo bufferInfo{};
+bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+bufferInfo.size = sizeof(vertices[0]) * vertices.size();
+```
+
+The first field of the struct is `size`, which specifies the size of the buffer
+in bytes. Calculating the byte size of the vertex data is straightforward with
+`sizeof`.
+
+```c++
+bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+```
+
+The second field is `usage`, which indicates for which purposes the data in the
+buffer is going to be used. It is possible to specify multiple purposes using a
+bitwise or. Our use case will be a vertex buffer, we'll look at other types of
+usage in future chapters.
+
+```c++
+bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+```
+
+Just like the images in the swap chain, buffers can also be owned by a specific
+queue family or be shared between multiple at the same time. The buffer will
+only be used from the graphics queue, so we can stick to exclusive access.
+
+The `flags` parameter is used to configure sparse buffer memory, which is not
+relevant right now. We'll leave it at the default value of `0`.
+
+We can now create the buffer with `vkCreateBuffer`. Define a class member to
+hold the buffer handle and call it `vertexBuffer`.
+
+```c++
+VkBuffer vertexBuffer;
+
+...
+
+void createVertexBuffer() {
+    VkBufferCreateInfo bufferInfo{};
+    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    bufferInfo.size = sizeof(vertices[0]) * vertices.size();
+    bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create vertex buffer!");
+    }
+}
+```
+
+The buffer should be available for use in rendering commands until the end of
+the program and it does not depend on the swap chain, so we'll clean it up in
+the original `cleanup` function:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyBuffer(device, vertexBuffer, nullptr);
+
+    ...
+}
+```
+
+## Memory requirements
+
+The buffer has been created, but it doesn't actually have any memory assigned to
+it yet. The first step of allocating memory for the buffer is to query its
+memory requirements using the aptly named `vkGetBufferMemoryRequirements`
+function.
+
+```c++
+VkMemoryRequirements memRequirements;
+vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
+```
+
+The `VkMemoryRequirements` struct has three fields:
+
+* `size`: The size of the required amount of memory in bytes, may differ from
+`bufferInfo.size`.
+* `alignment`: The offset in bytes where the buffer begins in the allocated
+region of memory, depends on `bufferInfo.usage` and `bufferInfo.flags`.
+* `memoryTypeBits`: Bit field of the memory types that are suitable for the
+buffer.
+
+Graphics cards can offer different types of memory to allocate from. Each type
+of memory varies in terms of allowed operations and performance characteristics.
+We need to combine the requirements of the buffer and our own application
+requirements to find the right type of memory to use. Let's create a new
+function `findMemoryType` for this purpose.
+
+```c++
+uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
+
+}
+```
+
+First we need to query info about the available types of memory using
+`vkGetPhysicalDeviceMemoryProperties`.
+
+```c++
+VkPhysicalDeviceMemoryProperties memProperties;
+vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
+```
+
+The `VkPhysicalDeviceMemoryProperties` structure has two arrays `memoryTypes`
+and `memoryHeaps`. Memory heaps are distinct memory resources like dedicated
+VRAM and swap space in RAM for when VRAM runs out. The different types of memory
+exist within these heaps. Right now we'll only concern ourselves with the type
+of memory and not the heap it comes from, but you can imagine that this can
+affect performance.
+
+Let's first find a memory type that is suitable for the buffer itself:
+
+```c++
+for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
+    if (typeFilter & (1 << i)) {
+        return i;
+    }
+}
+
+throw std::runtime_error("failed to find suitable memory type!");
+```
+
+The `typeFilter` parameter will be used to specify the bit field of memory types
+that are suitable. That means that we can find the index of a suitable memory
+type by simply iterating over them and checking if the corresponding bit is set
+to `1`.
+
+However, we're not just interested in a memory type that is suitable for the
+vertex buffer. We also need to be able to write our vertex data to that memory.
+The `memoryTypes` array consists of `VkMemoryType` structs that specify the heap
+and properties of each type of memory. The properties define special features
+of the memory, like being able to map it so we can write to it from the CPU.
+This property is indicated with `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, but we
+also need to use the `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` property. We'll see
+why when we map the memory.
+
+We can now modify the loop to also check for the support of this property:
+
+```c++
+for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
+    if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
+        return i;
+    }
+}
+```
+
+We may have more than one desirable property, so we should check if the result
+of the bitwise AND is not just non-zero, but equal to the desired properties bit
+field. If there is a memory type suitable for the buffer that also has all of
+the properties we need, then we return its index, otherwise we throw an
+exception.
+
+## Memory allocation
+
+We now have a way to determine the right memory type, so we can actually
+allocate the memory by filling in the `VkMemoryAllocateInfo` structure.
+
+```c++
+VkMemoryAllocateInfo allocInfo{};
+allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+allocInfo.allocationSize = memRequirements.size;
+allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+```
+
+Memory allocation is now as simple as specifying the size and type, both of
+which are derived from the memory requirements of the vertex buffer and the
+desired property. Create a class member to store the handle to the memory and
+allocate it with `vkAllocateMemory`.
+
+```c++
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+...
+
+if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
+    throw std::runtime_error("failed to allocate vertex buffer memory!");
+}
+```
+
+If memory allocation was successful, then we can now associate this memory with
+the buffer using `vkBindBufferMemory`:
+
+```c++
+vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+```
+
+The first three parameters are self-explanatory and the fourth parameter is the
+offset within the region of memory. Since this memory is allocated specifically
+for this the vertex buffer, the offset is simply `0`. If the offset is non-zero,
+then it is required to be divisible by `memRequirements.alignment`.
+
+Of course, just like dynamic memory allocation in C++, the memory should be
+freed at some point. Memory that is bound to a buffer object may be freed once
+the buffer is no longer used, so let's free it after the buffer has been
+destroyed:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyBuffer(device, vertexBuffer, nullptr);
+    vkFreeMemory(device, vertexBufferMemory, nullptr);
+```
+
+## Filling the vertex buffer
+
+It is now time to copy the vertex data to the buffer. This is done by [mapping
+the buffer memory](https://en.wikipedia.org/wiki/Memory-mapped_I/O) into CPU
+accessible memory with `vkMapMemory`.
+
+```c++
+void* data;
+vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
+```
+
+This function allows us to access a region of the specified memory resource
+defined by an offset and size. The offset and size here are `0` and
+`bufferInfo.size`, respectively. It is also possible to specify the special
+value `VK_WHOLE_SIZE` to map all of the memory. The second to last parameter can
+be used to specify flags, but there aren't any available yet in the current API.
+It must be set to the value `0`. The last parameter specifies the output for the
+pointer to the mapped memory.
+
+```c++
+void* data;
+vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
+    memcpy(data, vertices.data(), (size_t) bufferInfo.size);
+vkUnmapMemory(device, vertexBufferMemory);
+```
+
+You can now simply `memcpy` the vertex data to the mapped memory and unmap it
+again using `vkUnmapMemory`. Unfortunately the driver may not immediately copy
+the data into the buffer memory, for example because of caching. It is also
+possible that writes to the buffer are not visible in the mapped memory yet.
+There are two ways to deal with that problem:
+
+* Use a memory heap that is host coherent, indicated with
+`VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`
+* Call `vkFlushMappedMemoryRanges` after writing to the mapped memory, and
+call `vkInvalidateMappedMemoryRanges` before reading from the mapped memory
+
+We went for the first approach, which ensures that the mapped memory always
+matches the contents of the allocated memory. Do keep in mind that this may lead
+to slightly worse performance than explicit flushing, but we'll see why that
+doesn't matter in the next chapter.
+
+Flushing memory ranges or using a coherent memory heap means that the driver will be aware of our writes to the buffer, but it doesn't mean that they are actually visible on the GPU yet. The transfer of data to the GPU is an operation that happens in the background and the specification simply [tells us](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-submission-host-writes) that it is guaranteed to be complete as of the next call to `vkQueueSubmit`.
+
+## Binding the vertex buffer
+
+All that remains now is binding the vertex buffer during rendering operations.
+We're going to extend the `recordCommandBuffer` function to do that.
+
+```c++
+vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+
+VkBuffer vertexBuffers[] = {vertexBuffer};
+VkDeviceSize offsets[] = {0};
+vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
+
+vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0);
+```
+
+The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to
+bindings, like the one we set up in the previous chapter. The first two
+parameters, besides the command buffer, specify the offset and number of
+bindings we're going to specify vertex buffers for. The last two parameters
+specify the array of vertex buffers to bind and the byte offsets to start
+reading vertex data from. You should also change the call to `vkCmdDraw` to pass
+the number of vertices in the buffer as opposed to the hardcoded number `3`.
+
+Now run the program and you should see the familiar triangle again:
+
+![](/images/triangle.png)
+
+Try changing the color of the top vertex to white by modifying the `vertices`
+array:
+
+```c++
+const std::vector<Vertex> vertices = {
+    {{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}},
+    {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
+    {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
+};
+```
+
+Run the program again and you should see the following:
+
+![](/images/triangle_white.png)
+
+In the next chapter we'll look at a different way to copy vertex data to a
+vertex buffer that results in better performance, but takes some more work.
+
+[C++ code](/code/19_vertex_buffer.cpp) /
+[Vertex shader](/code/18_shader_vertexbuffer.vert) /
+[Fragment shader](/code/18_shader_vertexbuffer.frag)
diff --git a/kr/04_Vertex_buffers/02_Staging_buffer.md b/kr/04_Vertex_buffers/02_Staging_buffer.md
new file mode 100644
index 00000000..289e74d4
--- /dev/null
+++ b/kr/04_Vertex_buffers/02_Staging_buffer.md
@@ -0,0 +1,267 @@
+## Introduction
+
+The vertex buffer we have right now works correctly, but the memory type that
+allows us to access it from the CPU may not be the most optimal memory type for
+the graphics card itself to read from. The most optimal memory has the
+`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` flag and is usually not accessible by the
+CPU on dedicated graphics cards. In this chapter we're going to create two
+vertex buffers. One *staging buffer* in CPU accessible memory to upload the data
+from the vertex array to, and the final vertex buffer in device local memory.
+We'll then use a buffer copy command to move the data from the staging buffer to
+the actual vertex buffer.
+
+## Transfer queue
+
+The buffer copy command requires a queue family that supports transfer
+operations, which is indicated using `VK_QUEUE_TRANSFER_BIT`. The good news is
+that any queue family with `VK_QUEUE_GRAPHICS_BIT` or `VK_QUEUE_COMPUTE_BIT`
+capabilities already implicitly support `VK_QUEUE_TRANSFER_BIT` operations. The
+implementation is not required to explicitly list it in `queueFlags` in those
+cases.
+
+If you like a challenge, then you can still try to use a different queue family
+specifically for transfer operations. It will require you to make the following
+modifications to your program:
+
+* Modify `QueueFamilyIndices` and `findQueueFamilies` to explicitly look for a
+queue family with the `VK_QUEUE_TRANSFER_BIT` bit, but not the
+`VK_QUEUE_GRAPHICS_BIT`.
+* Modify `createLogicalDevice` to request a handle to the transfer queue
+* Create a second command pool for command buffers that are submitted on the
+transfer queue family
+* Change the `sharingMode` of resources to be `VK_SHARING_MODE_CONCURRENT` and
+specify both the graphics and transfer queue families
+* Submit any transfer commands like `vkCmdCopyBuffer` (which we'll be using in
+this chapter) to the transfer queue instead of the graphics queue
+
+It's a bit of work, but it'll teach you a lot about how resources are shared
+between queue families.
+
+## Abstracting buffer creation
+
+Because we're going to create multiple buffers in this chapter, it's a good idea
+to move buffer creation to a helper function. Create a new function
+`createBuffer` and move the code in `createVertexBuffer` (except mapping) to it.
+
+```c++
+void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
+    VkBufferCreateInfo bufferInfo{};
+    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    bufferInfo.size = size;
+    bufferInfo.usage = usage;
+    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create buffer!");
+    }
+
+    VkMemoryRequirements memRequirements;
+    vkGetBufferMemoryRequirements(device, buffer, &memRequirements);
+
+    VkMemoryAllocateInfo allocInfo{};
+    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    allocInfo.allocationSize = memRequirements.size;
+    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
+
+    if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
+        throw std::runtime_error("failed to allocate buffer memory!");
+    }
+
+    vkBindBufferMemory(device, buffer, bufferMemory, 0);
+}
+```
+
+Make sure to add parameters for the buffer size, memory properties and usage so
+that we can use this function to create many different types of buffers. The
+last two parameters are output variables to write the handles to.
+
+You can now remove the buffer creation and memory allocation code from
+`createVertexBuffer` and just call `createBuffer` instead:
+
+```c++
+void createVertexBuffer() {
+    VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
+    createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory);
+
+    void* data;
+    vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data);
+        memcpy(data, vertices.data(), (size_t) bufferSize);
+    vkUnmapMemory(device, vertexBufferMemory);
+}
+```
+
+Run your program to make sure that the vertex buffer still works properly.
+
+## Using a staging buffer
+
+We're now going to change `createVertexBuffer` to only use a host visible buffer
+as temporary buffer and use a device local one as actual vertex buffer.
+
+```c++
+void createVertexBuffer() {
+    VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
+
+    VkBuffer stagingBuffer;
+    VkDeviceMemory stagingBufferMemory;
+    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+
+    void* data;
+    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
+        memcpy(data, vertices.data(), (size_t) bufferSize);
+    vkUnmapMemory(device, stagingBufferMemory);
+
+    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);
+}
+```
+
+We're now using a new `stagingBuffer` with `stagingBufferMemory` for mapping and
+copying the vertex data. In this chapter we're going to use two new buffer usage
+flags:
+
+* `VK_BUFFER_USAGE_TRANSFER_SRC_BIT`: Buffer can be used as source in a memory
+transfer operation.
+* `VK_BUFFER_USAGE_TRANSFER_DST_BIT`: Buffer can be used as destination in a
+memory transfer operation.
+
+The `vertexBuffer` is now allocated from a memory type that is device local,
+which generally means that we're not able to use `vkMapMemory`. However, we can
+copy data from the `stagingBuffer` to the `vertexBuffer`. We have to indicate
+that we intend to do that by specifying the transfer source flag for the
+`stagingBuffer` and the transfer destination flag for the `vertexBuffer`, along
+with the vertex buffer usage flag.
+
+We're now going to write a function to copy the contents from one buffer to
+another, called `copyBuffer`.
+
+```c++
+void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
+
+}
+```
+
+Memory transfer operations are executed using command buffers, just like drawing
+commands. Therefore we must first allocate a temporary command buffer. You may
+wish to create a separate command pool for these kinds of short-lived buffers,
+because the implementation may be able to apply memory allocation optimizations.
+You should use the `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` flag during command
+pool generation in that case.
+
+```c++
+void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
+    VkCommandBufferAllocateInfo allocInfo{};
+    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    allocInfo.commandPool = commandPool;
+    allocInfo.commandBufferCount = 1;
+
+    VkCommandBuffer commandBuffer;
+    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
+}
+```
+
+And immediately start recording the command buffer:
+
+```c++
+VkCommandBufferBeginInfo beginInfo{};
+beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+vkBeginCommandBuffer(commandBuffer, &beginInfo);
+```
+
+We're only going to use the command buffer once and wait with returning from the function until the copy
+operation has finished executing. It's good practice to tell the driver about
+our intent using `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`.
+
+```c++
+VkBufferCopy copyRegion{};
+copyRegion.srcOffset = 0; // Optional
+copyRegion.dstOffset = 0; // Optional
+copyRegion.size = size;
+vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
+```
+
+Contents of buffers are transferred using the `vkCmdCopyBuffer` command. It
+takes the source and destination buffers as arguments, and an array of regions
+to copy. The regions are defined in `VkBufferCopy` structs and consist of a
+source buffer offset, destination buffer offset and size. It is not possible to
+specify `VK_WHOLE_SIZE` here, unlike the `vkMapMemory` command.
+
+```c++
+vkEndCommandBuffer(commandBuffer);
+```
+
+This command buffer only contains the copy command, so we can stop recording
+right after that. Now execute the command buffer to complete the transfer:
+
+```c++
+VkSubmitInfo submitInfo{};
+submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+submitInfo.commandBufferCount = 1;
+submitInfo.pCommandBuffers = &commandBuffer;
+
+vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
+vkQueueWaitIdle(graphicsQueue);
+```
+
+Unlike the draw commands, there are no events we need to wait on this time. We
+just want to execute the transfer on the buffers immediately. There are again
+two possible ways to wait on this transfer to complete. We could use a fence and
+wait with `vkWaitForFences`, or simply wait for the transfer queue to become
+idle with `vkQueueWaitIdle`. A fence would allow you to schedule multiple
+transfers simultaneously and wait for all of them complete, instead of executing
+one at a time. That may give the driver more opportunities to optimize.
+
+```c++
+vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+```
+
+Don't forget to clean up the command buffer used for the transfer operation.
+
+We can now call `copyBuffer` from the `createVertexBuffer` function to move the
+vertex data to the device local buffer:
+
+```c++
+createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);
+
+copyBuffer(stagingBuffer, vertexBuffer, bufferSize);
+```
+
+After copying the data from the staging buffer to the device buffer, we should
+clean it up:
+
+```c++
+    ...
+
+    copyBuffer(stagingBuffer, vertexBuffer, bufferSize);
+
+    vkDestroyBuffer(device, stagingBuffer, nullptr);
+    vkFreeMemory(device, stagingBufferMemory, nullptr);
+}
+```
+
+Run your program to verify that you're seeing the familiar triangle again. The
+improvement may not be visible right now, but its vertex data is now being
+loaded from high performance memory. This will matter when we're going to start
+rendering more complex geometry.
+
+## Conclusion
+
+It should be noted that in a real world application, you're not supposed to
+actually call `vkAllocateMemory` for every individual buffer. The maximum number
+of simultaneous memory allocations is limited by the `maxMemoryAllocationCount`
+physical device limit, which may be as low as `4096` even on high end hardware
+like an NVIDIA GTX 1080. The right way to allocate memory for a large number of
+objects at the same time is to create a custom allocator that splits up a single
+allocation among many different objects by using the `offset` parameters that
+we've seen in many functions.
+
+You can either implement such an allocator yourself, or use the
+[VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
+library provided by the GPUOpen initiative. However, for this tutorial it's okay
+to use a separate allocation for every resource, because we won't come close to
+hitting any of these limits for now.
+
+[C++ code](/code/20_staging_buffer.cpp) /
+[Vertex shader](/code/18_shader_vertexbuffer.vert) /
+[Fragment shader](/code/18_shader_vertexbuffer.frag)
diff --git a/kr/04_Vertex_buffers/03_Index_buffer.md b/kr/04_Vertex_buffers/03_Index_buffer.md
new file mode 100644
index 00000000..088263db
--- /dev/null
+++ b/kr/04_Vertex_buffers/03_Index_buffer.md
@@ -0,0 +1,179 @@
+## Introduction
+
+The 3D meshes you'll be rendering in a real world application will often share
+vertices between multiple triangles. This already happens even with something
+simple like drawing a rectangle:
+
+![](/images/vertex_vs_index.svg)
+
+Drawing a rectangle takes two triangles, which means that we need a vertex
+buffer with 6 vertices. The problem is that the data of two vertices needs to be
+duplicated resulting in 50% redundancy. It only gets worse with more complex
+meshes, where vertices are reused in an average number of 3 triangles. The
+solution to this problem is to use an *index buffer*.
+
+An index buffer is essentially an array of pointers into the vertex buffer. It
+allows you to reorder the vertex data, and reuse existing data for multiple
+vertices. The illustration above demonstrates what the index buffer would look
+like for the rectangle if we have a vertex buffer containing each of the four
+unique vertices. The first three indices define the upper-right triangle and the
+last three indices define the vertices for the bottom-left triangle.
+
+## Index buffer creation
+
+In this chapter we're going to modify the vertex data and add index data to
+draw a rectangle like the one in the illustration. Modify the vertex data to
+represent the four corners:
+
+```c++
+const std::vector<Vertex> vertices = {
+    {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
+    {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
+    {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
+    {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
+};
+```
+
+The top-left corner is red, top-right is green, bottom-right is blue and the
+bottom-left is white. We'll add a new array `indices` to represent the contents
+of the index buffer. It should match the indices in the illustration to draw the
+upper-right triangle and bottom-left triangle.
+
+```c++
+const std::vector<uint16_t> indices = {
+    0, 1, 2, 2, 3, 0
+};
+```
+
+It is possible to use either `uint16_t` or `uint32_t` for your index buffer
+depending on the number of entries in `vertices`. We can stick to `uint16_t` for
+now because we're using less than 65535 unique vertices.
+
+Just like the vertex data, the indices need to be uploaded into a `VkBuffer` for
+the GPU to be able to access them. Define two new class members to hold the
+resources for the index buffer:
+
+```c++
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+VkBuffer indexBuffer;
+VkDeviceMemory indexBufferMemory;
+```
+
+The `createIndexBuffer` function that we'll add now is almost identical to
+`createVertexBuffer`:
+
+```c++
+void initVulkan() {
+    ...
+    createVertexBuffer();
+    createIndexBuffer();
+    ...
+}
+
+void createIndexBuffer() {
+    VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();
+
+    VkBuffer stagingBuffer;
+    VkDeviceMemory stagingBufferMemory;
+    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+
+    void* data;
+    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
+    memcpy(data, indices.data(), (size_t) bufferSize);
+    vkUnmapMemory(device, stagingBufferMemory);
+
+    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);
+
+    copyBuffer(stagingBuffer, indexBuffer, bufferSize);
+
+    vkDestroyBuffer(device, stagingBuffer, nullptr);
+    vkFreeMemory(device, stagingBufferMemory, nullptr);
+}
+```
+
+There are only two notable differences. The `bufferSize` is now equal to the
+number of indices times the size of the index type, either `uint16_t` or
+`uint32_t`. The usage of the `indexBuffer` should be
+`VK_BUFFER_USAGE_INDEX_BUFFER_BIT` instead of
+`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`, which makes sense. Other than that, the
+process is exactly the same. We create a staging buffer to copy the contents of
+`indices` to and then copy it to the final device local index buffer.
+
+The index buffer should be cleaned up at the end of the program, just like the
+vertex buffer:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyBuffer(device, indexBuffer, nullptr);
+    vkFreeMemory(device, indexBufferMemory, nullptr);
+
+    vkDestroyBuffer(device, vertexBuffer, nullptr);
+    vkFreeMemory(device, vertexBufferMemory, nullptr);
+
+    ...
+}
+```
+
+## Using an index buffer
+
+Using an index buffer for drawing involves two changes to
+`recordCommandBuffer`. We first need to bind the index buffer, just like we did
+for the vertex buffer. The difference is that you can only have a single index
+buffer. It's unfortunately not possible to use different indices for each vertex
+attribute, so we do still have to completely duplicate vertex data even if just
+one attribute varies.
+
+```c++
+vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
+
+vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
+```
+
+An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer,
+a byte offset into it, and the type of index data as parameters. As mentioned
+before, the possible types are `VK_INDEX_TYPE_UINT16` and
+`VK_INDEX_TYPE_UINT32`.
+
+Just binding an index buffer doesn't change anything yet, we also need to change
+the drawing command to tell Vulkan to use the index buffer. Remove the
+`vkCmdDraw` line and replace it with `vkCmdDrawIndexed`:
+
+```c++
+vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
+```
+
+A call to this function is very similar to `vkCmdDraw`. The first two parameters
+specify the number of indices and the number of instances. We're not using
+instancing, so just specify `1` instance. The number of indices represents the
+number of vertices that will be passed to the vertex shader. The next parameter
+specifies an offset into the index buffer, using a value of `1` would cause the
+graphics card to start reading at the second index. The second to last parameter
+specifies an offset to add to the indices in the index buffer. The final
+parameter specifies an offset for instancing, which we're not using.
+
+Now run your program and you should see the following:
+
+![](/images/indexed_rectangle.png)
+
+You now know how to save memory by reusing vertices with index buffers. This
+will become especially important in a future chapter where we're going to load
+complex 3D models.
+
+The previous chapter already mentioned that you should allocate multiple
+resources like buffers from a single memory allocation, but in fact you should
+go a step further. [Driver developers recommend](https://developer.nvidia.com/vulkan-memory-management)
+that you also store multiple buffers, like the vertex and index buffer, into a
+single `VkBuffer` and use offsets in commands like `vkCmdBindVertexBuffers`. The
+advantage is that your data is more cache friendly in that case, because it's
+closer together. It is even possible to reuse the same chunk of memory for
+multiple resources if they are not used during the same render operations,
+provided that their data is refreshed, of course. This is known as *aliasing*
+and some Vulkan functions have explicit flags to specify that you want to do
+this.
+
+[C++ code](/code/21_index_buffer.cpp) /
+[Vertex shader](/code/18_shader_vertexbuffer.vert) /
+[Fragment shader](/code/18_shader_vertexbuffer.frag)
diff --git a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
new file mode 100644
index 00000000..b274cb38
--- /dev/null
+++ b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
@@ -0,0 +1,416 @@
+## Introduction
+
+We're now able to pass arbitrary attributes to the vertex shader for each
+vertex, but what about global variables? We're going to move on to 3D graphics
+from this chapter on and that requires a model-view-projection matrix. We could
+include it as vertex data, but that's a waste of memory and it would require us
+to update the vertex buffer whenever the transformation changes. The
+transformation could easily change every single frame.
+
+The right way to tackle this in Vulkan is to use *resource descriptors*. A
+descriptor is a way for shaders to freely access resources like buffers and
+images. We're going to set up a buffer that contains the transformation matrices
+and have the vertex shader access them through a descriptor. Usage of
+descriptors consists of three parts:
+
+* Specify a descriptor layout during pipeline creation
+* Allocate a descriptor set from a descriptor pool
+* Bind the descriptor set during rendering
+
+The *descriptor layout* specifies the types of resources that are going to be
+accessed by the pipeline, just like a render pass specifies the types of
+attachments that will be accessed. A *descriptor set* specifies the actual
+buffer or image resources that will be bound to the descriptors, just like a
+framebuffer specifies the actual image views to bind to render pass attachments.
+The descriptor set is then bound for the drawing commands just like the vertex
+buffers and framebuffer.
+
+There are many types of descriptors, but in this chapter we'll work with uniform
+buffer objects (UBO). We'll look at other types of descriptors in future
+chapters, but the basic process is the same. Let's say we have the data we want
+the vertex shader to have in a C struct like this:
+
+```c++
+struct UniformBufferObject {
+    glm::mat4 model;
+    glm::mat4 view;
+    glm::mat4 proj;
+};
+```
+
+Then we can copy the data to a `VkBuffer` and access it through a uniform buffer
+object descriptor from the vertex shader like this:
+
+```glsl
+layout(binding = 0) uniform UniformBufferObject {
+    mat4 model;
+    mat4 view;
+    mat4 proj;
+} ubo;
+
+void main() {
+    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
+    fragColor = inColor;
+}
+```
+
+We're going to update the model, view and projection matrices every frame to
+make the rectangle from the previous chapter spin around in 3D.
+
+## Vertex shader
+
+Modify the vertex shader to include the uniform buffer object like it was
+specified above. I will assume that you are familiar with MVP transformations.
+If you're not, see [the resource](https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/)
+mentioned in the first chapter.
+
+```glsl
+#version 450
+
+layout(binding = 0) uniform UniformBufferObject {
+    mat4 model;
+    mat4 view;
+    mat4 proj;
+} ubo;
+
+layout(location = 0) in vec2 inPosition;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+void main() {
+    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
+    fragColor = inColor;
+}
+```
+
+Note that the order of the `uniform`, `in` and `out` declarations doesn't
+matter. The `binding` directive is similar to the `location` directive for
+attributes. We're going to reference this binding in the descriptor layout. The
+line with `gl_Position` is changed to use the transformations to compute the
+final position in clip coordinates. Unlike the 2D triangles, the last component
+of the clip coordinates may not be `1`, which will result in a division when
+converted to the final normalized device coordinates on the screen. This is used
+in perspective projection as the *perspective division* and is essential for
+making closer objects look larger than objects that are further away.
+
+## Descriptor set layout
+
+The next step is to define the UBO on the C++ side and to tell Vulkan about this
+descriptor in the vertex shader.
+
+```c++
+struct UniformBufferObject {
+    glm::mat4 model;
+    glm::mat4 view;
+    glm::mat4 proj;
+};
+```
+
+We can exactly match the definition in the shader using data types in GLM. The
+data in the matrices is binary compatible with the way the shader expects it, so
+we can later just `memcpy` a `UniformBufferObject` to a `VkBuffer`.
+
+We need to provide details about every descriptor binding used in the shaders
+for pipeline creation, just like we had to do for every vertex attribute and its
+`location` index. We'll set up a new function to define all of this information
+called `createDescriptorSetLayout`. It should be called right before pipeline
+creation, because we're going to need it there.
+
+```c++
+void initVulkan() {
+    ...
+    createDescriptorSetLayout();
+    createGraphicsPipeline();
+    ...
+}
+
+...
+
+void createDescriptorSetLayout() {
+
+}
+```
+
+Every binding needs to be described through a `VkDescriptorSetLayoutBinding`
+struct.
+
+```c++
+void createDescriptorSetLayout() {
+    VkDescriptorSetLayoutBinding uboLayoutBinding{};
+    uboLayoutBinding.binding = 0;
+    uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    uboLayoutBinding.descriptorCount = 1;
+}
+```
+
+The first two fields specify the `binding` used in the shader and the type of
+descriptor, which is a uniform buffer object. It is possible for the shader
+variable to represent an array of uniform buffer objects, and `descriptorCount`
+specifies the number of values in the array. This could be used to specify a
+transformation for each of the bones in a skeleton for skeletal animation, for
+example. Our MVP transformation is in a single uniform buffer object, so we're
+using a `descriptorCount` of `1`.
+
+```c++
+uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+```
+
+We also need to specify in which shader stages the descriptor is going to be
+referenced. The `stageFlags` field can be a combination of `VkShaderStageFlagBits` values
+or the value `VK_SHADER_STAGE_ALL_GRAPHICS`. In our case, we're only referencing
+the descriptor from the vertex shader.
+
+```c++
+uboLayoutBinding.pImmutableSamplers = nullptr; // Optional
+```
+
+The `pImmutableSamplers` field is only relevant for image sampling related
+descriptors, which we'll look at later. You can leave this to its default value.
+
+All of the descriptor bindings are combined into a single
+`VkDescriptorSetLayout` object. Define a new class member above
+`pipelineLayout`:
+
+```c++
+VkDescriptorSetLayout descriptorSetLayout;
+VkPipelineLayout pipelineLayout;
+```
+
+We can then create it using `vkCreateDescriptorSetLayout`. This function accepts
+a simple `VkDescriptorSetLayoutCreateInfo` with the array of bindings:
+
+```c++
+VkDescriptorSetLayoutCreateInfo layoutInfo{};
+layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+layoutInfo.bindingCount = 1;
+layoutInfo.pBindings = &uboLayoutBinding;
+
+if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create descriptor set layout!");
+}
+```
+
+We need to specify the descriptor set layout during pipeline creation to tell
+Vulkan which descriptors the shaders will be using. Descriptor set layouts are
+specified in the pipeline layout object. Modify the `VkPipelineLayoutCreateInfo`
+to reference the layout object:
+
+```c++
+VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
+pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+pipelineLayoutInfo.setLayoutCount = 1;
+pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
+```
+
+You may be wondering why it's possible to specify multiple descriptor set
+layouts here, because a single one already includes all of the bindings. We'll
+get back to that in the next chapter, where we'll look into descriptor pools and
+descriptor sets.
+
+The descriptor layout should stick around while we may create new graphics
+pipelines i.e. until the program ends:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+    ...
+}
+```
+
+## Uniform buffer
+
+In the next chapter we'll specify the buffer that contains the UBO data for the
+shader, but we need to create this buffer first. We're going to copy new data to
+the uniform buffer every frame, so it doesn't really make any sense to have a
+staging buffer. It would just add extra overhead in this case and likely degrade
+performance instead of improving it.
+
+We should have multiple buffers, because multiple frames may be in flight at the same
+time and we don't want to update the buffer in preparation of the next frame while a
+previous one is still reading from it! Thus, we need to have as many uniform buffers
+as we have frames in flight, and write to a uniform buffer that is not currently
+being read by the GPU.
+
+To that end, add new class members for `uniformBuffers`, and `uniformBuffersMemory`:
+
+```c++
+VkBuffer indexBuffer;
+VkDeviceMemory indexBufferMemory;
+
+std::vector<VkBuffer> uniformBuffers;
+std::vector<VkDeviceMemory> uniformBuffersMemory;
+std::vector<void*> uniformBuffersMapped;
+```
+
+Similarly, create a new function `createUniformBuffers` that is called after
+`createIndexBuffer` and allocates the buffers:
+
+```c++
+void initVulkan() {
+    ...
+    createVertexBuffer();
+    createIndexBuffer();
+    createUniformBuffers();
+    ...
+}
+
+...
+
+void createUniformBuffers() {
+    VkDeviceSize bufferSize = sizeof(UniformBufferObject);
+
+    uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
+    uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
+    uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT);
+
+    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+        createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);
+
+        vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]);
+    }
+}
+```
+
+We map the buffer right after creation using `vkMapMemory` to get a pointer to which we can write the data later on. The buffer stays mapped to this pointer for the application's whole lifetime. This technique is called **"persistent mapping"** and works on all Vulkan implementations. Not having to map the buffer every time we need to update it increases performances, as mapping is not free.
+
+The uniform data will be used for all draw calls, so the buffer containing it should only be destroyed when we stop rendering.
+
+```c++
+void cleanup() {
+    ...
+
+    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+        vkDestroyBuffer(device, uniformBuffers[i], nullptr);
+        vkFreeMemory(device, uniformBuffersMemory[i], nullptr);
+    }
+
+    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+    ...
+
+}
+```
+
+## Updating uniform data
+
+Create a new function `updateUniformBuffer` and add a call to it from the `drawFrame` function before submitting the next frame:
+
+```c++
+void drawFrame() {
+    ...
+
+    updateUniformBuffer(currentFrame);
+
+    ...
+
+    VkSubmitInfo submitInfo{};
+    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+
+    ...
+}
+
+...
+
+void updateUniformBuffer(uint32_t currentImage) {
+
+}
+```
+
+This function will generate a new transformation every frame to make the
+geometry spin around. We need to include two new headers to implement this
+functionality:
+
+```c++
+#define GLM_FORCE_RADIANS
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+#include <chrono>
+```
+
+The `glm/gtc/matrix_transform.hpp` header exposes functions that can be used to
+generate model transformations like `glm::rotate`, view transformations like
+`glm::lookAt` and projection transformations like `glm::perspective`. The
+`GLM_FORCE_RADIANS` definition is necessary to make sure that functions like
+`glm::rotate` use radians as arguments, to avoid any possible confusion.
+
+The `chrono` standard library header exposes functions to do precise
+timekeeping. We'll use this to make sure that the geometry rotates 90 degrees
+per second regardless of frame rate.
+
+```c++
+void updateUniformBuffer(uint32_t currentImage) {
+    static auto startTime = std::chrono::high_resolution_clock::now();
+
+    auto currentTime = std::chrono::high_resolution_clock::now();
+    float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();
+}
+```
+
+The `updateUniformBuffer` function will start out with some logic to calculate
+the time in seconds since rendering has started with floating point accuracy.
+
+We will now define the model, view and projection transformations in the
+uniform buffer object. The model rotation will be a simple rotation around the
+Z-axis using the `time` variable:
+
+```c++
+UniformBufferObject ubo{};
+ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
+```
+
+The `glm::rotate` function takes an existing transformation, rotation angle and
+rotation axis as parameters. The `glm::mat4(1.0f)` constructor returns an
+identity matrix. Using a rotation angle of `time * glm::radians(90.0f)`
+accomplishes the purpose of rotation 90 degrees per second.
+
+```c++
+ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
+```
+
+For the view transformation I've decided to look at the geometry from above at a
+45 degree angle. The `glm::lookAt` function takes the eye position, center
+position and up axis as parameters.
+
+```c++
+ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f);
+```
+
+I've chosen to use a perspective projection with a 45 degree vertical
+field-of-view. The other parameters are the aspect ratio, near and far
+view planes. It is important to use the current swap chain extent to calculate
+the aspect ratio to take into account the new width and height of the window
+after a resize.
+
+```c++
+ubo.proj[1][1] *= -1;
+```
+
+GLM was originally designed for OpenGL, where the Y coordinate of the clip
+coordinates is inverted. The easiest way to compensate for that is to flip the
+sign on the scaling factor of the Y axis in the projection matrix. If you don't
+do this, then the image will be rendered upside down.
+
+All of the transformations are defined now, so we can copy the data in the
+uniform buffer object to the current uniform buffer. This happens in exactly the same
+way as we did for vertex buffers, except without a staging buffer. As noted earlier, we only map the uniform buffer once, so we can directly write to it without having to map again:
+
+```c++
+memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
+```
+
+Using a UBO this way is not the most efficient way to pass frequently changing
+values to the shader. A more efficient way to pass a small buffer of data to
+shaders are *push constants*. We may look at these in a future chapter.
+
+In the next chapter we'll look at descriptor sets, which will actually bind the
+`VkBuffer`s to the uniform buffer descriptors so that the shader can access this
+transformation data.
+
+[C++ code](/code/22_descriptor_layout.cpp) /
+[Vertex shader](/code/22_shader_ubo.vert) /
+[Fragment shader](/code/22_shader_ubo.frag)
diff --git a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
new file mode 100644
index 00000000..5e373f68
--- /dev/null
+++ b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
@@ -0,0 +1,391 @@
+## Introduction
+
+The descriptor layout from the previous chapter describes the type of
+descriptors that can be bound. In this chapter we're going to create
+a descriptor set for each `VkBuffer` resource to bind it to the
+uniform buffer descriptor.
+
+## Descriptor pool
+
+Descriptor sets can't be created directly, they must be allocated from a pool
+like command buffers. The equivalent for descriptor sets is unsurprisingly
+called a *descriptor pool*. We'll write a new function `createDescriptorPool`
+to set it up.
+
+```c++
+void initVulkan() {
+    ...
+    createUniformBuffers();
+    createDescriptorPool();
+    ...
+}
+
+...
+
+void createDescriptorPool() {
+
+}
+```
+
+We first need to describe which descriptor types our descriptor sets are going
+to contain and how many of them, using `VkDescriptorPoolSize` structures.
+
+```c++
+VkDescriptorPoolSize poolSize{};
+poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+poolSize.descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+```
+
+We will allocate one of these descriptors for every frame. This
+pool size structure is referenced by the main `VkDescriptorPoolCreateInfo`:
+
+```c++
+VkDescriptorPoolCreateInfo poolInfo{};
+poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+poolInfo.poolSizeCount = 1;
+poolInfo.pPoolSizes = &poolSize;
+```
+
+Aside from the maximum number of individual descriptors that are available, we
+also need to specify the maximum number of descriptor sets that may be
+allocated:
+
+```c++
+poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+```
+
+The structure has an optional flag similar to command pools that determines if
+individual descriptor sets can be freed or not:
+`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. We're not going to touch
+the descriptor set after creating it, so we don't need this flag. You can leave
+`flags` to its default value of `0`.
+
+```c++
+VkDescriptorPool descriptorPool;
+
+...
+
+if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create descriptor pool!");
+}
+```
+
+Add a new class member to store the handle of the descriptor pool and call
+`vkCreateDescriptorPool` to create it.
+
+## Descriptor set
+
+We can now allocate the descriptor sets themselves. Add a `createDescriptorSets`
+function for that purpose:
+
+```c++
+void initVulkan() {
+    ...
+    createDescriptorPool();
+    createDescriptorSets();
+    ...
+}
+
+...
+
+void createDescriptorSets() {
+
+}
+```
+
+A descriptor set allocation is described with a `VkDescriptorSetAllocateInfo`
+struct. You need to specify the descriptor pool to allocate from, the number of
+descriptor sets to allocate, and the descriptor layout to base them on:
+
+```c++
+std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout);
+VkDescriptorSetAllocateInfo allocInfo{};
+allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+allocInfo.descriptorPool = descriptorPool;
+allocInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+allocInfo.pSetLayouts = layouts.data();
+```
+
+In our case we will create one descriptor set for each frame in flight, all with the same layout.
+Unfortunately we do need all the copies of the layout because the next function expects an array matching the number of sets.
+
+Add a class member to hold the descriptor set handles and allocate them with
+`vkAllocateDescriptorSets`:
+
+```c++
+VkDescriptorPool descriptorPool;
+std::vector<VkDescriptorSet> descriptorSets;
+
+...
+
+descriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
+if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
+    throw std::runtime_error("failed to allocate descriptor sets!");
+}
+```
+
+You don't need to explicitly clean up descriptor sets, because they will be
+automatically freed when the descriptor pool is destroyed. The call to
+`vkAllocateDescriptorSets` will allocate descriptor sets, each with one uniform
+buffer descriptor.
+
+```c++
+void cleanup() {
+    ...
+    vkDestroyDescriptorPool(device, descriptorPool, nullptr);
+
+    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+    ...
+}
+```
+
+The descriptor sets have been allocated now, but the descriptors within still need
+to be configured. We'll now add a loop to populate every descriptor:
+
+```c++
+for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+
+}
+```
+
+Descriptors that refer to buffers, like our uniform buffer
+descriptor, are configured with a `VkDescriptorBufferInfo` struct. This
+structure specifies the buffer and the region within it that contains the data
+for the descriptor.
+
+```c++
+for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+    VkDescriptorBufferInfo bufferInfo{};
+    bufferInfo.buffer = uniformBuffers[i];
+    bufferInfo.offset = 0;
+    bufferInfo.range = sizeof(UniformBufferObject);
+}
+```
+
+If you're overwriting the whole buffer, like we are in this case, then it is also possible to use the `VK_WHOLE_SIZE` value for the range. The configuration of descriptors is updated using the `vkUpdateDescriptorSets`
+function, which takes an array of `VkWriteDescriptorSet` structs as parameter.
+
+```c++
+VkWriteDescriptorSet descriptorWrite{};
+descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+descriptorWrite.dstSet = descriptorSets[i];
+descriptorWrite.dstBinding = 0;
+descriptorWrite.dstArrayElement = 0;
+```
+
+The first two fields specify the descriptor set to update and the binding. We
+gave our uniform buffer binding index `0`. Remember that descriptors can be
+arrays, so we also need to specify the first index in the array that we want to
+update. We're not using an array, so the index is simply `0`.
+
+```c++
+descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+descriptorWrite.descriptorCount = 1;
+```
+
+We need to specify the type of descriptor again. It's possible to update
+multiple descriptors at once in an array, starting at index `dstArrayElement`.
+The `descriptorCount` field specifies how many array elements you want to
+update.
+
+```c++
+descriptorWrite.pBufferInfo = &bufferInfo;
+descriptorWrite.pImageInfo = nullptr; // Optional
+descriptorWrite.pTexelBufferView = nullptr; // Optional
+```
+
+The last field references an array with `descriptorCount` structs that actually
+configure the descriptors. It depends on the type of descriptor which one of the
+three you actually need to use. The `pBufferInfo` field is used for descriptors
+that refer to buffer data, `pImageInfo` is used for descriptors that refer to
+image data, and `pTexelBufferView` is used for descriptors that refer to buffer
+views. Our descriptor is based on buffers, so we're using `pBufferInfo`.
+
+```c++
+vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
+```
+
+The updates are applied using `vkUpdateDescriptorSets`. It accepts two kinds of
+arrays as parameters: an array of `VkWriteDescriptorSet` and an array of
+`VkCopyDescriptorSet`. The latter can be used to copy descriptors to each other,
+as its name implies.
+
+## Using descriptor sets
+
+We now need to update the `recordCommandBuffer` function to actually bind the
+right descriptor set for each frame to the descriptors in the shader with `vkCmdBindDescriptorSets`. This needs to be done before the `vkCmdDrawIndexed` call:
+
+```c++
+vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr);
+vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
+```
+
+Unlike vertex and index buffers, descriptor sets are not unique to graphics
+pipelines. Therefore we need to specify if we want to bind descriptor sets to
+the graphics or compute pipeline. The next parameter is the layout that the
+descriptors are based on. The next three parameters specify the index of the
+first descriptor set, the number of sets to bind, and the array of sets to bind.
+We'll get back to this in a moment. The last two parameters specify an array of
+offsets that are used for dynamic descriptors. We'll look at these in a future
+chapter.
+
+If you run your program now, then you'll notice that unfortunately nothing is
+visible. The problem is that because of the Y-flip we did in the projection
+matrix, the vertices are now being drawn in counter-clockwise order instead of
+clockwise order. This causes backface culling to kick in and prevents
+any geometry from being drawn. Go to the `createGraphicsPipeline` function and
+modify the `frontFace` in `VkPipelineRasterizationStateCreateInfo` to correct
+this:
+
+```c++
+rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
+rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+```
+
+Run your program again and you should now see the following:
+
+![](/images/spinning_quad.png)
+
+The rectangle has changed into a square because the projection matrix now
+corrects for aspect ratio. The `updateUniformBuffer` takes care of screen
+resizing, so we don't need to recreate the descriptor set in
+`recreateSwapChain`.
+
+## Alignment requirements
+
+One thing we've glossed over so far is how exactly the data in the C++ structure should match with the uniform definition in the shader. It seems obvious enough to simply use the same types in both:
+
+```c++
+struct UniformBufferObject {
+    glm::mat4 model;
+    glm::mat4 view;
+    glm::mat4 proj;
+};
+
+layout(binding = 0) uniform UniformBufferObject {
+    mat4 model;
+    mat4 view;
+    mat4 proj;
+} ubo;
+```
+
+However, that's not all there is to it. For example, try modifying the struct and shader to look like this:
+
+```c++
+struct UniformBufferObject {
+    glm::vec2 foo;
+    glm::mat4 model;
+    glm::mat4 view;
+    glm::mat4 proj;
+};
+
+layout(binding = 0) uniform UniformBufferObject {
+    vec2 foo;
+    mat4 model;
+    mat4 view;
+    mat4 proj;
+} ubo;
+```
+
+Recompile your shader and your program and run it and you'll find that the colorful square you worked so far has disappeared! That's because we haven't taken into account the *alignment requirements*.
+
+Vulkan expects the data in your structure to be aligned in memory in a specific way, for example:
+
+* Scalars have to be aligned by N (= 4 bytes given 32 bit floats).
+* A `vec2` must be aligned by 2N (= 8 bytes)
+* A `vec3` or `vec4` must be aligned by 4N (= 16 bytes)
+* A nested structure must be aligned by the base alignment of its members rounded up to a multiple of 16.
+* A `mat4` matrix must have the same alignment as a `vec4`.
+
+You can find the full list of alignment requirements in [the specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout).
+
+Our original shader with just three `mat4` fields already met the alignment requirements. As each `mat4` is 4 x 4 x 4 = 64 bytes in size, `model` has an offset of `0`, `view` has an offset of 64 and `proj` has an offset of 128. All of these are multiples of 16 and that's why it worked fine.
+
+The new structure starts with a `vec2` which is only 8 bytes in size and therefore throws off all of the offsets. Now `model` has an offset of `8`, `view` an offset of `72` and `proj` an offset of `136`, none of which are multiples of 16. To fix this problem we can use the [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) specifier introduced in C++11:
+
+```c++
+struct UniformBufferObject {
+    glm::vec2 foo;
+    alignas(16) glm::mat4 model;
+    glm::mat4 view;
+    glm::mat4 proj;
+};
+```
+
+If you now compile and run your program again you should see that the shader correctly receives its matrix values once again.
+
+Luckily there is a way to not have to think about these alignment requirements *most* of the time. We can define `GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` right before including GLM:
+
+```c++
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES
+#include <glm/glm.hpp>
+```
+
+This will force GLM to use a version of `vec2` and `mat4` that has the alignment requirements already specified for us. If you add this definition then you can remove the `alignas` specifier and your program should still work.
+
+Unfortunately this method can break down if you start using nested structures. Consider the following definition in the C++ code:
+
+```c++
+struct Foo {
+    glm::vec2 v;
+};
+
+struct UniformBufferObject {
+    Foo f1;
+    Foo f2;
+};
+```
+
+And the following shader definition:
+
+```c++
+struct Foo {
+    vec2 v;
+};
+
+layout(binding = 0) uniform UniformBufferObject {
+    Foo f1;
+    Foo f2;
+} ubo;
+```
+
+In this case `f2` will have an offset of `8` whereas it should have an offset of `16` since it is a nested structure. In this case you must specify the alignment yourself:
+
+```c++
+struct UniformBufferObject {
+    Foo f1;
+    alignas(16) Foo f2;
+};
+```
+
+These gotchas are a good reason to always be explicit about alignment. That way you won't be caught offguard by the strange symptoms of alignment errors.
+
+```c++
+struct UniformBufferObject {
+    alignas(16) glm::mat4 model;
+    alignas(16) glm::mat4 view;
+    alignas(16) glm::mat4 proj;
+};
+```
+
+Don't forget to recompile your shader after removing the `foo` field.
+
+## Multiple descriptor sets
+
+As some of the structures and function calls hinted at, it is actually possible
+to bind multiple descriptor sets simultaneously. You need to specify a descriptor layout for
+each descriptor set when creating the pipeline layout. Shaders can then
+reference specific descriptor sets like this:
+
+```c++
+layout(set = 0, binding = 0) uniform UniformBufferObject { ... }
+```
+
+You can use this feature to put descriptors that vary per-object and descriptors
+that are shared into separate descriptor sets. In that case you avoid rebinding
+most of the descriptors across draw calls which is potentially more efficient.
+
+[C++ code](/code/23_descriptor_sets.cpp) /
+[Vertex shader](/code/22_shader_ubo.vert) /
+[Fragment shader](/code/22_shader_ubo.frag)
diff --git a/kr/06_Texture_mapping/00_Images.md b/kr/06_Texture_mapping/00_Images.md
new file mode 100644
index 00000000..8c9967f6
--- /dev/null
+++ b/kr/06_Texture_mapping/00_Images.md
@@ -0,0 +1,769 @@
+## Introduction
+
+The geometry has been colored using per-vertex colors so far, which is a rather
+limited approach. In this part of the tutorial we're going to implement texture
+mapping to make the geometry look more interesting. This will also allow us to
+load and draw basic 3D models in a future chapter.
+
+Adding a texture to our application will involve the following steps:
+
+* Create an image object backed by device memory
+* Fill it with pixels from an image file
+* Create an image sampler
+* Add a combined image sampler descriptor to sample colors from the texture
+
+We've already worked with image objects before, but those were automatically
+created by the swap chain extension. This time we'll have to create one by
+ourselves. Creating an image and filling it with data is similar to vertex
+buffer creation. We'll start by creating a staging resource and filling it with
+pixel data and then we copy this to the final image object that we'll use for
+rendering. Although it is possible to create a staging image for this purpose,
+Vulkan also allows you to copy pixels from a `VkBuffer` to an image and the API
+for this is actually [faster on some hardware](https://developer.nvidia.com/vulkan-memory-management).
+We'll first create this buffer and fill it with pixel values, and then we'll
+create an image to copy the pixels to. Creating an image is not very different
+from creating buffers. It involves querying the memory requirements, allocating
+device memory and binding it, just like we've seen before.
+
+However, there is something extra that we'll have to take care of when working
+with images. Images can have different *layouts* that affect how the pixels are
+organized in memory. Due to the way graphics hardware works, simply storing the
+pixels row by row may not lead to the best performance, for example. When
+performing any operation on images, you must make sure that they have the layout
+that is optimal for use in that operation. We've actually already seen some of
+these layouts when we specified the render pass:
+
+* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Optimal for presentation
+* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Optimal as attachment for writing
+colors from the fragment shader
+* `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`: Optimal as source in a transfer
+operation, like `vkCmdCopyImageToBuffer`
+* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Optimal as destination in a transfer
+operation, like `vkCmdCopyBufferToImage`
+* `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`: Optimal for sampling from a shader
+
+One of the most common ways to transition the layout of an image is a *pipeline
+barrier*. Pipeline barriers are primarily used for synchronizing access to
+resources, like making sure that an image was written to before it is read, but
+they can also be used to transition layouts. In this chapter we'll see how
+pipeline barriers are used for this purpose. Barriers can additionally be used
+to transfer queue family ownership when using `VK_SHARING_MODE_EXCLUSIVE`.
+
+## Image library
+
+There are many libraries available for loading images, and you can even write
+your own code to load simple formats like BMP and PPM. In this tutorial we'll be
+using the stb_image library from the [stb collection](https://github.com/nothings/stb).
+The advantage of it is that all of the code is in a single file, so it doesn't
+require any tricky build configuration. Download `stb_image.h` and store it in a
+convenient location, like the directory where you saved GLFW and GLM. Add the
+location to your include path.
+
+**Visual Studio**
+
+Add the directory with `stb_image.h` in it to the `Additional Include
+Directories` paths.
+
+![](/images/include_dirs_stb.png)
+
+**Makefile**
+
+Add the directory with `stb_image.h` to the include directories for GCC:
+
+```text
+VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64
+STB_INCLUDE_PATH = /home/user/libraries/stb
+
+...
+
+CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH)
+```
+
+## Loading an image
+
+Include the image library like this:
+
+```c++
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+```
+
+The header only defines the prototypes of the functions by default. One code
+file needs to include the header with the `STB_IMAGE_IMPLEMENTATION` definition
+to include the function bodies, otherwise we'll get linking errors.
+
+```c++
+void initVulkan() {
+    ...
+    createCommandPool();
+    createTextureImage();
+    createVertexBuffer();
+    ...
+}
+
+...
+
+void createTextureImage() {
+
+}
+```
+
+Create a new function `createTextureImage` where we'll load an image and upload
+it into a Vulkan image object. We're going to use command buffers, so it should
+be called after `createCommandPool`.
+
+Create a new directory `textures` next to the `shaders` directory to store
+texture images in. We're going to load an image called `texture.jpg` from that
+directory. I've chosen to use the following
+[CC0 licensed image](https://pixabay.com/en/statue-sculpture-fig-historically-1275469/)
+resized to 512 x 512 pixels, but feel free to pick any image you want. The
+library supports most common image file formats, like JPEG, PNG, BMP and GIF.
+
+![](/images/texture.jpg)
+
+Loading an image with this library is really easy:
+
+```c++
+void createTextureImage() {
+    int texWidth, texHeight, texChannels;
+    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
+    VkDeviceSize imageSize = texWidth * texHeight * 4;
+
+    if (!pixels) {
+        throw std::runtime_error("failed to load texture image!");
+    }
+}
+```
+
+The `stbi_load` function takes the file path and number of channels to load as
+arguments. The `STBI_rgb_alpha` value forces the image to be loaded with an
+alpha channel, even if it doesn't have one, which is nice for consistency with
+other textures in the future. The middle three parameters are outputs for the
+width, height and actual number of channels in the image. The pointer that is
+returned is the first element in an array of pixel values. The pixels are laid
+out row by row with 4 bytes per pixel in the case of `STBI_rgb_alpha` for a
+total of `texWidth * texHeight * 4` values.
+
+## Staging buffer
+
+We're now going to create a buffer in host visible memory so that we can use
+`vkMapMemory` and copy the pixels to it. Add variables for this temporary buffer
+to the `createTextureImage` function:
+
+```c++
+VkBuffer stagingBuffer;
+VkDeviceMemory stagingBufferMemory;
+```
+
+The buffer should be in host visible memory so that we can map it and it should
+be usable as a transfer source so that we can copy it to an image later on:
+
+```c++
+createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+```
+
+We can then directly copy the pixel values that we got from the image loading
+library to the buffer:
+
+```c++
+void* data;
+vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
+    memcpy(data, pixels, static_cast<size_t>(imageSize));
+vkUnmapMemory(device, stagingBufferMemory);
+```
+
+Don't forget to clean up the original pixel array now:
+
+```c++
+stbi_image_free(pixels);
+```
+
+## Texture Image
+
+Although we could set up the shader to access the pixel values in the buffer,
+it's better to use image objects in Vulkan for this purpose. Image objects will
+make it easier and faster to retrieve colors by allowing us to use 2D
+coordinates, for one. Pixels within an image object are known as texels and
+we'll use that name from this point on. Add the following new class members:
+
+```c++
+VkImage textureImage;
+VkDeviceMemory textureImageMemory;
+```
+
+The parameters for an image are specified in a `VkImageCreateInfo` struct:
+
+```c++
+VkImageCreateInfo imageInfo{};
+imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+imageInfo.imageType = VK_IMAGE_TYPE_2D;
+imageInfo.extent.width = static_cast<uint32_t>(texWidth);
+imageInfo.extent.height = static_cast<uint32_t>(texHeight);
+imageInfo.extent.depth = 1;
+imageInfo.mipLevels = 1;
+imageInfo.arrayLayers = 1;
+```
+
+The image type, specified in the `imageType` field, tells Vulkan with what kind
+of coordinate system the texels in the image are going to be addressed. It is
+possible to create 1D, 2D and 3D images. One dimensional images can be used to
+store an array of data or gradient, two dimensional images are mainly used for
+textures, and three dimensional images can be used to store voxel volumes, for
+example. The `extent` field specifies the dimensions of the image, basically how
+many texels there are on each axis. That's why `depth` must be `1` instead of
+`0`. Our texture will not be an array and we won't be using mipmapping for now.
+
+```c++
+imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
+```
+
+Vulkan supports many possible image formats, but we should use the same format
+for the texels as the pixels in the buffer, otherwise the copy operation will
+fail.
+
+```c++
+imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+```
+
+The `tiling` field can have one of two values:
+
+* `VK_IMAGE_TILING_LINEAR`: Texels are laid out in row-major order like our
+`pixels` array
+* `VK_IMAGE_TILING_OPTIMAL`: Texels are laid out in an implementation defined
+order for optimal access
+
+Unlike the layout of an image, the tiling mode cannot be changed at a later
+time. If you want to be able to directly access texels in the memory of the
+image, then you must use `VK_IMAGE_TILING_LINEAR`. We will be using a staging
+buffer instead of a staging image, so this won't be necessary. We will be using
+`VK_IMAGE_TILING_OPTIMAL` for efficient access from the shader.
+
+```c++
+imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+```
+
+There are only two possible values for the `initialLayout` of an image:
+
+* `VK_IMAGE_LAYOUT_UNDEFINED`: Not usable by the GPU and the very first
+transition will discard the texels.
+* `VK_IMAGE_LAYOUT_PREINITIALIZED`: Not usable by the GPU, but the first
+transition will preserve the texels.
+
+There are few situations where it is necessary for the texels to be preserved
+during the first transition. One example, however, would be if you wanted to use
+an image as a staging image in combination with the `VK_IMAGE_TILING_LINEAR`
+layout. In that case, you'd want to upload the texel data to it and then
+transition the image to be a transfer source without losing the data. In our
+case, however, we're first going to transition the image to be a transfer
+destination and then copy texel data to it from a buffer object, so we don't
+need this property and can safely use `VK_IMAGE_LAYOUT_UNDEFINED`.
+
+```c++
+imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+```
+
+The `usage` field has the same semantics as the one during buffer creation. The
+image is going to be used as destination for the buffer copy, so it should be
+set up as a transfer destination. We also want to be able to access the image
+from the shader to color our mesh, so the usage should include
+`VK_IMAGE_USAGE_SAMPLED_BIT`.
+
+```c++
+imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+```
+
+The image will only be used by one queue family: the one that supports graphics
+(and therefore also) transfer operations.
+
+```c++
+imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+imageInfo.flags = 0; // Optional
+```
+
+The `samples` flag is related to multisampling. This is only relevant for images
+that will be used as attachments, so stick to one sample. There are some
+optional flags for images that are related to sparse images. Sparse images are
+images where only certain regions are actually backed by memory. If you were
+using a 3D texture for a voxel terrain, for example, then you could use this to
+avoid allocating memory to store large volumes of "air" values. We won't be
+using it in this tutorial, so leave it to its default value of `0`.
+
+```c++
+if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create image!");
+}
+```
+
+The image is created using `vkCreateImage`, which doesn't have any particularly
+noteworthy parameters. It is possible that the `VK_FORMAT_R8G8B8A8_SRGB` format
+is not supported by the graphics hardware. You should have a list of acceptable
+alternatives and go with the best one that is supported. However, support for
+this particular format is so widespread that we'll skip this step. Using
+different formats would also require annoying conversions. We will get back to
+this in the depth buffer chapter, where we'll implement such a system.
+
+```c++
+VkMemoryRequirements memRequirements;
+vkGetImageMemoryRequirements(device, textureImage, &memRequirements);
+
+VkMemoryAllocateInfo allocInfo{};
+allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+allocInfo.allocationSize = memRequirements.size;
+allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) {
+    throw std::runtime_error("failed to allocate image memory!");
+}
+
+vkBindImageMemory(device, textureImage, textureImageMemory, 0);
+```
+
+Allocating memory for an image works in exactly the same way as allocating
+memory for a buffer. Use `vkGetImageMemoryRequirements` instead of
+`vkGetBufferMemoryRequirements`, and use `vkBindImageMemory` instead of
+`vkBindBufferMemory`.
+
+This function is already getting quite large and there'll be a need to create
+more images in later chapters, so we should abstract image creation into a
+`createImage` function, like we did for buffers. Create the function and move
+the image object creation and memory allocation to it:
+
+```c++
+void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
+    VkImageCreateInfo imageInfo{};
+    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    imageInfo.imageType = VK_IMAGE_TYPE_2D;
+    imageInfo.extent.width = width;
+    imageInfo.extent.height = height;
+    imageInfo.extent.depth = 1;
+    imageInfo.mipLevels = 1;
+    imageInfo.arrayLayers = 1;
+    imageInfo.format = format;
+    imageInfo.tiling = tiling;
+    imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    imageInfo.usage = usage;
+    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create image!");
+    }
+
+    VkMemoryRequirements memRequirements;
+    vkGetImageMemoryRequirements(device, image, &memRequirements);
+
+    VkMemoryAllocateInfo allocInfo{};
+    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    allocInfo.allocationSize = memRequirements.size;
+    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
+
+    if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) {
+        throw std::runtime_error("failed to allocate image memory!");
+    }
+
+    vkBindImageMemory(device, image, imageMemory, 0);
+}
+```
+
+I've made the width, height, format, tiling mode, usage, and memory properties
+parameters, because these will all vary between the images we'll be creating
+throughout this tutorial.
+
+The `createTextureImage` function can now be simplified to:
+
+```c++
+void createTextureImage() {
+    int texWidth, texHeight, texChannels;
+    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
+    VkDeviceSize imageSize = texWidth * texHeight * 4;
+
+    if (!pixels) {
+        throw std::runtime_error("failed to load texture image!");
+    }
+
+    VkBuffer stagingBuffer;
+    VkDeviceMemory stagingBufferMemory;
+    createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+
+    void* data;
+    vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
+        memcpy(data, pixels, static_cast<size_t>(imageSize));
+    vkUnmapMemory(device, stagingBufferMemory);
+
+    stbi_image_free(pixels);
+
+    createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
+}
+```
+
+## Layout transitions
+
+The function we're going to write now involves recording and executing a command
+buffer again, so now's a good time to move that logic into a helper function or
+two:
+
+```c++
+VkCommandBuffer beginSingleTimeCommands() {
+    VkCommandBufferAllocateInfo allocInfo{};
+    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    allocInfo.commandPool = commandPool;
+    allocInfo.commandBufferCount = 1;
+
+    VkCommandBuffer commandBuffer;
+    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
+
+    VkCommandBufferBeginInfo beginInfo{};
+    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+    vkBeginCommandBuffer(commandBuffer, &beginInfo);
+
+    return commandBuffer;
+}
+
+void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
+    vkEndCommandBuffer(commandBuffer);
+
+    VkSubmitInfo submitInfo{};
+    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo.commandBufferCount = 1;
+    submitInfo.pCommandBuffers = &commandBuffer;
+
+    vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
+    vkQueueWaitIdle(graphicsQueue);
+
+    vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+}
+```
+
+The code for these functions is based on the existing code in `copyBuffer`. You
+can now simplify that function to:
+
+```c++
+void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
+    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+    VkBufferCopy copyRegion{};
+    copyRegion.size = size;
+    vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
+
+    endSingleTimeCommands(commandBuffer);
+}
+```
+
+If we were still using buffers, then we could now write a function to record and
+execute `vkCmdCopyBufferToImage` to finish the job, but this command requires
+the image to be in the right layout first. Create a new function to handle
+layout transitions:
+
+```c++
+void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) {
+    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+    endSingleTimeCommands(commandBuffer);
+}
+```
+
+One of the most common ways to perform layout transitions is using an *image
+memory barrier*. A pipeline barrier like that is generally used to synchronize
+access to resources, like ensuring that a write to a buffer completes before
+reading from it, but it can also be used to transition image layouts and
+transfer queue family ownership when `VK_SHARING_MODE_EXCLUSIVE` is used. There
+is an equivalent *buffer memory barrier* to do this for buffers.
+
+```c++
+VkImageMemoryBarrier barrier{};
+barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+barrier.oldLayout = oldLayout;
+barrier.newLayout = newLayout;
+```
+
+The first two fields specify layout transition. It is possible to use
+`VK_IMAGE_LAYOUT_UNDEFINED` as `oldLayout` if you don't care about the existing
+contents of the image.
+
+```c++
+barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+```
+
+If you are using the barrier to transfer queue family ownership, then these two
+fields should be the indices of the queue families. They must be set to
+`VK_QUEUE_FAMILY_IGNORED` if you don't want to do this (not the default value!).
+
+```c++
+barrier.image = image;
+barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+barrier.subresourceRange.baseMipLevel = 0;
+barrier.subresourceRange.levelCount = 1;
+barrier.subresourceRange.baseArrayLayer = 0;
+barrier.subresourceRange.layerCount = 1;
+```
+
+The `image` and `subresourceRange` specify the image that is affected and the
+specific part of the image. Our image is not an array and does not have mipmapping
+levels, so only one level and layer are specified.
+
+```c++
+barrier.srcAccessMask = 0; // TODO
+barrier.dstAccessMask = 0; // TODO
+```
+
+Barriers are primarily used for synchronization purposes, so you must specify
+which types of operations that involve the resource must happen before the
+barrier, and which operations that involve the resource must wait on the
+barrier. We need to do that despite already using `vkQueueWaitIdle` to manually
+synchronize. The right values depend on the old and new layout, so we'll get
+back to this once we've figured out which transitions we're going to use.
+
+```c++
+vkCmdPipelineBarrier(
+    commandBuffer,
+    0 /* TODO */, 0 /* TODO */,
+    0,
+    0, nullptr,
+    0, nullptr,
+    1, &barrier
+);
+```
+
+All types of pipeline barriers are submitted using the same function. The first
+parameter after the command buffer specifies in which pipeline stage the
+operations occur that should happen before the barrier. The second parameter
+specifies the pipeline stage in which operations will wait on the barrier. The
+pipeline stages that you are allowed to specify before and after the barrier
+depend on how you use the resource before and after the barrier. The allowed
+values are listed in [this table](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-access-types-supported)
+of the specification. For example, if you're going to read from a uniform after
+the barrier, you would specify a usage of `VK_ACCESS_UNIFORM_READ_BIT` and the
+earliest shader that will read from the uniform as pipeline stage, for example
+`VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT`. It would not make sense to specify
+a non-shader pipeline stage for this type of usage and the validation layers
+will warn you when you specify a pipeline stage that does not match the type of
+usage.
+
+The third parameter is either `0` or `VK_DEPENDENCY_BY_REGION_BIT`. The latter
+turns the barrier into a per-region condition. That means that the
+implementation is allowed to already begin reading from the parts of a resource
+that were written so far, for example.
+
+The last three pairs of parameters reference arrays of pipeline barriers of the
+three available types: memory barriers, buffer memory barriers, and image memory
+barriers like the one we're using here. Note that we're not using the `VkFormat`
+parameter yet, but we'll be using that one for special transitions in the depth
+buffer chapter.
+
+## Copying buffer to image
+
+Before we get back to `createTextureImage`, we're going to write one more helper
+function: `copyBufferToImage`:
+
+```c++
+void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
+    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+    endSingleTimeCommands(commandBuffer);
+}
+```
+
+Just like with buffer copies, you need to specify which part of the buffer is
+going to be copied to which part of the image. This happens through
+`VkBufferImageCopy` structs:
+
+```c++
+VkBufferImageCopy region{};
+region.bufferOffset = 0;
+region.bufferRowLength = 0;
+region.bufferImageHeight = 0;
+
+region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+region.imageSubresource.mipLevel = 0;
+region.imageSubresource.baseArrayLayer = 0;
+region.imageSubresource.layerCount = 1;
+
+region.imageOffset = {0, 0, 0};
+region.imageExtent = {
+    width,
+    height,
+    1
+};
+```
+
+Most of these fields are self-explanatory. The `bufferOffset` specifies the byte
+offset in the buffer at which the pixel values start. The `bufferRowLength` and
+`bufferImageHeight` fields specify how the pixels are laid out in memory. For
+example, you could have some padding bytes between rows of the image. Specifying
+`0` for both indicates that the pixels are simply tightly packed like they are
+in our case. The `imageSubresource`, `imageOffset` and `imageExtent` fields
+indicate to which part of the image we want to copy the pixels.
+
+Buffer to image copy operations are enqueued using the `vkCmdCopyBufferToImage`
+function:
+
+```c++
+vkCmdCopyBufferToImage(
+    commandBuffer,
+    buffer,
+    image,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    1,
+    &region
+);
+```
+
+The fourth parameter indicates which layout the image is currently using. I'm
+assuming here that the image has already been transitioned to the layout that is
+optimal for copying pixels to. Right now we're only copying one chunk of pixels
+to the whole image, but it's possible to specify an array of `VkBufferImageCopy`
+to perform many different copies from this buffer to the image in one operation.
+
+## Preparing the texture image
+
+We now have all of the tools we need to finish setting up the texture image, so
+we're going back to the `createTextureImage` function. The last thing we did
+there was creating the texture image. The next step is to copy the staging
+buffer to the texture image. This involves two steps:
+
+* Transition the texture image to `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`
+* Execute the buffer to image copy operation
+
+This is easy to do with the functions we just created:
+
+```c++
+transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
+```
+
+The image was created with the `VK_IMAGE_LAYOUT_UNDEFINED` layout, so that one
+should be specified as old layout when transitioning `textureImage`. Remember
+that we can do this because we don't care about its contents before performing
+the copy operation.
+
+To be able to start sampling from the texture image in the shader, we need one
+last transition to prepare it for shader access:
+
+```c++
+transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+```
+
+## Transition barrier masks
+
+If you run your application with validation layers enabled now, then you'll see that
+it complains about the access masks and pipeline stages in
+`transitionImageLayout` being invalid. We still need to set those based on the
+layouts in the transition.
+
+There are two transitions we need to handle:
+
+* Undefined → transfer destination: transfer writes that don't need to wait on
+anything
+* Transfer destination → shader reading: shader reads should wait on transfer
+writes, specifically the shader reads in the fragment shader, because that's
+where we're going to use the texture
+
+These rules are specified using the following access masks and pipeline stages:
+
+```c++
+VkPipelineStageFlags sourceStage;
+VkPipelineStageFlags destinationStage;
+
+if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+    barrier.srcAccessMask = 0;
+    barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+    sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+    destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+} else {
+    throw std::invalid_argument("unsupported layout transition!");
+}
+
+vkCmdPipelineBarrier(
+    commandBuffer,
+    sourceStage, destinationStage,
+    0,
+    0, nullptr,
+    0, nullptr,
+    1, &barrier
+);
+```
+
+As you can see in the aforementioned table, transfer writes must occur in the
+pipeline transfer stage. Since the writes don't have to wait on anything, you
+may specify an empty access mask and the earliest possible pipeline stage
+`VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` for the pre-barrier operations. It should be
+noted that `VK_PIPELINE_STAGE_TRANSFER_BIT` is not a *real* stage within the
+graphics and compute pipelines. It is more of a pseudo-stage where transfers
+happen. See [the documentation](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#VkPipelineStageFlagBits)
+for more information and other examples of pseudo-stages.
+
+The image will be written in the same pipeline stage and subsequently read by
+the fragment shader, which is why we specify shader reading access in the
+fragment shader pipeline stage.
+
+If we need to do more transitions in the future, then we'll extend the function.
+The application should now run successfully, although there are of course no
+visual changes yet.
+
+One thing to note is that command buffer submission results in implicit
+`VK_ACCESS_HOST_WRITE_BIT` synchronization at the beginning. Since the
+`transitionImageLayout` function executes a command buffer with only a single
+command, you could use this implicit synchronization and set `srcAccessMask` to
+`0` if you ever needed a `VK_ACCESS_HOST_WRITE_BIT` dependency in a layout
+transition. It's up to you if you want to be explicit about it or not, but I'm
+personally not a fan of relying on these OpenGL-like "hidden" operations.
+
+There is actually a special type of image layout that supports all operations,
+`VK_IMAGE_LAYOUT_GENERAL`. The problem with it, of course, is that it doesn't
+necessarily offer the best performance for any operation. It is required for
+some special cases, like using an image as both input and output, or for reading
+an image after it has left the preinitialized layout.
+
+All of the helper functions that submit commands so far have been set up to
+execute synchronously by waiting for the queue to become idle. For practical
+applications it is recommended to combine these operations in a single command
+buffer and execute them asynchronously for higher throughput, especially the
+transitions and copy in the `createTextureImage` function. Try to experiment
+with this by creating a `setupCommandBuffer` that the helper functions record
+commands into, and add a `flushSetupCommands` to execute the commands that have
+been recorded so far. It's best to do this after the texture mapping works to
+check if the texture resources are still set up correctly.
+
+## Cleanup
+
+Finish the `createTextureImage` function by cleaning up the staging buffer and
+its memory at the end:
+
+```c++
+    transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+    vkDestroyBuffer(device, stagingBuffer, nullptr);
+    vkFreeMemory(device, stagingBufferMemory, nullptr);
+}
+```
+
+The main texture image is used until the end of the program:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyImage(device, textureImage, nullptr);
+    vkFreeMemory(device, textureImageMemory, nullptr);
+
+    ...
+}
+```
+
+The image now contains the texture, but we still need a way to access it from
+the graphics pipeline. We'll work on that in the next chapter.
+
+[C++ code](/code/24_texture_image.cpp) /
+[Vertex shader](/code/22_shader_ubo.vert) /
+[Fragment shader](/code/22_shader_ubo.frag)
diff --git a/kr/06_Texture_mapping/01_Image_view_and_sampler.md b/kr/06_Texture_mapping/01_Image_view_and_sampler.md
new file mode 100644
index 00000000..9d98c9e4
--- /dev/null
+++ b/kr/06_Texture_mapping/01_Image_view_and_sampler.md
@@ -0,0 +1,369 @@
+In this chapter we're going to create two more resources that are needed for the
+graphics pipeline to sample an image. The first resource is one that we've
+already seen before while working with the swap chain images, but the second one
+is new - it relates to how the shader will read texels from the image.
+
+## Texture image view
+
+We've seen before, with the swap chain images and the framebuffer, that images
+are accessed through image views rather than directly. We will also need to
+create such an image view for the texture image.
+
+Add a class member to hold a `VkImageView` for the texture image and create a
+new function `createTextureImageView` where we'll create it:
+
+```c++
+VkImageView textureImageView;
+
+...
+
+void initVulkan() {
+    ...
+    createTextureImage();
+    createTextureImageView();
+    createVertexBuffer();
+    ...
+}
+
+...
+
+void createTextureImageView() {
+
+}
+```
+
+The code for this function can be based directly on `createImageViews`. The only
+two changes you have to make are the `format` and the `image`:
+
+```c++
+VkImageViewCreateInfo viewInfo{};
+viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+viewInfo.image = textureImage;
+viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
+viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+viewInfo.subresourceRange.baseMipLevel = 0;
+viewInfo.subresourceRange.levelCount = 1;
+viewInfo.subresourceRange.baseArrayLayer = 0;
+viewInfo.subresourceRange.layerCount = 1;
+```
+
+I've left out the explicit `viewInfo.components` initialization, because
+`VK_COMPONENT_SWIZZLE_IDENTITY` is defined as `0` anyway. Finish creating the
+image view by calling `vkCreateImageView`:
+
+```c++
+if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create texture image view!");
+}
+```
+
+Because so much of the logic is duplicated from `createImageViews`, you may wish
+to abstract it into a new `createImageView` function:
+
+```c++
+VkImageView createImageView(VkImage image, VkFormat format) {
+    VkImageViewCreateInfo viewInfo{};
+    viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    viewInfo.image = image;
+    viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    viewInfo.format = format;
+    viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    viewInfo.subresourceRange.baseMipLevel = 0;
+    viewInfo.subresourceRange.levelCount = 1;
+    viewInfo.subresourceRange.baseArrayLayer = 0;
+    viewInfo.subresourceRange.layerCount = 1;
+
+    VkImageView imageView;
+    if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create image view!");
+    }
+
+    return imageView;
+}
+```
+
+The `createTextureImageView` function can now be simplified to:
+
+```c++
+void createTextureImageView() {
+    textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB);
+}
+```
+
+And `createImageViews` can be simplified to:
+
+```c++
+void createImageViews() {
+    swapChainImageViews.resize(swapChainImages.size());
+
+    for (uint32_t i = 0; i < swapChainImages.size(); i++) {
+        swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat);
+    }
+}
+```
+
+Make sure to destroy the image view at the end of the program, right before
+destroying the image itself:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroyImageView(device, textureImageView, nullptr);
+
+    vkDestroyImage(device, textureImage, nullptr);
+    vkFreeMemory(device, textureImageMemory, nullptr);
+```
+
+## Samplers
+
+It is possible for shaders to read texels directly from images, but that is not
+very common when they are used as textures. Textures are usually accessed
+through samplers, which will apply filtering and transformations to compute the
+final color that is retrieved.
+
+These filters are helpful to deal with problems like oversampling. Consider a
+texture that is mapped to geometry with more fragments than texels. If you
+simply took the closest texel for the texture coordinate in each fragment, then
+you would get a result like the first image:
+
+![](/images/texture_filtering.png)
+
+If you combined the 4 closest texels through linear interpolation, then you
+would get a smoother result like the one on the right. Of course your
+application may have art style requirements that fit the left style more (think
+Minecraft), but the right is preferred in conventional graphics applications. A
+sampler object automatically applies this filtering for you when reading a color
+from the texture.
+
+Undersampling is the opposite problem, where you have more texels than
+fragments. This will lead to artifacts when sampling high frequency patterns
+like a checkerboard texture at a sharp angle:
+
+![](/images/anisotropic_filtering.png)
+
+As shown in the left image, the texture turns into a blurry mess in the
+distance. The solution to this is [anisotropic filtering](https://en.wikipedia.org/wiki/Anisotropic_filtering),
+which can also be applied automatically by a sampler.
+
+Aside from these filters, a sampler can also take care of transformations. It
+determines what happens when you try to read texels outside the image through
+its *addressing mode*. The image below displays some of the possibilities:
+
+![](/images/texture_addressing.png)
+
+We will now create a function `createTextureSampler` to set up such a sampler
+object. We'll be using that sampler to read colors from the texture in the
+shader later on.
+
+```c++
+void initVulkan() {
+    ...
+    createTextureImage();
+    createTextureImageView();
+    createTextureSampler();
+    ...
+}
+
+...
+
+void createTextureSampler() {
+
+}
+```
+
+Samplers are configured through a `VkSamplerCreateInfo` structure, which
+specifies all filters and transformations that it should apply.
+
+```c++
+VkSamplerCreateInfo samplerInfo{};
+samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+samplerInfo.magFilter = VK_FILTER_LINEAR;
+samplerInfo.minFilter = VK_FILTER_LINEAR;
+```
+
+The `magFilter` and `minFilter` fields specify how to interpolate texels that
+are magnified or minified. Magnification concerns the oversampling problem
+describes above, and minification concerns undersampling. The choices are
+`VK_FILTER_NEAREST` and `VK_FILTER_LINEAR`, corresponding to the modes
+demonstrated in the images above.
+
+```c++
+samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+```
+
+The addressing mode can be specified per axis using the `addressMode` fields.
+The available values are listed below. Most of these are demonstrated in the
+image above. Note that the axes are called U, V and W instead of X, Y and Z.
+This is a convention for texture space coordinates.
+
+* `VK_SAMPLER_ADDRESS_MODE_REPEAT`: Repeat the texture when going beyond the
+image dimensions.
+* `VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT`: Like repeat, but inverts the
+coordinates to mirror the image when going beyond the dimensions.
+* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE`: Take the color of the edge closest to
+the coordinate beyond the image dimensions.
+* `VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE`: Like clamp to edge, but
+instead uses the edge opposite to the closest edge.
+* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER`: Return a solid color when sampling
+beyond the dimensions of the image.
+
+It doesn't really matter which addressing mode we use here, because we're not
+going to sample outside of the image in this tutorial. However, the repeat mode
+is probably the most common mode, because it can be used to tile textures like
+floors and walls.
+
+```c++
+samplerInfo.anisotropyEnable = VK_TRUE;
+samplerInfo.maxAnisotropy = ???;
+```
+
+These two fields specify if anisotropic filtering should be used. There is no
+reason not to use this unless performance is a concern. The `maxAnisotropy`
+field limits the amount of texel samples that can be used to calculate the final
+color. A lower value results in better performance, but lower quality results.
+To figure out which value we can use, we need to retrieve the properties of the physical device like so:
+
+```c++
+VkPhysicalDeviceProperties properties{};
+vkGetPhysicalDeviceProperties(physicalDevice, &properties);
+```
+
+If you look at the documentation for the `VkPhysicalDeviceProperties` structure, you'll see that it contains a `VkPhysicalDeviceLimits` member named `limits`. This struct in turn has a member called `maxSamplerAnisotropy` and this is the maximum value we can specify for `maxAnisotropy`. If we want to go for maximum quality, we can simply use that value directly:
+
+```c++
+samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
+```
+
+You can either query the properties at the beginning of your program and pass them around to the functions that need them, or query them in the `createTextureSampler` function itself.
+
+```c++
+samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+```
+
+The `borderColor` field specifies which color is returned when sampling beyond
+the image with clamp to border addressing mode. It is possible to return black,
+white or transparent in either float or int formats. You cannot specify an
+arbitrary color.
+
+```c++
+samplerInfo.unnormalizedCoordinates = VK_FALSE;
+```
+
+The `unnormalizedCoordinates` field specifies which coordinate system you want
+to use to address texels in an image. If this field is `VK_TRUE`, then you can
+simply use coordinates within the `[0, texWidth)` and `[0, texHeight)` range. If
+it is `VK_FALSE`, then the texels are addressed using the `[0, 1)` range on all
+axes. Real-world applications almost always use normalized coordinates, because
+then it's possible to use textures of varying resolutions with the exact same
+coordinates.
+
+```c++
+samplerInfo.compareEnable = VK_FALSE;
+samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+```
+
+If a comparison function is enabled, then texels will first be compared to a
+value, and the result of that comparison is used in filtering operations. This
+is mainly used for [percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html)
+on shadow maps. We'll look at this in a future chapter.
+
+```c++
+samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+samplerInfo.mipLodBias = 0.0f;
+samplerInfo.minLod = 0.0f;
+samplerInfo.maxLod = 0.0f;
+```
+
+All of these fields apply to mipmapping. We will look at mipmapping in a [later
+chapter](/Generating_Mipmaps), but basically it's another type of filter that can be applied.
+
+The functioning of the sampler is now fully defined. Add a class member to
+hold the handle of the sampler object and create the sampler with
+`vkCreateSampler`:
+
+```c++
+VkImageView textureImageView;
+VkSampler textureSampler;
+
+...
+
+void createTextureSampler() {
+    ...
+
+    if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create texture sampler!");
+    }
+}
+```
+
+Note the sampler does not reference a `VkImage` anywhere. The sampler is a
+distinct object that provides an interface to extract colors from a texture. It
+can be applied to any image you want, whether it is 1D, 2D or 3D. This is
+different from many older APIs, which combined texture images and filtering into
+a single state.
+
+Destroy the sampler at the end of the program when we'll no longer be accessing
+the image:
+
+```c++
+void cleanup() {
+    cleanupSwapChain();
+
+    vkDestroySampler(device, textureSampler, nullptr);
+    vkDestroyImageView(device, textureImageView, nullptr);
+
+    ...
+}
+```
+
+## Anisotropy device feature
+
+If you run your program right now, you'll see a validation layer message like
+this:
+
+![](/images/validation_layer_anisotropy.png)
+
+That's because anisotropic filtering is actually an optional device feature. We
+need to update the `createLogicalDevice` function to request it:
+
+```c++
+VkPhysicalDeviceFeatures deviceFeatures{};
+deviceFeatures.samplerAnisotropy = VK_TRUE;
+```
+
+And even though it is very unlikely that a modern graphics card will not support
+it, we should update `isDeviceSuitable` to check if it is available:
+
+```c++
+bool isDeviceSuitable(VkPhysicalDevice device) {
+    ...
+
+    VkPhysicalDeviceFeatures supportedFeatures;
+    vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
+
+    return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
+}
+```
+
+The `vkGetPhysicalDeviceFeatures` repurposes the `VkPhysicalDeviceFeatures`
+struct to indicate which features are supported rather than requested by setting
+the boolean values.
+
+Instead of enforcing the availability of anisotropic filtering, it's also
+possible to simply not use it by conditionally setting:
+
+```c++
+samplerInfo.anisotropyEnable = VK_FALSE;
+samplerInfo.maxAnisotropy = 1.0f;
+```
+
+In the next chapter we will expose the image and sampler objects to the shaders
+to draw the texture onto the square.
+
+[C++ code](/code/25_sampler.cpp) /
+[Vertex shader](/code/22_shader_ubo.vert) /
+[Fragment shader](/code/22_shader_ubo.frag)
diff --git a/kr/06_Texture_mapping/02_Combined_image_sampler.md b/kr/06_Texture_mapping/02_Combined_image_sampler.md
new file mode 100644
index 00000000..b86853d6
--- /dev/null
+++ b/kr/06_Texture_mapping/02_Combined_image_sampler.md
@@ -0,0 +1,296 @@
+## Introduction
+
+We looked at descriptors for the first time in the uniform buffers part of the
+tutorial. In this chapter we will look at a new type of descriptor: *combined
+image sampler*. This descriptor makes it possible for shaders to access an image
+resource through a sampler object like the one we created in the previous
+chapter.
+
+We'll start by modifying the descriptor layout, descriptor pool and descriptor
+set to include such a combined image sampler descriptor. After that, we're going
+to add texture coordinates to `Vertex` and modify the fragment shader to read
+colors from the texture instead of just interpolating the vertex colors.
+
+## Updating the descriptors
+
+Browse to the `createDescriptorSetLayout` function and add a
+`VkDescriptorSetLayoutBinding` for a combined image sampler descriptor. We'll
+simply put it in the binding after the uniform buffer:
+
+```c++
+VkDescriptorSetLayoutBinding samplerLayoutBinding{};
+samplerLayoutBinding.binding = 1;
+samplerLayoutBinding.descriptorCount = 1;
+samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+samplerLayoutBinding.pImmutableSamplers = nullptr;
+samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+std::array<VkDescriptorSetLayoutBinding, 2> bindings = {uboLayoutBinding, samplerLayoutBinding};
+VkDescriptorSetLayoutCreateInfo layoutInfo{};
+layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
+layoutInfo.pBindings = bindings.data();
+```
+
+Make sure to set the `stageFlags` to indicate that we intend to use the combined
+image sampler descriptor in the fragment shader. That's where the color of the
+fragment is going to be determined. It is possible to use texture sampling in
+the vertex shader, for example to dynamically deform a grid of vertices by a
+[heightmap](https://en.wikipedia.org/wiki/Heightmap).
+
+We must also create a larger descriptor pool to make room for the allocation
+of the combined image sampler by adding another `VkPoolSize` of type
+`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` to the
+`VkDescriptorPoolCreateInfo`. Go to the `createDescriptorPool` function and
+modify it to include a `VkDescriptorPoolSize` for this descriptor:
+
+```c++
+std::array<VkDescriptorPoolSize, 2> poolSizes{};
+poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+
+VkDescriptorPoolCreateInfo poolInfo{};
+poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
+poolInfo.pPoolSizes = poolSizes.data();
+poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+```
+
+Inadequate descriptor pools are a good example of a problem that the validation
+layers will not catch: As of Vulkan 1.1, `vkAllocateDescriptorSets` may fail
+with the error code `VK_ERROR_POOL_OUT_OF_MEMORY` if the pool is not
+sufficiently large, but the driver may also try to solve the problem internally.
+This means that sometimes (depending on hardware, pool size and allocation size)
+the driver will let us get away with an allocation that exceeds the limits of
+our descriptor pool. Other times, `vkAllocateDescriptorSets` will fail and
+return `VK_ERROR_POOL_OUT_OF_MEMORY`. This can be particularly frustrating if
+the allocation succeeds on some machines, but fails on others.
+
+Since Vulkan shifts the responsiblity for the allocation to the driver, it is no
+longer a strict requirement to only allocate as many descriptors of a certain
+type (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, etc.) as specified by the
+corresponding `descriptorCount` members for the creation of the descriptor pool.
+However, it remains best practise to do so, and in the future,
+`VK_LAYER_KHRONOS_validation` will warn about this type of problem if you enable
+[Best Practice Validation](https://vulkan.lunarg.com/doc/view/1.2.189.0/linux/best_practices.html).
+
+The final step is to bind the actual image and sampler resources to the
+descriptors in the descriptor set. Go to the `createDescriptorSets` function.
+
+```c++
+for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+    VkDescriptorBufferInfo bufferInfo{};
+    bufferInfo.buffer = uniformBuffers[i];
+    bufferInfo.offset = 0;
+    bufferInfo.range = sizeof(UniformBufferObject);
+
+    VkDescriptorImageInfo imageInfo{};
+    imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    imageInfo.imageView = textureImageView;
+    imageInfo.sampler = textureSampler;
+
+    ...
+}
+```
+
+The resources for a combined image sampler structure must be specified in a
+`VkDescriptorImageInfo` struct, just like the buffer resource for a uniform
+buffer descriptor is specified in a `VkDescriptorBufferInfo` struct. This is
+where the objects from the previous chapter come together.
+
+```c++
+std::array<VkWriteDescriptorSet, 2> descriptorWrites{};
+
+descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+descriptorWrites[0].dstSet = descriptorSets[i];
+descriptorWrites[0].dstBinding = 0;
+descriptorWrites[0].dstArrayElement = 0;
+descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+descriptorWrites[0].descriptorCount = 1;
+descriptorWrites[0].pBufferInfo = &bufferInfo;
+
+descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+descriptorWrites[1].dstSet = descriptorSets[i];
+descriptorWrites[1].dstBinding = 1;
+descriptorWrites[1].dstArrayElement = 0;
+descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+descriptorWrites[1].descriptorCount = 1;
+descriptorWrites[1].pImageInfo = &imageInfo;
+
+vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
+```
+
+The descriptors must be updated with this image info, just like the buffer. This
+time we're using the `pImageInfo` array instead of `pBufferInfo`. The descriptors
+are now ready to be used by the shaders!
+
+## Texture coordinates
+
+There is one important ingredient for texture mapping that is still missing, and
+that's the actual coordinates for each vertex. The coordinates determine how the
+image is actually mapped to the geometry.
+
+```c++
+struct Vertex {
+    glm::vec2 pos;
+    glm::vec3 color;
+    glm::vec2 texCoord;
+
+    static VkVertexInputBindingDescription getBindingDescription() {
+        VkVertexInputBindingDescription bindingDescription{};
+        bindingDescription.binding = 0;
+        bindingDescription.stride = sizeof(Vertex);
+        bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+        return bindingDescription;
+    }
+
+    static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
+        std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};
+
+        attributeDescriptions[0].binding = 0;
+        attributeDescriptions[0].location = 0;
+        attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
+        attributeDescriptions[0].offset = offsetof(Vertex, pos);
+
+        attributeDescriptions[1].binding = 0;
+        attributeDescriptions[1].location = 1;
+        attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
+        attributeDescriptions[1].offset = offsetof(Vertex, color);
+
+        attributeDescriptions[2].binding = 0;
+        attributeDescriptions[2].location = 2;
+        attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
+        attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
+
+        return attributeDescriptions;
+    }
+};
+```
+
+Modify the `Vertex` struct to include a `vec2` for texture coordinates. Make
+sure to also add a `VkVertexInputAttributeDescription` so that we can use access
+texture coordinates as input in the vertex shader. That is necessary to be able
+to pass them to the fragment shader for interpolation across the surface of the
+square.
+
+```c++
+const std::vector<Vertex> vertices = {
+    {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}},
+    {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}},
+    {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}},
+    {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}
+};
+```
+
+In this tutorial, I will simply fill the square with the texture by using
+coordinates from `0, 0` in the top-left corner to `1, 1` in the bottom-right
+corner. Feel free to experiment with different coordinates. Try using
+coordinates below `0` or above `1` to see the addressing modes in action!
+
+## Shaders
+
+The final step is modifying the shaders to sample colors from the texture. We
+first need to modify the vertex shader to pass through the texture coordinates
+to the fragment shader:
+
+```glsl
+layout(location = 0) in vec2 inPosition;
+layout(location = 1) in vec3 inColor;
+layout(location = 2) in vec2 inTexCoord;
+
+layout(location = 0) out vec3 fragColor;
+layout(location = 1) out vec2 fragTexCoord;
+
+void main() {
+    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
+    fragColor = inColor;
+    fragTexCoord = inTexCoord;
+}
+```
+
+Just like the per vertex colors, the `fragTexCoord` values will be smoothly
+interpolated across the area of the square by the rasterizer. We can visualize
+this by having the fragment shader output the texture coordinates as colors:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 1) in vec2 fragTexCoord;
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+    outColor = vec4(fragTexCoord, 0.0, 1.0);
+}
+```
+
+You should see something like the image below. Don't forget to recompile the
+shaders!
+
+![](/images/texcoord_visualization.png)
+
+The green channel represents the horizontal coordinates and the red channel the
+vertical coordinates. The black and yellow corners confirm that the texture
+coordinates are correctly interpolated from `0, 0` to `1, 1` across the square.
+Visualizing data using colors is the shader programming equivalent of `printf`
+debugging, for lack of a better option!
+
+A combined image sampler descriptor is represented in GLSL by a sampler uniform.
+Add a reference to it in the fragment shader:
+
+```glsl
+layout(binding = 1) uniform sampler2D texSampler;
+```
+
+There are equivalent `sampler1D` and `sampler3D` types for other types of
+images. Make sure to use the correct binding here.
+
+```glsl
+void main() {
+    outColor = texture(texSampler, fragTexCoord);
+}
+```
+
+Textures are sampled using the built-in `texture` function. It takes a `sampler`
+and coordinate as arguments. The sampler automatically takes care of the
+filtering and transformations in the background. You should now see the texture
+on the square when you run the application:
+
+![](/images/texture_on_square.png)
+
+Try experimenting with the addressing modes by scaling the texture coordinates
+to values higher than `1`. For example, the following fragment shader produces
+the result in the image below when using `VK_SAMPLER_ADDRESS_MODE_REPEAT`:
+
+```glsl
+void main() {
+    outColor = texture(texSampler, fragTexCoord * 2.0);
+}
+```
+
+![](/images/texture_on_square_repeated.png)
+
+You can also manipulate the texture colors using the vertex colors:
+
+```glsl
+void main() {
+    outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0);
+}
+```
+
+I've separated the RGB and alpha channels here to not scale the alpha channel.
+
+![](/images/texture_on_square_colorized.png)
+
+You now know how to access images in shaders! This is a very powerful technique
+when combined with images that are also written to in framebuffers. You can use
+these images as inputs to implement cool effects like post-processing and camera
+displays within the 3D world.
+
+[C++ code](/code/26_texture_mapping.cpp) /
+[Vertex shader](/code/26_shader_textures.vert) /
+[Fragment shader](/code/26_shader_textures.frag)
diff --git a/kr/07_Depth_buffering.md b/kr/07_Depth_buffering.md
new file mode 100644
index 00000000..92040cb3
--- /dev/null
+++ b/kr/07_Depth_buffering.md
@@ -0,0 +1,600 @@
+## Introduction
+
+The geometry we've worked with so far is projected into 3D, but it's still
+completely flat. In this chapter we're going to add a Z coordinate to the
+position to prepare for 3D meshes. We'll use this third coordinate to place a
+square over the current square to see a problem that arises when geometry is not
+sorted by depth.
+
+## 3D geometry
+
+Change the `Vertex` struct to use a 3D vector for the position, and update the
+`format` in the corresponding `VkVertexInputAttributeDescription`:
+
+```c++
+struct Vertex {
+    glm::vec3 pos;
+    glm::vec3 color;
+    glm::vec2 texCoord;
+
+    ...
+
+    static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
+        std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};
+
+        attributeDescriptions[0].binding = 0;
+        attributeDescriptions[0].location = 0;
+        attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
+        attributeDescriptions[0].offset = offsetof(Vertex, pos);
+
+        ...
+    }
+};
+```
+
+Next, update the vertex shader to accept and transform 3D coordinates as input.
+Don't forget to recompile it afterwards!
+
+```glsl
+layout(location = 0) in vec3 inPosition;
+
+...
+
+void main() {
+    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
+    fragColor = inColor;
+    fragTexCoord = inTexCoord;
+}
+```
+
+Lastly, update the `vertices` container to include Z coordinates:
+
+```c++
+const std::vector<Vertex> vertices = {
+    {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
+    {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
+    {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
+    {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}
+};
+```
+
+If you run your application now, then you should see exactly the same result as
+before. It's time to add some extra geometry to make the scene more interesting,
+and to demonstrate the problem that we're going to tackle in this chapter.
+Duplicate the vertices to define positions for a square right under the current
+one like this:
+
+![](/images/extra_square.svg)
+
+Use Z coordinates of `-0.5f` and add the appropriate indices for the extra
+square:
+
+```c++
+const std::vector<Vertex> vertices = {
+    {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
+    {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
+    {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
+    {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
+
+    {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
+    {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
+    {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
+    {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}
+};
+
+const std::vector<uint16_t> indices = {
+    0, 1, 2, 2, 3, 0,
+    4, 5, 6, 6, 7, 4
+};
+```
+
+Run your program now and you'll see something resembling an Escher illustration:
+
+![](/images/depth_issues.png)
+
+The problem is that the fragments of the lower square are drawn over the
+fragments of the upper square, simply because it comes later in the index array.
+There are two ways to solve this:
+
+* Sort all of the draw calls by depth from back to front
+* Use depth testing with a depth buffer
+
+The first approach is commonly used for drawing transparent objects, because
+order-independent transparency is a difficult challenge to solve. However, the
+problem of ordering fragments by depth is much more commonly solved using a
+*depth buffer*. A depth buffer is an additional attachment that stores the depth
+for every position, just like the color attachment stores the color of every
+position. Every time the rasterizer produces a fragment, the depth test will
+check if the new fragment is closer than the previous one. If it isn't, then the
+new fragment is discarded. A fragment that passes the depth test writes its own
+depth to the depth buffer. It is possible to manipulate this value from the
+fragment shader, just like you can manipulate the color output.
+
+```c++
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+```
+
+The perspective projection matrix generated by GLM will use the OpenGL depth
+range of `-1.0` to `1.0` by default. We need to configure it to use the Vulkan
+range of `0.0` to `1.0` using the `GLM_FORCE_DEPTH_ZERO_TO_ONE` definition.
+
+## Depth image and view
+
+A depth attachment is based on an image, just like the color attachment. The
+difference is that the swap chain will not automatically create depth images for us. We only need a single depth image, because only one draw operation is
+running at once. The depth image will again require the trifecta of resources:
+image, memory and image view.
+
+```c++
+VkImage depthImage;
+VkDeviceMemory depthImageMemory;
+VkImageView depthImageView;
+```
+
+Create a new function `createDepthResources` to set up these resources:
+
+```c++
+void initVulkan() {
+    ...
+    createCommandPool();
+    createDepthResources();
+    createTextureImage();
+    ...
+}
+
+...
+
+void createDepthResources() {
+
+}
+```
+
+Creating a depth image is fairly straightforward. It should have the same
+resolution as the color attachment, defined by the swap chain extent, an image
+usage appropriate for a depth attachment, optimal tiling and device local
+memory. The only question is: what is the right format for a depth image? The
+format must contain a depth component, indicated by `_D??_` in the `VK_FORMAT_`.
+
+Unlike the texture image, we don't necessarily need a specific format, because
+we won't be directly accessing the texels from the program. It just needs to
+have a reasonable accuracy, at least 24 bits is common in real-world
+applications. There are several formats that fit this requirement:
+
+* `VK_FORMAT_D32_SFLOAT`: 32-bit float for depth
+* `VK_FORMAT_D32_SFLOAT_S8_UINT`: 32-bit signed float for depth and 8 bit
+stencil component
+* `VK_FORMAT_D24_UNORM_S8_UINT`: 24-bit float for depth and 8 bit stencil
+component
+
+The stencil component is used for [stencil tests](https://en.wikipedia.org/wiki/Stencil_buffer),
+which is an additional test that can be combined with depth testing. We'll look
+at this in a future chapter.
+
+We could simply go for the `VK_FORMAT_D32_SFLOAT` format, because support for it
+is extremely common (see the hardware database), but it's nice to add some extra
+flexibility to our application where possible. We're going to write a function
+`findSupportedFormat` that takes a list of candidate formats in order from most
+desirable to least desirable, and checks which is the first one that is
+supported:
+
+```c++
+VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
+
+}
+```
+
+The support of a format depends on the tiling mode and usage, so we must also
+include these as parameters. The support of a format can be queried using
+the `vkGetPhysicalDeviceFormatProperties` function:
+
+```c++
+for (VkFormat format : candidates) {
+    VkFormatProperties props;
+    vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
+}
+```
+
+The `VkFormatProperties` struct contains three fields:
+
+* `linearTilingFeatures`: Use cases that are supported with linear tiling
+* `optimalTilingFeatures`: Use cases that are supported with optimal tiling
+* `bufferFeatures`: Use cases that are supported for buffers
+
+Only the first two are relevant here, and the one we check depends on the
+`tiling` parameter of the function:
+
+```c++
+if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
+    return format;
+} else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
+    return format;
+}
+```
+
+If none of the candidate formats support the desired usage, then we can either
+return a special value or simply throw an exception:
+
+```c++
+VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
+    for (VkFormat format : candidates) {
+        VkFormatProperties props;
+        vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
+
+        if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
+            return format;
+        } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
+            return format;
+        }
+    }
+
+    throw std::runtime_error("failed to find supported format!");
+}
+```
+
+We'll use this function now to create a `findDepthFormat` helper function to
+select a format with a depth component that supports usage as depth attachment:
+
+```c++
+VkFormat findDepthFormat() {
+    return findSupportedFormat(
+        {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT},
+        VK_IMAGE_TILING_OPTIMAL,
+        VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
+    );
+}
+```
+
+Make sure to use the `VK_FORMAT_FEATURE_` flag instead of `VK_IMAGE_USAGE_` in
+this case. All of these candidate formats contain a depth component, but the
+latter two also contain a stencil component. We won't be using that yet, but we
+do need to take that into account when performing layout transitions on images
+with these formats. Add a simple helper function that tells us if the chosen
+depth format contains a stencil component:
+
+```c++
+bool hasStencilComponent(VkFormat format) {
+    return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
+}
+```
+
+Call the function to find a depth format from `createDepthResources`:
+
+```c++
+VkFormat depthFormat = findDepthFormat();
+```
+
+We now have all the required information to invoke our `createImage` and
+`createImageView` helper functions:
+
+```c++
+createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
+depthImageView = createImageView(depthImage, depthFormat);
+```
+
+However, the `createImageView` function currently assumes that the subresource
+is always the `VK_IMAGE_ASPECT_COLOR_BIT`, so we will need to turn that field
+into a parameter:
+
+```c++
+VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) {
+    ...
+    viewInfo.subresourceRange.aspectMask = aspectFlags;
+    ...
+}
+```
+
+Update all calls to this function to use the right aspect:
+
+```c++
+swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT);
+...
+depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT);
+...
+textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT);
+```
+
+That's it for creating the depth image. We don't need to map it or copy another
+image to it, because we're going to clear it at the start of the render pass
+like the color attachment.
+
+### Explicitly transitioning the depth image
+
+We don't need to explicitly transition the layout of the image to a depth
+attachment because we'll take care of this in the render pass. However, for
+completeness I'll still describe the process in this section. You may skip it if
+you like.
+
+Make a call to `transitionImageLayout` at the end of the `createDepthResources`
+function like so:
+
+```c++
+transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+```
+
+The undefined layout can be used as initial layout, because there are no
+existing depth image contents that matter. We need to update some of the logic
+in `transitionImageLayout` to use the right subresource aspect:
+
+```c++
+if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
+    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+
+    if (hasStencilComponent(format)) {
+        barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+    }
+} else {
+    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+}
+```
+
+Although we're not using the stencil component, we do need to include it in the
+layout transitions of the depth image.
+
+Finally, add the correct access masks and pipeline stages:
+
+```c++
+if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+    barrier.srcAccessMask = 0;
+    barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+    sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+    destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+} else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
+    barrier.srcAccessMask = 0;
+    barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+
+    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+    destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+} else {
+    throw std::invalid_argument("unsupported layout transition!");
+}
+```
+
+The depth buffer will be read from to perform depth tests to see if a fragment
+is visible, and will be written to when a new fragment is drawn. The reading
+happens in the `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` stage and the
+writing in the `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`. You should pick the
+earliest pipeline stage that matches the specified operations, so that it is
+ready for usage as depth attachment when it needs to be.
+
+## Render pass
+
+We're now going to modify `createRenderPass` to include a depth attachment.
+First specify the `VkAttachmentDescription`:
+
+```c++
+VkAttachmentDescription depthAttachment{};
+depthAttachment.format = findDepthFormat();
+depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
+depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+```
+
+The `format` should be the same as the depth image itself. This time we don't
+care about storing the depth data (`storeOp`), because it will not be used after
+drawing has finished. This may allow the hardware to perform additional
+optimizations. Just like the color buffer, we don't care about the previous
+depth contents, so we can use `VK_IMAGE_LAYOUT_UNDEFINED` as `initialLayout`.
+
+```c++
+VkAttachmentReference depthAttachmentRef{};
+depthAttachmentRef.attachment = 1;
+depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+```
+
+Add a reference to the attachment for the first (and only) subpass:
+
+```c++
+VkSubpassDescription subpass{};
+subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+subpass.colorAttachmentCount = 1;
+subpass.pColorAttachments = &colorAttachmentRef;
+subpass.pDepthStencilAttachment = &depthAttachmentRef;
+```
+
+Unlike color attachments, a subpass can only use a single depth (+stencil)
+attachment. It wouldn't really make any sense to do depth tests on multiple
+buffers.
+
+```c++
+std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
+VkRenderPassCreateInfo renderPassInfo{};
+renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+renderPassInfo.pAttachments = attachments.data();
+renderPassInfo.subpassCount = 1;
+renderPassInfo.pSubpasses = &subpass;
+renderPassInfo.dependencyCount = 1;
+renderPassInfo.pDependencies = &dependency;
+```
+
+Next, update the `VkSubpassDependency` struct to refer to both
+attachments.
+
+```c++
+dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+```
+
+Finally, we need to extend our subpass dependencies to make sure that there is no conflict between the transitioning of the depth image and it being cleared as part of its load operation. The depth image is first accessed in the early fragment test pipeline stage and because we have a load operation that *clears*, we should specify the access mask for writes.
+
+## Framebuffer
+
+The next step is to modify the framebuffer creation to bind the depth image to
+the depth attachment. Go to `createFramebuffers` and specify the depth image
+view as second attachment:
+
+```c++
+std::array<VkImageView, 2> attachments = {
+    swapChainImageViews[i],
+    depthImageView
+};
+
+VkFramebufferCreateInfo framebufferInfo{};
+framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+framebufferInfo.renderPass = renderPass;
+framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+framebufferInfo.pAttachments = attachments.data();
+framebufferInfo.width = swapChainExtent.width;
+framebufferInfo.height = swapChainExtent.height;
+framebufferInfo.layers = 1;
+```
+
+The color attachment differs for every swap chain image, but the same depth
+image can be used by all of them because only a single subpass is running at the
+same time due to our semaphores.
+
+You'll also need to move the call to `createFramebuffers` to make sure that it
+is called after the depth image view has actually been created:
+
+```c++
+void initVulkan() {
+    ...
+    createDepthResources();
+    createFramebuffers();
+    ...
+}
+```
+
+## Clear values
+
+Because we now have multiple attachments with `VK_ATTACHMENT_LOAD_OP_CLEAR`, we
+also need to specify multiple clear values. Go to `recordCommandBuffer` and
+create an array of `VkClearValue` structs:
+
+```c++
+std::array<VkClearValue, 2> clearValues{};
+clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
+clearValues[1].depthStencil = {1.0f, 0};
+
+renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
+renderPassInfo.pClearValues = clearValues.data();
+```
+
+The range of depths in the depth buffer is `0.0` to `1.0` in Vulkan, where `1.0`
+lies at the far view plane and `0.0` at the near view plane. The initial value
+at each point in the depth buffer should be the furthest possible depth, which
+is `1.0`.
+
+Note that the order of `clearValues` should be identical to the order of your attachments.
+
+## Depth and stencil state
+
+The depth attachment is ready to be used now, but depth testing still needs to
+be enabled in the graphics pipeline. It is configured through the
+`VkPipelineDepthStencilStateCreateInfo` struct:
+
+```c++
+VkPipelineDepthStencilStateCreateInfo depthStencil{};
+depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+depthStencil.depthTestEnable = VK_TRUE;
+depthStencil.depthWriteEnable = VK_TRUE;
+```
+
+The `depthTestEnable` field specifies if the depth of new fragments should be
+compared to the depth buffer to see if they should be discarded. The
+`depthWriteEnable` field specifies if the new depth of fragments that pass the
+depth test should actually be written to the depth buffer.
+
+```c++
+depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
+```
+
+The `depthCompareOp` field specifies the comparison that is performed to keep or
+discard fragments. We're sticking to the convention of lower depth = closer, so
+the depth of new fragments should be *less*.
+
+```c++
+depthStencil.depthBoundsTestEnable = VK_FALSE;
+depthStencil.minDepthBounds = 0.0f; // Optional
+depthStencil.maxDepthBounds = 1.0f; // Optional
+```
+
+The `depthBoundsTestEnable`, `minDepthBounds` and `maxDepthBounds` fields are
+used for the optional depth bound test. Basically, this allows you to only keep
+fragments that fall within the specified depth range. We won't be using this
+functionality.
+
+```c++
+depthStencil.stencilTestEnable = VK_FALSE;
+depthStencil.front = {}; // Optional
+depthStencil.back = {}; // Optional
+```
+
+The last three fields configure stencil buffer operations, which we also won't
+be using in this tutorial. If you want to use these operations, then you will
+have to make sure that the format of the depth/stencil image contains a stencil
+component.
+
+```c++
+pipelineInfo.pDepthStencilState = &depthStencil;
+```
+
+Update the `VkGraphicsPipelineCreateInfo` struct to reference the depth stencil
+state we just filled in. A depth stencil state must always be specified if the
+render pass contains a depth stencil attachment.
+
+If you run your program now, then you should see that the fragments of the
+geometry are now correctly ordered:
+
+![](/images/depth_correct.png)
+
+## Handling window resize
+
+The resolution of the depth buffer should change when the window is resized to
+match the new color attachment resolution. Extend the `recreateSwapChain`
+function to recreate the depth resources in that case:
+
+```c++
+void recreateSwapChain() {
+    int width = 0, height = 0;
+    while (width == 0 || height == 0) {
+        glfwGetFramebufferSize(window, &width, &height);
+        glfwWaitEvents();
+    }
+
+    vkDeviceWaitIdle(device);
+
+    cleanupSwapChain();
+
+    createSwapChain();
+    createImageViews();
+    createDepthResources();
+    createFramebuffers();
+}
+```
+
+The cleanup operations should happen in the swap chain cleanup function:
+
+```c++
+void cleanupSwapChain() {
+    vkDestroyImageView(device, depthImageView, nullptr);
+    vkDestroyImage(device, depthImage, nullptr);
+    vkFreeMemory(device, depthImageMemory, nullptr);
+
+    ...
+}
+```
+
+Congratulations, your application is now finally ready to render arbitrary 3D
+geometry and have it look right. We're going to try this out in the next chapter
+by drawing a textured model!
+
+[C++ code](/code/27_depth_buffering.cpp) /
+[Vertex shader](/code/27_shader_depth.vert) /
+[Fragment shader](/code/27_shader_depth.frag)
diff --git a/kr/08_Loading_models.md b/kr/08_Loading_models.md
new file mode 100644
index 00000000..620e31bf
--- /dev/null
+++ b/kr/08_Loading_models.md
@@ -0,0 +1,332 @@
+## Introduction
+
+Your program is now ready to render textured 3D meshes, but the current geometry
+in the `vertices` and `indices` arrays is not very interesting yet. In this
+chapter we're going to extend the program to load the vertices and indices from
+an actual model file to make the graphics card actually do some work.
+
+Many graphics API tutorials have the reader write their own OBJ loader in a
+chapter like this. The problem with this is that any remotely interesting 3D
+application will soon require features that are not supported by this file
+format, like skeletal animation. We *will* load mesh data from an OBJ model in
+this chapter, but we'll focus more on integrating the mesh data with the program
+itself rather than the details of loading it from a file.
+
+## Library
+
+We will use the [tinyobjloader](https://github.com/syoyo/tinyobjloader) library
+to load vertices and faces from an OBJ file. It's fast and it's easy to
+integrate because it's a single file library like stb_image. Go to the
+repository linked above and download the `tiny_obj_loader.h` file to a folder in
+your library directory.
+
+**Visual Studio**
+
+Add the directory with `tiny_obj_loader.h` in it to the `Additional Include
+Directories` paths.
+
+![](/images/include_dirs_tinyobjloader.png)
+
+**Makefile**
+
+Add the directory with `tiny_obj_loader.h` to the include directories for GCC:
+
+```text
+VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64
+STB_INCLUDE_PATH = /home/user/libraries/stb
+TINYOBJ_INCLUDE_PATH = /home/user/libraries/tinyobjloader
+
+...
+
+CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH)
+```
+
+## Sample mesh
+
+In this chapter we won't be enabling lighting yet, so it helps to use a sample
+model that has lighting baked into the texture. An easy way to find such models
+is to look for 3D scans on [Sketchfab](https://sketchfab.com/). Many of the
+models on that site are available in OBJ format with a permissive license.
+
+For this tutorial I've decided to go with the [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)
+model by [nigelgoh](https://sketchfab.com/nigelgoh) ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). I tweaked the size and orientation of the model to use it
+as a drop in replacement for the current geometry:
+
+* [viking_room.obj](/resources/viking_room.obj)
+* [viking_room.png](/resources/viking_room.png)
+
+Feel free to use your own model, but make sure that it only consists of one
+material and that is has dimensions of about 1.5 x 1.5 x 1.5 units. If it is
+larger than that, then you'll have to change the view matrix. Put the model file
+in a new `models` directory next to `shaders` and `textures`, and put the
+texture image in the `textures` directory.
+
+Put two new configuration variables in your program to define the model and
+texture paths:
+
+```c++
+const uint32_t WIDTH = 800;
+const uint32_t HEIGHT = 600;
+
+const std::string MODEL_PATH = "models/viking_room.obj";
+const std::string TEXTURE_PATH = "textures/viking_room.png";
+```
+
+And update `createTextureImage` to use this path variable:
+
+```c++
+stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
+```
+
+## Loading vertices and indices
+
+We're going to load the vertices and indices from the model file now, so you
+should remove the global `vertices` and `indices` arrays now. Replace them with
+non-const containers as class members:
+
+```c++
+std::vector<Vertex> vertices;
+std::vector<uint32_t> indices;
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+```
+
+You should change the type of the indices from `uint16_t` to `uint32_t`, because
+there are going to be a lot more vertices than 65535. Remember to also change
+the `vkCmdBindIndexBuffer` parameter:
+
+```c++
+vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
+```
+
+The tinyobjloader library is included in the same way as STB libraries. Include
+the `tiny_obj_loader.h` file and make sure to define
+`TINYOBJLOADER_IMPLEMENTATION` in one source file to include the function
+bodies and avoid linker errors:
+
+```c++
+#define TINYOBJLOADER_IMPLEMENTATION
+#include <tiny_obj_loader.h>
+```
+
+We're now going to write a `loadModel` function that uses this library to
+populate the `vertices` and `indices` containers with the vertex data from the
+mesh. It should be called somewhere before the vertex and index buffers are
+created:
+
+```c++
+void initVulkan() {
+    ...
+    loadModel();
+    createVertexBuffer();
+    createIndexBuffer();
+    ...
+}
+
+...
+
+void loadModel() {
+
+}
+```
+
+A model is loaded into the library's data structures by calling the
+`tinyobj::LoadObj` function:
+
+```c++
+void loadModel() {
+    tinyobj::attrib_t attrib;
+    std::vector<tinyobj::shape_t> shapes;
+    std::vector<tinyobj::material_t> materials;
+    std::string warn, err;
+
+    if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) {
+        throw std::runtime_error(warn + err);
+    }
+}
+```
+
+An OBJ file consists of positions, normals, texture coordinates and faces. Faces
+consist of an arbitrary amount of vertices, where each vertex refers to a
+position, normal and/or texture coordinate by index. This makes it possible to
+not just reuse entire vertices, but also individual attributes.
+
+The `attrib` container holds all of the positions, normals and texture
+coordinates in its `attrib.vertices`, `attrib.normals` and `attrib.texcoords`
+vectors. The `shapes` container contains all of the separate objects and their
+faces. Each face consists of an array of vertices, and each vertex contains the
+indices of the position, normal and texture coordinate attributes. OBJ models
+can also define a material and texture per face, but we will be ignoring those.
+
+The `err` string contains errors and the `warn` string contains warnings that occurred while loading the
+file, like a missing material definition. Loading only really failed if the
+`LoadObj` function returns `false`. As mentioned above, faces in OBJ files can
+actually contain an arbitrary number of vertices, whereas our application can
+only render triangles. Luckily the `LoadObj` has an optional parameter to
+automatically triangulate such faces, which is enabled by default.
+
+We're going to combine all of the faces in the file into a single model, so just
+iterate over all of the shapes:
+
+```c++
+for (const auto& shape : shapes) {
+
+}
+```
+
+The triangulation feature has already made sure that there are three vertices
+per face, so we can now directly iterate over the vertices and dump them
+straight into our `vertices` vector:
+
+```c++
+for (const auto& shape : shapes) {
+    for (const auto& index : shape.mesh.indices) {
+        Vertex vertex{};
+
+        vertices.push_back(vertex);
+        indices.push_back(indices.size());
+    }
+}
+```
+
+For simplicity, we will assume that every vertex is unique for now, hence the
+simple auto-increment indices. The `index` variable is of type
+`tinyobj::index_t`, which contains the `vertex_index`, `normal_index` and
+`texcoord_index` members. We need to use these indices to look up the actual
+vertex attributes in the `attrib` arrays:
+
+```c++
+vertex.pos = {
+    attrib.vertices[3 * index.vertex_index + 0],
+    attrib.vertices[3 * index.vertex_index + 1],
+    attrib.vertices[3 * index.vertex_index + 2]
+};
+
+vertex.texCoord = {
+    attrib.texcoords[2 * index.texcoord_index + 0],
+    attrib.texcoords[2 * index.texcoord_index + 1]
+};
+
+vertex.color = {1.0f, 1.0f, 1.0f};
+```
+
+Unfortunately the `attrib.vertices` array is an array of `float` values instead
+of something like `glm::vec3`, so you need to multiply the index by `3`.
+Similarly, there are two texture coordinate components per entry. The offsets of
+`0`, `1` and `2` are used to access the X, Y and Z components, or the U and V
+components in the case of texture coordinates.
+
+Run your program now with optimization enabled (e.g. `Release` mode in Visual
+Studio and with the `-O3` compiler flag for GCC`). This is necessary, because
+otherwise loading the model will be very slow. You should see something like the
+following:
+
+![](/images/inverted_texture_coordinates.png)
+
+Great, the geometry looks correct, but what's going on with the texture? The OBJ format assumes a coordinate system where a vertical coordinate of `0` means the bottom of the image, however we've uploaded our image into Vulkan in a top to bottom orientation where `0` means the top of the image. Solve this by
+flipping the vertical component of the texture coordinates:
+
+```c++
+vertex.texCoord = {
+    attrib.texcoords[2 * index.texcoord_index + 0],
+    1.0f - attrib.texcoords[2 * index.texcoord_index + 1]
+};
+```
+
+When you run your program again, you should now see the correct result:
+
+![](/images/drawing_model.png)
+
+All that hard work is finally beginning to pay off with a demo like this!
+
+>As the model rotates you may notice that the rear (backside of the walls) looks a bit funny. This is normal and is simply because the model is not really designed to be viewed from that side.
+
+## Vertex deduplication
+
+Unfortunately we're not really taking advantage of the index buffer yet. The
+`vertices` vector contains a lot of duplicated vertex data, because many
+vertices are included in multiple triangles. We should keep only the unique
+vertices and use the index buffer to reuse them whenever they come up. A
+straightforward way to implement this is to use a `map` or `unordered_map` to
+keep track of the unique vertices and respective indices:
+
+```c++
+#include <unordered_map>
+
+...
+
+std::unordered_map<Vertex, uint32_t> uniqueVertices{};
+
+for (const auto& shape : shapes) {
+    for (const auto& index : shape.mesh.indices) {
+        Vertex vertex{};
+
+        ...
+
+        if (uniqueVertices.count(vertex) == 0) {
+            uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
+            vertices.push_back(vertex);
+        }
+
+        indices.push_back(uniqueVertices[vertex]);
+    }
+}
+```
+
+Every time we read a vertex from the OBJ file, we check if we've already seen a
+vertex with the exact same position and texture coordinates before. If not, we
+add it to `vertices` and store its index in the `uniqueVertices` container.
+After that we add the index of the new vertex to `indices`. If we've seen the
+exact same vertex before, then we look up its index in `uniqueVertices` and
+store that index in `indices`.
+
+The program will fail to compile right now, because using a user-defined type
+like our `Vertex` struct as key in a hash table requires us to implement two
+functions: equality test and hash calculation. The former is easy to implement
+by overriding the `==` operator in the `Vertex` struct:
+
+```c++
+bool operator==(const Vertex& other) const {
+    return pos == other.pos && color == other.color && texCoord == other.texCoord;
+}
+```
+
+A hash function for `Vertex` is implemented by specifying a template
+specialization for `std::hash<T>`. Hash functions are a complex topic, but
+[cppreference.com recommends](http://en.cppreference.com/w/cpp/utility/hash) the
+following approach combining the fields of a struct to create a decent quality
+hash function:
+
+```c++
+namespace std {
+    template<> struct hash<Vertex> {
+        size_t operator()(Vertex const& vertex) const {
+            return ((hash<glm::vec3>()(vertex.pos) ^
+                   (hash<glm::vec3>()(vertex.color) << 1)) >> 1) ^
+                   (hash<glm::vec2>()(vertex.texCoord) << 1);
+        }
+    };
+}
+```
+
+This code should be placed outside the `Vertex` struct. The hash functions for
+the GLM types need to be included using the following header:
+
+```c++
+#define GLM_ENABLE_EXPERIMENTAL
+#include <glm/gtx/hash.hpp>
+```
+
+The hash functions are defined in the `gtx` folder, which means that it is
+technically still an experimental extension to GLM. Therefore you need to define
+`GLM_ENABLE_EXPERIMENTAL` to use it. It means that the API could change with a
+new version of GLM in the future, but in practice the API is very stable.
+
+You should now be able to successfully compile and run your program. If you
+check the size of `vertices`, then you'll see that it has shrunk down from
+1,500,000 to 265,645! That means that each vertex is reused in an average number
+of ~6 triangles. This definitely saves us a lot of GPU memory.
+
+[C++ code](/code/28_model_loading.cpp) /
+[Vertex shader](/code/27_shader_depth.vert) /
+[Fragment shader](/code/27_shader_depth.frag)
diff --git a/kr/09_Generating_Mipmaps.md b/kr/09_Generating_Mipmaps.md
new file mode 100644
index 00000000..e4033136
--- /dev/null
+++ b/kr/09_Generating_Mipmaps.md
@@ -0,0 +1,354 @@
+## Introduction
+Our program can now load and render 3D models. In this chapter, we will add one more feature, mipmap generation. Mipmaps are widely used in games and rendering software, and Vulkan gives us complete control over how they are created.
+
+Mipmaps are precalculated, downscaled versions of an image. Each new image is half the width and height of the previous one.  Mipmaps are used as a form of *Level of Detail* or *LOD.* Objects that are far away from the camera will sample their textures from the smaller mip images. Using smaller images increases the rendering speed and avoids artifacts such as [Moiré patterns](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern). An example of what mipmaps look like:
+
+![](/images/mipmaps_example.jpg)
+
+## Image creation
+
+In Vulkan, each of the mip images is stored in different *mip levels* of a `VkImage`. Mip level 0 is the original image, and the mip levels after level 0 are commonly referred to as the *mip chain.*
+
+The number of mip levels is specified when the `VkImage` is created. Up until now, we have always set this value to one. We need to calculate the number of mip levels from the dimensions of the image. First, add a class member to store this number:
+
+```c++
+...
+uint32_t mipLevels;
+VkImage textureImage;
+...
+```
+
+The value for `mipLevels` can be found once we've loaded the texture in `createTextureImage`:
+
+```c++
+int texWidth, texHeight, texChannels;
+stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
+...
+mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
+
+```
+
+This calculates the number of levels in the mip chain. The `max` function selects the largest dimension. The `log2` function calculates how many times that dimension can be divided by 2. The `floor` function handles cases where the largest dimension is not a power of 2.  `1` is added so that the original image has a mip level.
+
+To use this value, we need to change the `createImage`, `createImageView`, and `transitionImageLayout` functions to allow us to specify the number of mip levels. Add a `mipLevels` parameter to the functions:
+
+```c++
+void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
+    ...
+    imageInfo.mipLevels = mipLevels;
+    ...
+}
+```
+```c++
+VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) {
+    ...
+    viewInfo.subresourceRange.levelCount = mipLevels;
+    ...
+```
+```c++
+void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) {
+    ...
+    barrier.subresourceRange.levelCount = mipLevels;
+    ...
+```
+
+Update all calls to these functions to use the right values:
+
+```c++
+createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
+...
+createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
+```
+```c++
+swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
+...
+depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1);
+...
+textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels);
+```
+```c++
+transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1);
+...
+transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
+```
+
+
+
+## Generating Mipmaps
+
+Our texture image now has multiple mip levels, but the staging buffer can only be used to fill mip level 0. The other levels are still undefined. To fill these levels we need to generate the data from the single level that we have. We will use the `vkCmdBlitImage` command. This command performs copying, scaling, and filtering operations. We will call this multiple times to *blit* data to each level of our texture image.
+
+`vkCmdBlitImage` is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. Add `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` to the texture image's usage flags in `createTextureImage`:
+
+```c++
+...
+createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
+...
+```
+
+Like other image operations, `vkCmdBlitImage` depends on the layout of the image it operates on. We could transition the entire image to `VK_IMAGE_LAYOUT_GENERAL`, but this will most likely be slow. For optimal performance, the source image should be in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination image should be in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Vulkan allows us to transition each mip level of an image independently. Each blit will only deal with two mip levels at a time, so we can transition each level into the optimal layout between blits commands.
+
+`transitionImageLayout` only performs layout transitions on the entire image, so we'll need to write a few more pipeline barrier commands. Remove the existing transition to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` in `createTextureImage`:
+
+```c++
+...
+transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
+    copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
+//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps
+...
+```
+
+This will leave each level of the texture image in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Each level will be transitioned to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` after the blit command reading from it is finished.
+
+We're now going to write the function that generates the mipmaps:
+
+```c++
+void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {
+    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+    VkImageMemoryBarrier barrier{};
+    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barrier.image = image;
+    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barrier.subresourceRange.baseArrayLayer = 0;
+    barrier.subresourceRange.layerCount = 1;
+    barrier.subresourceRange.levelCount = 1;
+
+    endSingleTimeCommands(commandBuffer);
+}
+```
+
+We're going to make several transitions, so we'll reuse this `VkImageMemoryBarrier`. The fields set above will remain the same for all barriers. `subresourceRange.miplevel`, `oldLayout`, `newLayout`, `srcAccessMask`, and `dstAccessMask` will be changed for each transition.
+
+```c++
+int32_t mipWidth = texWidth;
+int32_t mipHeight = texHeight;
+
+for (uint32_t i = 1; i < mipLevels; i++) {
+
+}
+```
+
+This loop will record each of the `VkCmdBlitImage` commands. Note that the loop variable starts at 1, not 0.
+
+```c++
+barrier.subresourceRange.baseMipLevel = i - 1;
+barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+
+vkCmdPipelineBarrier(commandBuffer,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
+    0, nullptr,
+    0, nullptr,
+    1, &barrier);
+```
+
+First, we transition level `i - 1` to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. This transition will wait for level `i - 1` to be filled, either from the previous blit command, or from `vkCmdCopyBufferToImage`. The current blit command will wait on this transition.
+
+```c++
+VkImageBlit blit{};
+blit.srcOffsets[0] = { 0, 0, 0 };
+blit.srcOffsets[1] = { mipWidth, mipHeight, 1 };
+blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+blit.srcSubresource.mipLevel = i - 1;
+blit.srcSubresource.baseArrayLayer = 0;
+blit.srcSubresource.layerCount = 1;
+blit.dstOffsets[0] = { 0, 0, 0 };
+blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
+blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+blit.dstSubresource.mipLevel = i;
+blit.dstSubresource.baseArrayLayer = 0;
+blit.dstSubresource.layerCount = 1;
+```
+
+Next, we specify the regions that will be used in the blit operation. The source mip level is `i - 1` and the destination mip level is `i`. The two elements of the `srcOffsets` array determine the 3D region that data will be blitted from. `dstOffsets` determines the region that data will be blitted to. The X and Y dimensions of the `dstOffsets[1]` are divided by two since each mip level is half the size of the previous level. The Z dimension of `srcOffsets[1]` and `dstOffsets[1]` must be 1, since a 2D image has a depth of 1.
+
+```c++
+vkCmdBlitImage(commandBuffer,
+    image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+    image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    1, &blit,
+    VK_FILTER_LINEAR);
+```
+
+Now, we record the blit command. Note that `textureImage` is used for both the `srcImage` and `dstImage` parameter. This is because we're blitting between different levels of the same image. The source mip level was just transitioned to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination level is still in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` from `createTextureImage`.
+
+Beware if you are using a dedicated transfer queue (as suggested in [Vertex buffers](!en/Vertex_buffers/Staging_buffer)): `vkCmdBlitImage` must be submitted to a queue with graphics capability.
+
+The last parameter allows us to specify a `VkFilter` to use in the blit. We have the same filtering options here that we had when making the `VkSampler`. We use the `VK_FILTER_LINEAR` to enable interpolation.
+
+```c++
+barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+vkCmdPipelineBarrier(commandBuffer,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
+    0, nullptr,
+    0, nullptr,
+    1, &barrier);
+```
+
+This barrier transitions mip level `i - 1` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This transition waits on the current blit command to finish. All sampling operations will wait on this transition to finish.
+
+```c++
+    ...
+    if (mipWidth > 1) mipWidth /= 2;
+    if (mipHeight > 1) mipHeight /= 2;
+}
+```
+
+At the end of the loop, we divide the current mip dimensions by two. We check each dimension before the division to ensure that dimension never becomes 0. This handles cases where the image is not square, since one of the mip dimensions would reach 1 before the other dimension. When this happens, that dimension should remain 1 for all remaining levels.
+
+```c++
+    barrier.subresourceRange.baseMipLevel = mipLevels - 1;
+    barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+    vkCmdPipelineBarrier(commandBuffer,
+        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
+        0, nullptr,
+        0, nullptr,
+        1, &barrier);
+
+    endSingleTimeCommands(commandBuffer);
+}
+```
+
+Before we end the command buffer, we insert one more pipeline barrier. This barrier transitions the last mip level from `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This wasn't handled by the loop, since the last mip level is never blitted from.
+
+Finally, add the call to `generateMipmaps` in `createTextureImage`:
+
+```c++
+transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
+    copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
+//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps
+...
+generateMipmaps(textureImage, texWidth, texHeight, mipLevels);
+```
+
+Our texture image's mipmaps are now completely filled.
+
+## Linear filtering support
+
+It is very convenient to use a built-in function like `vkCmdBlitImage` to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. It requires the texture image format we use to support linear filtering, which can be checked with the `vkGetPhysicalDeviceFormatProperties` function. We will add a check to the `generateMipmaps` function for this.
+
+First add an additional parameter that specifies the image format:
+
+```c++
+void createTextureImage() {
+    ...
+
+    generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels);
+}
+
+void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {
+
+    ...
+}
+```
+
+In the `generateMipmaps` function, use `vkGetPhysicalDeviceFormatProperties` to request the properties of the texture image format:
+
+```c++
+void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {
+
+    // Check if image format supports linear blitting
+    VkFormatProperties formatProperties;
+    vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties);
+
+    ...
+```
+
+The `VkFormatProperties` struct has three fields named `linearTilingFeatures`, `optimalTilingFeatures` and `bufferFeatures` that each describe how the format can be used depending on the way it is used. We create a texture image with the optimal tiling format, so we need to check `optimalTilingFeatures`. Support for the linear filtering feature can be checked with the `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`:
+
+```c++
+if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
+    throw std::runtime_error("texture image format does not support linear blitting!");
+}
+```
+
+There are two alternatives in this case. You could implement a function that searches common texture image formats for one that *does* support linear blitting, or you could implement the mipmap generation in software with a library like [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h). Each mip level can then be loaded into the image in the same way that you loaded the original image.
+
+It should be noted that it is uncommon in practice to generate the mipmap levels at runtime anyway. Usually they are pregenerated and stored in the texture file alongside the base level to improve loading speed. Implementing resizing in software and loading multiple levels from a file is left as an exercise to the reader.
+
+## Sampler
+
+While the `VkImage` holds the mipmap data, `VkSampler` controls how that data is read while rendering. Vulkan allows us to specify `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode` ("Lod" means "Level of Detail"). When a texture is sampled, the sampler selects a mip level according to the following pseudocode:
+
+```c++
+lod = getLodLevelFromScreenSize(); //smaller when the object is close, may be negative
+lod = clamp(lod + mipLodBias, minLod, maxLod);
+
+level = clamp(floor(lod), 0, texture.mipLevels - 1);  //clamped to the number of mip levels in the texture
+
+if (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) {
+    color = sample(level);
+} else {
+    color = blend(sample(level), sample(level + 1));
+}
+```
+
+If `samplerInfo.mipmapMode` is `VK_SAMPLER_MIPMAP_MODE_NEAREST`, `lod` selects the mip level to sample from. If the mipmap mode is `VK_SAMPLER_MIPMAP_MODE_LINEAR`, `lod` is used to select two mip levels to be sampled. Those levels are sampled and the results are linearly blended.
+
+The sample operation is also affected by `lod`:
+
+```c++
+if (lod <= 0) {
+    color = readTexture(uv, magFilter);
+} else {
+    color = readTexture(uv, minFilter);
+}
+```
+
+If the object is close to the camera, `magFilter` is used as the filter. If the object is further from the camera, `minFilter` is used. Normally, `lod` is non-negative, and is only 0 when close the camera. `mipLodBias` lets us force Vulkan to use lower `lod` and `level` than it would normally use.
+
+To see the results of this chapter, we need to choose values for our `textureSampler`. We've already set the `minFilter` and `magFilter` to use `VK_FILTER_LINEAR`. We just need to choose values for `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode`.
+
+```c++
+void createTextureSampler() {
+    ...
+    samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+    samplerInfo.minLod = 0.0f; // Optional
+    samplerInfo.maxLod = VK_LOD_CLAMP_NONE;
+    samplerInfo.mipLodBias = 0.0f; // Optional
+    ...
+}
+```
+
+To allow the full range of mip levels to be used, we set `minLod` to 0.0f, and `maxLod` to `VK_LOD_CLAMP_NONE`. This constant is equal to `1000.0f`, which means that all available mipmap levels in the texture will be sampled. We have no reason to change the `lod` value, so we set `mipLodBias` to 0.0f.
+
+Now run your program and you should see the following:
+
+![](/images/mipmaps.png)
+
+It's not a dramatic difference, since our scene is so simple. There are subtle differences if you look closely.
+
+![](/images/mipmaps_comparison.png)
+
+The most noticeable difference is the writing on the papers. With mipmaps, the writing has been smoothed. Without mipmaps, the writing has harsh edges and gaps from Moiré artifacts.
+
+You can play around with the sampler settings to see how they affect mipmapping. For example, by changing `minLod`, you can force the sampler to not use the lowest mip levels:
+
+```c++
+samplerInfo.minLod = static_cast<float>(mipLevels / 2);
+```
+
+These settings will produce this image:
+
+
+![](/images/highmipmaps.png)
+
+This is how higher mip levels will be used when objects are further away from the camera.
+
+
+[C++ code](/code/29_mipmapping.cpp) /
+[Vertex shader](/code/27_shader_depth.vert) /
+[Fragment shader](/code/27_shader_depth.frag)
diff --git a/kr/10_Multisampling.md b/kr/10_Multisampling.md
new file mode 100644
index 00000000..70b27b51
--- /dev/null
+++ b/kr/10_Multisampling.md
@@ -0,0 +1,298 @@
+## Introduction
+
+Our program can now load multiple levels of detail for textures which fixes artifacts when rendering objects far away from the viewer. The image is now a lot smoother, however on closer inspection you will notice jagged saw-like patterns along the edges of drawn geometric shapes. This is especially visible in one of our early programs when we rendered a quad:
+
+![](/images/texcoord_visualization.png)
+
+This undesired effect is called "aliasing" and it's a result of a limited numbers of pixels that are available for rendering. Since there are no displays out there with unlimited resolution, it will be always visible to some extent. There's a number of ways to fix this and in this chapter we'll focus on one of the more popular ones: [Multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) (MSAA).
+
+In ordinary rendering, the pixel color is determined based on a single sample point which in most cases is the center of the target pixel on screen. If part of the drawn line passes through a certain pixel but doesn't cover the sample point, that pixel will be left blank, leading to the jagged "staircase" effect.
+
+![](/images/aliasing.png)
+
+What MSAA does is it uses multiple sample points per pixel (hence the name) to determine its final color. As one might expect, more samples lead to better results, however it is also more computationally expensive.
+
+![](/images/antialiasing.png)
+
+In our implementation, we will focus on using the maximum available sample count. Depending on your application this may not always be the best approach and it might be better to use less samples for the sake of higher performance if the final result meets your quality demands.
+
+
+## Getting available sample count
+
+Let's start off by determining how many samples our hardware can use. Most modern GPUs support at least 8 samples but this number is not guaranteed to be the same everywhere. We'll keep track of it by adding a new class member:
+
+```c++
+...
+VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
+...
+```
+
+By default we'll be using only one sample per pixel which is equivalent to no multisampling, in which case the final image will remain unchanged. The exact maximum number of samples can be extracted from `VkPhysicalDeviceProperties` associated with our selected physical device. We're using a depth buffer, so we have to take into account the sample count for both color and depth. The highest sample count that is supported by both (&) will be the maximum we can support. Add a function that will fetch this information for us:
+
+```c++
+VkSampleCountFlagBits getMaxUsableSampleCount() {
+    VkPhysicalDeviceProperties physicalDeviceProperties;
+    vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties);
+
+    VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts;
+    if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; }
+    if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; }
+    if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; }
+    if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; }
+    if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; }
+    if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; }
+
+    return VK_SAMPLE_COUNT_1_BIT;
+}
+```
+
+We will now use this function to set the `msaaSamples` variable during the physical device selection process. For this, we have to slightly modify the `pickPhysicalDevice` function:
+
+```c++
+void pickPhysicalDevice() {
+    ...
+    for (const auto& device : devices) {
+        if (isDeviceSuitable(device)) {
+            physicalDevice = device;
+            msaaSamples = getMaxUsableSampleCount();
+            break;
+        }
+    }
+    ...
+}
+```
+
+## Setting up a render target
+
+In MSAA, each pixel is sampled in an offscreen buffer which is then rendered to the screen. This new buffer is slightly different from regular images we've been rendering to - they have to be able to store more than one sample per pixel. Once a multisampled buffer is created, it has to be resolved to the default framebuffer (which stores only a single sample per pixel). This is why we have to create an additional render target and modify our current drawing process. We only need one render target since only one drawing operation is active at a time, just like with the depth buffer. Add the following class members:
+
+```c++
+...
+VkImage colorImage;
+VkDeviceMemory colorImageMemory;
+VkImageView colorImageView;
+...
+```
+
+This new image will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during the image creation process. Modify the `createImage` function by adding a `numSamples` parameter:
+
+```c++
+void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
+    ...
+    imageInfo.samples = numSamples;
+    ...
+```
+
+For now, update all calls to this function using `VK_SAMPLE_COUNT_1_BIT` - we will be replacing this with proper values as we progress with implementation:
+
+```c++
+createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
+...
+createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
+```
+
+We will now create a multisampled color buffer. Add a `createColorResources` function and note that we're using `msaaSamples` here as a function parameter to `createImage`. We're also using only one mip level, since this is enforced by the Vulkan specification in case of images with more than one sample per pixel. Also, this color buffer doesn't need mipmaps since it's not going to be used as a texture:
+
+```c++
+void createColorResources() {
+    VkFormat colorFormat = swapChainImageFormat;
+
+    createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImage, colorImageMemory);
+    colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
+}
+```
+
+For consistency, call the function right before `createDepthResources`:
+
+```c++
+void initVulkan() {
+    ...
+    createColorResources();
+    createDepthResources();
+    ...
+}
+```
+
+Now that we have a multisampled color buffer in place it's time to take care of depth. Modify `createDepthResources` and update the number of samples used by the depth buffer:
+
+```c++
+void createDepthResources() {
+    ...
+    createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
+    ...
+}
+```
+
+We have now created a couple of new Vulkan resources, so let's not forget to release them when necessary:
+
+```c++
+void cleanupSwapChain() {
+    vkDestroyImageView(device, colorImageView, nullptr);
+    vkDestroyImage(device, colorImage, nullptr);
+    vkFreeMemory(device, colorImageMemory, nullptr);
+    ...
+}
+```
+
+And update the `recreateSwapChain` so that the new color image can be recreated in the correct resolution when the window is resized:
+
+```c++
+void recreateSwapChain() {
+    ...
+    createImageViews();
+    createColorResources();
+    createDepthResources();
+    ...
+}
+```
+
+We made it past the initial MSAA setup, now we need to start using this new resource in our graphics pipeline, framebuffer, render pass and see the results!
+
+## Adding new attachments
+
+Let's take care of the render pass first. Modify `createRenderPass` and update color and depth attachment creation info structs:
+
+```c++
+void createRenderPass() {
+    ...
+    colorAttachment.samples = msaaSamples;
+    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    ...
+    depthAttachment.samples = msaaSamples;
+    ...
+```
+
+You'll notice that we have changed the finalLayout from `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`. That's because multisampled images cannot be presented directly. We first need to resolve them to a regular image. This requirement does not apply to the depth buffer, since it won't be presented at any point. Therefore we will have to add only one new attachment for color which is a so-called resolve attachment:
+
+```c++
+    ...
+    VkAttachmentDescription colorAttachmentResolve{};
+    colorAttachmentResolve.format = swapChainImageFormat;
+    colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT;
+    colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+    ...
+```
+
+The render pass now has to be instructed to resolve multisampled color image into regular attachment. Create a new attachment reference that will point to the color buffer which will serve as the resolve target:
+
+```c++
+    ...
+    VkAttachmentReference colorAttachmentResolveRef{};
+    colorAttachmentResolveRef.attachment = 2;
+    colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    ...
+```
+
+Set the `pResolveAttachments` subpass struct member to point to the newly created attachment reference. This is enough to let the render pass define a multisample resolve operation which will let us render the image to screen:
+
+```
+    ...
+    subpass.pResolveAttachments = &colorAttachmentResolveRef;
+    ...
+```
+
+Since we're reusing the multisampled color image, it's necessary to update the `srcAccessMask` of the `VkSubpassDependency`. This update ensures that any write operations to the color attachment are completed before subsequent ones begin, thus preventing write-after-write hazards that can lead to unstable rendering results:
+
+```c++
+    ...
+    dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+    ...
+```
+
+Now update render pass info struct with the new color attachment:
+
+```c++
+    ...
+    std::array<VkAttachmentDescription, 3> attachments = {colorAttachment, depthAttachment, colorAttachmentResolve};
+    ...
+```
+
+With the render pass in place, modify `createFramebuffers` and add the new image view to the list:
+
+```c++
+void createFramebuffers() {
+        ...
+        std::array<VkImageView, 3> attachments = {
+            colorImageView,
+            depthImageView,
+            swapChainImageViews[i]
+        };
+        ...
+}
+```
+
+Finally, tell the newly created pipeline to use more than one sample by modifying `createGraphicsPipeline`:
+
+```c++
+void createGraphicsPipeline() {
+    ...
+    multisampling.rasterizationSamples = msaaSamples;
+    ...
+}
+```
+
+Now run your program and you should see the following:
+
+![](/images/multisampling.png)
+
+Just like with mipmapping, the difference may not be apparent straight away. On a closer look you'll notice that the edges are not as jagged anymore and the whole image seems a bit smoother compared to the original.
+
+![](/images/multisampling_comparison.png)
+
+The difference is more noticable when looking up close at one of the edges:
+
+![](/images/multisampling_comparison2.png)
+
+## Quality improvements
+
+There are certain limitations of our current MSAA implementation which may impact the quality of the output image in more detailed scenes. For example, we're currently not solving potential problems caused by shader aliasing, i.e. MSAA only smoothens out the edges of geometry but not the interior filling. This may lead to a situation when you get a smooth polygon rendered on screen but the applied texture will still look aliased if it contains high contrasting colors. One way to approach this problem is to enable [Sample Shading](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap27.html#primsrast-sampleshading) which will improve the image quality even further, though at an additional performance cost:
+
+```c++
+
+void createLogicalDevice() {
+    ...
+    deviceFeatures.sampleRateShading = VK_TRUE; // enable sample shading feature for the device
+    ...
+}
+
+void createGraphicsPipeline() {
+    ...
+    multisampling.sampleShadingEnable = VK_TRUE; // enable sample shading in the pipeline
+    multisampling.minSampleShading = .2f; // min fraction for sample shading; closer to one is smoother
+    ...
+}
+```
+
+In this example we'll leave sample shading disabled but in certain scenarios the quality improvement may be noticeable:
+
+![](/images/sample_shading.png)
+
+## Conclusion
+
+It has taken a lot of work to get to this point, but now you finally have a good
+base for a Vulkan program. The knowledge of the basic principles of Vulkan that
+you now possess should be sufficient to start exploring more of the features,
+like:
+
+* Push constants
+* Instanced rendering
+* Dynamic uniforms
+* Separate images and sampler descriptors
+* Pipeline cache
+* Multi-threaded command buffer generation
+* Multiple subpasses
+* Compute shaders
+
+The current program can be extended in many ways, like adding Blinn-Phong
+lighting, post-processing effects and shadow mapping. You should be able to
+learn how these effects work from tutorials for other APIs, because despite
+Vulkan's explicitness, many concepts still work the same.
+
+[C++ code](/code/30_multisampling.cpp) /
+[Vertex shader](/code/27_shader_depth.vert) /
+[Fragment shader](/code/27_shader_depth.frag)
diff --git a/kr/11_Compute_Shader.md b/kr/11_Compute_Shader.md
new file mode 100644
index 00000000..628f58e9
--- /dev/null
+++ b/kr/11_Compute_Shader.md
@@ -0,0 +1,650 @@
+## Introduction
+
+In this bonus chapter we'll take a look at compute shaders. Up until now all previous chapters dealt with the traditional graphics part of the Vulkan pipeline. But unlike older APIs like OpenGL, compute shader support in Vulkan is mandatory. This means that you can use compute shaders on every Vulkan implementation available, no matter if it's a high-end desktop GPU or a low-powered embedded device.
+
+This opens up the world of general purpose computing on graphics processor units (GPGPU), no matter where your application is running. GPGPU means that you can do general computations on your GPU, something that has traditionally been a domain of CPUs. But with GPUs having become more and more powerful and more flexible, many workloads that would require the general purpose capabilities of a CPU can now be done on the GPU in realtime.
+
+A few examples of where the compute capabilities of a GPU can be used are image manipulation, visibility testing, post processing, advanced lighting calculations, animations, physics (e.g. for a particle system) and much more. And it's even possible to use compute for non-visual computational only work that does not require any graphics output, e.g. number crunching or AI related things. This is called "headless compute".
+
+## Advantages
+
+Doing computationally expensive calculations on the GPU has several advantages. The most obvious one is offloading work from the CPU. Another one is not requiring moving data between the CPU's main memory and the GPU's memory. All of the data can stay on the GPU without having to wait for slow transfers from main memory.
+
+Aside from these, GPUs are heavily parallelized with some of them having tens of thousands of small compute units. This often makes them a better fit for highly parallel workflows than a CPU with a few large compute units.
+
+## The Vulkan pipeline
+
+It's important to know that compute is completely separated from the graphics part of the pipeline. This is visible in the following block diagram of the Vulkan pipeline from the official specification:
+
+![](/images/vulkan_pipeline_block_diagram.png)
+
+In this diagram we can see the traditional graphics part of the pipeline on the left, and several stages on the right that are not part of this graphics pipeline, including the compute shader (stage). With the compute shader stage being detached from the graphics pipeline we'll be able to use it anywhere we see fit. This is very different from e.g. the fragment shader which is always applied to the transformed output of the vertex shader.
+
+The center of the diagram also shows that e.g. descriptor sets are also used by compute, so everything we learned about descriptors layouts, descriptor sets and descriptors also applies here.
+
+## An example
+
+An easy to understand example that we will implement in this chapter is a GPU based particle system. Such systems are used in many games and often consist of thousands of particles that need to be updated at interactive frame rates. Rendering such a system requires 2 main components: vertices, passed as vertex buffers, and a way to update them based on some equation.
+
+The "classical" CPU based particle system would store particle data in the system's main memory and then use the CPU to update them. After the update, the vertices need to be transferred to the GPU's memory again so it can display the updated particles in the next frame. The most straight-forward way would be recreating the vertex buffer with the new data for each frame. This is obviously very costly. Depending on your implementation, there are other options like mapping GPU memory so it can be written by the CPU (called "resizable BAR" on desktop systems, or unified memory on integrated GPUs) or just using a host local buffer (which would be the slowest method due to PCI-E bandwidth). But no matter what buffer update method you choose, you always require a "round-trip" to the CPU to update the particles.
+
+With a GPU based particle system, this round-trip is no longer required. Vertices are only uploaded to the GPU at the start and all updates are done in the GPU's memory using compute shaders. One of the main reasons why this is faster is the much higher bandwidth between the GPU and it's local memory. In a CPU based scenario, you'd be limited by main memory and PCI-express bandwidth, which is often just a fraction of the GPU's memory bandwidth.
+
+When doing this on a GPU with a dedicated compute queue, you can update particles in parallel to the rendering part of the graphics pipeline. This is called "async compute", and is an advanced topic not covered in this tutorial.
+
+Here is a screenshot from this chapter's code. The particles shown here are updated by a compute shader directly on the GPU, without any CPU interaction:
+
+![](/images/compute_shader_particles.png)
+
+## Data manipulation
+
+In this tutorial we already learned about different buffer types like vertex and index buffers for passing primitives and uniform buffers for passing data to a shader. And we also used images to do texture mapping. But up until now, we always wrote data using the CPU and only did reads on the GPU.
+
+An important concept introduced with compute shaders is the ability to arbitrarily read from **and write to** buffers. For this, Vulkan offers two dedicated storage types.
+
+### Shader storage buffer objects (SSBO)
+
+A shader storage buffer (SSBO) allows shaders to read from and write to a buffer. Using these is similar to using uniform buffer objects. The biggest differences are that you can alias other buffer types to SSBOs and that they can be arbitrarily large.
+
+Going back to the GPU based particle system, you might now wonder how to deal with vertices being updated (written) by the compute shader and read (drawn) by the vertex shader, as both usages would seemingly require different buffer types.
+
+But that's not the case. In Vulkan you can specify multiple usages for buffers and images. So for the particle vertex buffer to be used as a vertex buffer (in the graphics pass) and as a storage buffer (in the compute pass), you simply create the buffer with those two usage flags:
+
+```c++
+VkBufferCreateInfo bufferInfo{};
+...
+bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+...
+
+if (vkCreateBuffer(device, &bufferInfo, nullptr, &shaderStorageBuffers[i]) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create vertex buffer!");
+}
+```
+The two flags `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` and `VK_BUFFER_USAGE_STORAGE_BUFFER_BIT` set with `bufferInfo.usage` tell the implementation that we want to use this buffer for two different scenarios: as a vertex buffer in the vertex shader and as a store buffer. Note that we also added the `VK_BUFFER_USAGE_TRANSFER_DST_BIT` flag in here so we can transfer data from the host to the GPU. This is crucial as we want the shader storage buffer to stay in GPU memory only (`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`) we need to to transfer data from the host to this buffer.
+
+Here is the same code using using the `createBuffer` helper function:
+
+```c++
+createBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, shaderStorageBuffers[i], shaderStorageBuffersMemory[i]);
+```
+
+The GLSL shader declaration for accessing such a buffer looks like this:
+
+```glsl
+struct Particle {
+  vec2 position;
+  vec2 velocity;
+  vec4 color;
+};
+
+layout(std140, binding = 1) readonly buffer ParticleSSBOIn {
+   Particle particlesIn[ ];
+};
+
+layout(std140, binding = 2) buffer ParticleSSBOOut {
+   Particle particlesOut[ ];
+};
+```
+
+In this example we have a typed SSBO with each particle having a position and velocity value (see the `Particle` struct). The SSBO then contains an unbound number of particles as marked by the `[]`. Not having to specify the number of elements in an SSBO is one of the advantages over e.g. uniform buffers. `std140` is a memory layout qualifier that determines how the member elements of the shader storage buffer are aligned in memory. This gives us certain guarantees, required to map the buffers between the host and the GPU.
+
+Writing to such a storage buffer object in the compute shader is straight-forward and similar to how you'd write to the buffer on the C++ side:
+
+```glsl
+particlesOut[index].position = particlesIn[index].position + particlesIn[index].velocity.xy * ubo.deltaTime;
+```
+
+### Storage images
+
+*Note that we won't be doing image manipulation in this chapter. This paragraph is here to make readers aware that compute shaders can also be used for image manipulation.*
+
+A storage image allows you read from and write to an image. Typical use cases are applying image effects to textures, doing post processing (which in turn is very similar) or generating mip-maps.
+
+This is similar for images:
+
+```c++
+VkImageCreateInfo imageInfo {};
+...
+imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+...
+
+if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create image!");
+}
+```
+
+The two flags `VK_IMAGE_USAGE_SAMPLED_BIT` and `VK_IMAGE_USAGE_STORAGE_BIT` set with `imageInfo.usage` tell the implementation that we want to use this image for two different scenarios: as an image sampled in the fragment shader and as a storage image in the computer shader;
+
+The GLSL shader declaration for storage image looks similar to sampled images used e.g. in the fragment shader:
+
+```glsl
+layout (binding = 0, rgba8) uniform readonly image2D inputImage;
+layout (binding = 1, rgba8) uniform writeonly image2D outputImage;
+```
+
+A few differences here are additional attributes like `rgba8` for the format of the image, the `readonly` and `writeonly` qualifiers, telling the implementation that we will only read from the input image and write to the output image. And last but not least we need to use the `image2D` type to declare a storage image.
+
+Reading from and writing to storage images in the compute shader is then done using `imageLoad` and `imageStore`: 
+
+```glsl
+vec3 pixel = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.xy)).rgb;
+imageStore(outputImage, ivec2(gl_GlobalInvocationID.xy), pixel);
+```
+
+## Compute queue families
+
+In the [physical device and queue families chapter](03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md#page_Queue-families) we already learned about queue families and how to select a graphics queue family. Compute uses the queue family properties flag bit `VK_QUEUE_COMPUTE_BIT`. So if we want to do compute work, we need to get a queue from a queue family that supports compute.
+
+Note that Vulkan requires an implementation which supports graphics operations to have at least one queue family that supports both graphics and compute operations, but it's also possible that implementations offer a dedicated compute queue. This dedicated compute queue (that does not have the graphics bit) hints at an asynchronous compute queue. To keep this tutorial beginner friendly though, we'll use a queue that can do both graphics and compute operations. This will also save us from dealing with several advanced synchronization mechanisms.
+
+For our compute sample we need to change the device creation code a bit:
+
+```c++
+uint32_t queueFamilyCount = 0;
+vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
+
+std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
+vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
+
+int i = 0;
+for (const auto& queueFamily : queueFamilies) {
+    if ((queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) && (queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT)) {
+        indices.graphicsAndComputeFamily = i;
+    }
+
+    i++;
+}
+```
+
+The changed queue family index selection code will now try to find a queue family that supports both graphics and compute. 
+
+We can then get a compute queue from this queue family in `createLogicalDevice`:
+
+```c++
+vkGetDeviceQueue(device, indices.graphicsAndComputeFamily.value(), 0, &computeQueue);
+```
+
+## The compute shader stage
+
+In the graphics samples we have used different pipeline stages to load shaders and access descriptors. Compute shaders are accessed in a similar way by using the `VK_SHADER_STAGE_COMPUTE_BIT` pipeline. So loading a compute shader is just the same as loading a vertex shader, but with a different shader stage. We'll talk about this in detail in the next paragraphs. Compute also introduces a new binding point type for descriptors and pipelines named `VK_PIPELINE_BIND_POINT_COMPUTE` that we'll have to use later on.
+
+## Loading compute shaders
+
+Loading compute shaders in our application is the same as loading any other other shader. The only real difference is that we'll need to use the `VK_SHADER_STAGE_COMPUTE_BIT` mentioned above.
+
+```c++
+auto computeShaderCode = readFile("shaders/compute.spv");
+
+VkShaderModule computeShaderModule = createShaderModule(computeShaderCode);
+
+VkPipelineShaderStageCreateInfo computeShaderStageInfo{};
+computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+computeShaderStageInfo.module = computeShaderModule;
+computeShaderStageInfo.pName = "main";
+...
+```
+
+## Preparing the shader storage buffers
+
+Earlier on we learned that we can use shader storage buffers to pass arbitrary data to compute shaders. For this example we will upload an array of particles to the GPU, so we can manipulate it directly in the GPU's memory.
+
+In the [frames in flight](03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md) chapter we talked about duplicating resources per frame in flight, so we can keep the CPU and the GPU busy. First we declare a vector for the buffer object and the device memory backing it up:
+
+```c++
+std::vector<VkBuffer> shaderStorageBuffers;
+std::vector<VkDeviceMemory> shaderStorageBuffersMemory;
+```
+
+In the `createShaderStorageBuffers` we then resize those vectors to match the max. number of frames in flight:
+
+```c++
+shaderStorageBuffers.resize(MAX_FRAMES_IN_FLIGHT);
+shaderStorageBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
+```
+
+With this setup in place we can start to move the initial particle information to the GPU. We first initialize a vector of particles on the host side:
+
+```c++
+    // Initialize particles
+    std::default_random_engine rndEngine((unsigned)time(nullptr));
+    std::uniform_real_distribution<float> rndDist(0.0f, 1.0f);
+
+    // Initial particle positions on a circle
+    std::vector<Particle> particles(PARTICLE_COUNT);
+    for (auto& particle : particles) {
+        float r = 0.25f * sqrt(rndDist(rndEngine));
+        float theta = rndDist(rndEngine) * 2 * 3.14159265358979323846;
+        float x = r * cos(theta) * HEIGHT / WIDTH;
+        float y = r * sin(theta);
+        particle.position = glm::vec2(x, y);
+        particle.velocity = glm::normalize(glm::vec2(x,y)) * 0.00025f;
+        particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f);
+    }
+
+```
+
+We then create a [staging buffer](04_Vertex_buffers/02_Staging_buffer.md) in the host's memory to hold the initial particle properties:
+
+```c++
+    VkDeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT;
+
+    VkBuffer stagingBuffer;
+    VkDeviceMemory stagingBufferMemory;
+    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+
+    void* data;
+    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
+    memcpy(data, particles.data(), (size_t)bufferSize);
+    vkUnmapMemory(device, stagingBufferMemory);
+```    
+
+Using this staging buffer as a source we then create the per-frame shader storage buffers and copy the particle properties from the staging buffer to each of these:
+
+```c++
+    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+        createBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, shaderStorageBuffers[i], shaderStorageBuffersMemory[i]);
+        // Copy data from the staging buffer (host) to the shader storage buffer (GPU)
+        copyBuffer(stagingBuffer, shaderStorageBuffers[i], bufferSize);
+    }
+}
+```
+
+## Descriptors
+
+Setting up descriptors for compute is almost identical to graphics. The only difference is that descriptors need to have the `VK_SHADER_STAGE_COMPUTE_BIT` set to make them accessible by the compute stage:
+
+```c++
+std::array<VkDescriptorSetLayoutBinding, 3> layoutBindings{};
+layoutBindings[0].binding = 0;
+layoutBindings[0].descriptorCount = 1;
+layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+layoutBindings[0].pImmutableSamplers = nullptr;
+layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+...
+```
+
+Note that you can combine shader stages here, so if you want the descriptor to be accessible from the vertex and compute stage, e.g. for a uniform buffer with parameters shared across them, you simply set the bits for both stages:
+
+```c++
+layoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
+```
+
+Here is the descriptor setup for our sample. The layout looks like this:
+
+```c++
+std::array<VkDescriptorSetLayoutBinding, 3> layoutBindings{};
+layoutBindings[0].binding = 0;
+layoutBindings[0].descriptorCount = 1;
+layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+layoutBindings[0].pImmutableSamplers = nullptr;
+layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+
+layoutBindings[1].binding = 1;
+layoutBindings[1].descriptorCount = 1;
+layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+layoutBindings[1].pImmutableSamplers = nullptr;
+layoutBindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+
+layoutBindings[2].binding = 2;
+layoutBindings[2].descriptorCount = 1;
+layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+layoutBindings[2].pImmutableSamplers = nullptr;
+layoutBindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+
+VkDescriptorSetLayoutCreateInfo layoutInfo{};
+layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+layoutInfo.bindingCount = 3;
+layoutInfo.pBindings = layoutBindings.data();
+
+if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create compute descriptor set layout!");
+}
+```
+
+Looking at this setup, you might wonder why we have two layout bindings for shader storage buffer objects, even though we'll only render a single particle system. This is because the particle positions are updated frame by frame based on a delta time. This means that each frame needs to know about the last frames' particle positions, so it can update them with a new delta time and write them to it's own SSBO:
+
+![](/images/compute_ssbo_read_write.svg)
+
+For that, the compute shader needs to have access to the last and current frame's SSBOs. This is done by passing both to the compute shader in our descriptor setup. See the `storageBufferInfoLastFrame` and `storageBufferInfoCurrentFrame`: 
+
+```c++
+for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+    VkDescriptorBufferInfo uniformBufferInfo{};
+    uniformBufferInfo.buffer = uniformBuffers[i];
+    uniformBufferInfo.offset = 0;
+    uniformBufferInfo.range = sizeof(UniformBufferObject);
+
+    std::array<VkWriteDescriptorSet, 3> descriptorWrites{};
+    ...
+
+    VkDescriptorBufferInfo storageBufferInfoLastFrame{};
+    storageBufferInfoLastFrame.buffer = shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT];
+    storageBufferInfoLastFrame.offset = 0;
+    storageBufferInfoLastFrame.range = sizeof(Particle) * PARTICLE_COUNT;
+
+    descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptorWrites[1].dstSet = computeDescriptorSets[i];
+    descriptorWrites[1].dstBinding = 1;
+    descriptorWrites[1].dstArrayElement = 0;
+    descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+    descriptorWrites[1].descriptorCount = 1;
+    descriptorWrites[1].pBufferInfo = &storageBufferInfoLastFrame;
+
+    VkDescriptorBufferInfo storageBufferInfoCurrentFrame{};
+    storageBufferInfoCurrentFrame.buffer = shaderStorageBuffers[i];
+    storageBufferInfoCurrentFrame.offset = 0;
+    storageBufferInfoCurrentFrame.range = sizeof(Particle) * PARTICLE_COUNT;
+
+    descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptorWrites[2].dstSet = computeDescriptorSets[i];
+    descriptorWrites[2].dstBinding = 2;
+    descriptorWrites[2].dstArrayElement = 0;
+    descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+    descriptorWrites[2].descriptorCount = 1;
+    descriptorWrites[2].pBufferInfo = &storageBufferInfoCurrentFrame;
+
+    vkUpdateDescriptorSets(device, 3, descriptorWrites.data(), 0, nullptr);
+}
+```
+
+Remember that we also have to request the descriptor types for the SSBOs from our descriptor pool:
+
+```c++
+std::array<VkDescriptorPoolSize, 2> poolSizes{};
+...
+
+poolSizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT) * 2;
+```
+
+We need to double the number of `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER` types requested from the pool by two because our sets reference the SSBOs of the last and current frame.
+
+## Compute pipelines
+
+As compute is not a part of the graphics pipeline, we can't use `vkCreateGraphicsPipelines`. Instead we need to create a dedicated compute pipeline with `vkCreateComputePipelines` for running our compute commands. Since a compute pipeline does not touch any of the rasterization state, it has a lot less state than a graphics pipeline:
+
+```c++
+VkComputePipelineCreateInfo pipelineInfo{};
+pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+pipelineInfo.layout = computePipelineLayout;
+pipelineInfo.stage = computeShaderStageInfo;
+
+if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &computePipeline) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create compute pipeline!");
+}
+```
+
+The setup is a lot simpler, as we only require one shader stage and a pipeline layout. The pipeline layout works the same as with the graphics pipeline:
+
+```c++
+VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
+pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+pipelineLayoutInfo.setLayoutCount = 1;
+pipelineLayoutInfo.pSetLayouts = &computeDescriptorSetLayout;
+
+if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create compute pipeline layout!");
+}
+```
+
+## Compute space
+
+Before we get into how a compute shader works and how we submit compute workloads to the GPU, we need to talk about two important compute concepts: **work groups** and **invocations**. They define an abstract execution model for how compute workloads are processed by the compute hardware of the GPU in three dimensions (x, y, and z).
+
+**Work groups** define how the compute workloads are formed and processed by the the compute hardware of the GPU. You can think of them as work items the GPU has to work through. Work group dimensions are set by the application at command buffer time using a dispatch command.
+
+And each work group then is a collection of **invocations** that execute the same compute shader. Invocations can potentially run in parallel and their dimensions are set in the compute shader. Invocations within a single workgroup have access to shared memory.
+
+This image shows the relation between these two in three dimensions:
+
+![](/images/compute_space.svg)
+
+The number of dimensions for work groups (defined by `vkCmdDispatch`) and invocations depends (defined by the local sizes in the compute shader) on how input data is structured. If you e.g. work on a one-dimensional array, like we do in this chapter, you only have to specify the x dimension for both.
+
+As an example: If we dispatch a work group count of [64, 1, 1] with a compute shader local size of [32, 32, ,1], our compute shader will be invoked 64 x 32 x 32 = 65,536 times.
+
+Note that the maximum count for work groups and local sizes differs from implementation to implementation, so you should always check the compute related `maxComputeWorkGroupCount`, `maxComputeWorkGroupInvocations` and `maxComputeWorkGroupSize` limits in `VkPhysicalDeviceLimits`.
+
+## Compute shaders
+
+Now that we have learned about all the parts required to setup a compute shader pipeline, it's time to take a look at compute shaders. All of the things we learned about using GLSL shaders e.g. for vertex and fragment shaders also applies to compute shaders. The syntax is the same, and many concepts like passing data between the application and the shader are the same. But there are some important differences.
+
+A very basic compute shader for updating a linear array of particles may look like this:
+
+```glsl
+#version 450
+
+layout (binding = 0) uniform ParameterUBO {
+    float deltaTime;
+} ubo;
+
+struct Particle {
+    vec2 position;
+    vec2 velocity;
+    vec4 color;
+};
+
+layout(std140, binding = 1) readonly buffer ParticleSSBOIn {
+   Particle particlesIn[ ];
+};
+
+layout(std140, binding = 2) buffer ParticleSSBOOut {
+   Particle particlesOut[ ];
+};
+
+layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
+
+void main() 
+{
+    uint index = gl_GlobalInvocationID.x;  
+
+    Particle particleIn = particlesIn[index];
+
+    particlesOut[index].position = particleIn.position + particleIn.velocity.xy * ubo.deltaTime;
+    particlesOut[index].velocity = particleIn.velocity;
+    ...
+}
+```
+
+The top part of the shader contains the declarations for the shader's input. First is a uniform buffer object at binding 0, something we already learned about in this tutorial. Below we declare our Particle structure that matches the declaration in the C++ code. Binding 1 then refers to the shader storage buffer object with the particle data from the last frame (see the descriptor setup), and binding 2 points to the SSBO for the current frame, which is the one we'll be updating with this shader.
+
+An interesting thing is this compute-only declaration related to the compute space:
+
+```glsl
+layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
+```
+This defines the number invocations of this compute shader in the current work group. As noted earlier, this is the local part of the compute space. Hence the `local_` prefix. As we work on a linear 1D array of particles we only need to specify a number for x dimension in `local_size_x`.
+
+The `main` function then reads from the last frame's SSBO and writes the updated particle position to the SSBO for the current frame. Similar to other shader types, compute shaders have their own set of builtin input variables. Built-ins are always prefixed with `gl_`. One such built-in is `gl_GlobalInvocationID`, a variable that uniquely identifies the current compute shader invocation across the current dispatch. We use this to index into our particle array.
+
+## Running compute commands 
+
+### Dispatch
+
+Now it's time to actually tell the GPU to do some compute. This is done by calling `vkCmdDispatch` inside a command buffer. While not perfectly true, a dispatch is for compute as a draw call like `vkCmdDraw` is for graphics. This dispatches a given number of compute work items in at max. three dimensions.
+
+```c++
+VkCommandBufferBeginInfo beginInfo{};
+beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
+    throw std::runtime_error("failed to begin recording command buffer!");
+}
+
+...
+
+vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline);
+vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSets[i], 0, 0);
+
+vkCmdDispatch(computeCommandBuffer, PARTICLE_COUNT / 256, 1, 1);
+
+...
+
+if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
+    throw std::runtime_error("failed to record command buffer!");
+}
+```
+
+The `vkCmdDispatch` will dispatch `PARTICLE_COUNT / 256` local work groups in the x dimension. As our particles array is linear, we leave the other two dimensions at one, resulting in a one-dimensional dispatch. But why do we divide the number of particles (in our array) by 256? That's because in the previous paragraph we defined that every compute shader in a work group will do 256 invocations. So if we were to have 4096 particles, we would dispatch 16 work groups, with each work group running 256 compute shader invocations. Getting the two numbers right usually takes some tinkering and profiling, depending on your workload and the hardware you're running on. If your particle size would be dynamic and can't always be divided by e.g. 256, you can always use `gl_GlobalInvocationID` at the start of your compute shader and return from it if the global invocation index is greater than the number of your particles.
+
+And just as was the case for the compute pipeline, a compute command buffer contains a lot less state than a graphics command buffer. There's no need to start a render pass or set a viewport.
+
+### Submitting work
+
+As our sample does both compute and graphics operations, we'll be doing two submits to both the graphics and compute queue per frame (see the `drawFrame` function):
+
+```c++
+...
+if (vkQueueSubmit(computeQueue, 1, &submitInfo, nullptr) != VK_SUCCESS) {
+    throw std::runtime_error("failed to submit compute command buffer!");
+};
+...
+if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
+    throw std::runtime_error("failed to submit draw command buffer!");
+}
+```
+
+The first submit to the compute queue updates the particle positions using the compute shader, and the second submit will then use that updated data to draw the particle system.
+
+### Synchronizing graphics and compute
+
+Synchronization is an important part of Vulkan, even more so when doing compute in conjunction with graphics. Wrong or lacking synchronization may result in the vertex stage starting to draw (=read) particles while the compute shader hasn't finished updating (=write) them (read-after-write hazard), or the compute shader could start updating particles that are still in use by the vertex part of the pipeline (write-after-read hazard).
+
+So we must make sure that those cases don't happen by properly synchronizing the graphics and the compute load. There are different ways of doing so, depending on how you submit your compute workload but in our case with two separate submits, we'll be using [semaphores](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores) and [fences](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Fences) to ensure that the vertex shader won't start fetching vertices until the compute shader has finished updating them.
+
+This is necessary as even though the two submits are ordered one-after-another, there is no guarantee that they execute on the GPU in this order. Adding in wait and signal semaphores ensures this execution order.
+
+So we first add a new set of synchronization primitives for the compute work in `createSyncObjects`. The compute fences, just like the graphics fences, are created in the signaled state because otherwise, the first draw would time out while waiting for the fences to be signaled as detailed [here](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Waiting-for-the-previous-frame):
+
+```c++
+std::vector<VkFence> computeInFlightFences;
+std::vector<VkSemaphore> computeFinishedSemaphores;
+...
+computeInFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
+computeFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+
+VkSemaphoreCreateInfo semaphoreInfo{};
+semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+VkFenceCreateInfo fenceInfo{};
+fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+    ...
+    if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &computeFinishedSemaphores[i]) != VK_SUCCESS ||
+        vkCreateFence(device, &fenceInfo, nullptr, &computeInFlightFences[i]) != VK_SUCCESS) {
+        throw std::runtime_error("failed to create compute synchronization objects for a frame!");
+    }
+}
+```
+We then use these to synchronize the compute buffer submission with the graphics submission:
+
+```c++
+// Compute submission
+vkWaitForFences(device, 1, &computeInFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+
+updateUniformBuffer(currentFrame);
+
+vkResetFences(device, 1, &computeInFlightFences[currentFrame]);
+
+vkResetCommandBuffer(computeCommandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
+recordComputeCommandBuffer(computeCommandBuffers[currentFrame]);
+
+submitInfo.commandBufferCount = 1;
+submitInfo.pCommandBuffers = &computeCommandBuffers[currentFrame];
+submitInfo.signalSemaphoreCount = 1;
+submitInfo.pSignalSemaphores = &computeFinishedSemaphores[currentFrame];
+
+if (vkQueueSubmit(computeQueue, 1, &submitInfo, computeInFlightFences[currentFrame]) != VK_SUCCESS) {
+    throw std::runtime_error("failed to submit compute command buffer!");
+};
+
+// Graphics submission
+vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+
+...
+
+vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
+recordCommandBuffer(commandBuffers[currentFrame], imageIndex);
+
+VkSemaphore waitSemaphores[] = { computeFinishedSemaphores[currentFrame], imageAvailableSemaphores[currentFrame] };
+VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+submitInfo = {};
+submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+
+submitInfo.waitSemaphoreCount = 2;
+submitInfo.pWaitSemaphores = waitSemaphores;
+submitInfo.pWaitDstStageMask = waitStages;
+submitInfo.commandBufferCount = 1;
+submitInfo.pCommandBuffers = &commandBuffers[currentFrame];
+submitInfo.signalSemaphoreCount = 1;
+submitInfo.pSignalSemaphores = &renderFinishedSemaphores[currentFrame];
+
+if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
+    throw std::runtime_error("failed to submit draw command buffer!");
+}
+```
+
+Similar to the sample in the [semaphores chapter](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores), this setup will immediately run the compute shader as we haven't specified any wait semaphores. This is fine, as we are waiting for the compute command buffer of the current frame to finish execution before the compute submission with the `vkWaitForFences` command.
+
+The graphics submission on the other hand needs to wait for the compute work to finish so it doesn't start fetching vertices while the compute buffer is still updating them. So we wait on the `computeFinishedSemaphores` for the current frame and have the graphics submission wait on the `VK_PIPELINE_STAGE_VERTEX_INPUT_BIT` stage, where vertices are consumed.
+
+But it also needs to wait for presentation so the fragment shader won't output to the color attachments until the image has been presented. So we also wait on the `imageAvailableSemaphores` on the current frame at the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage.
+
+## Drawing the particle system
+
+Earlier on, we learned that buffers in Vulkan can have multiple use-cases and so we created the shader storage buffer that contains our particles with both the shader storage buffer bit and the vertex buffer bit. This means that we can use the shader storage buffer for drawing just as we used "pure" vertex buffers in the previous chapters.
+
+We first setup the vertex input state to match our particle structure:
+
+```c++
+struct Particle {
+    ...
+
+    static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
+        std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
+
+        attributeDescriptions[0].binding = 0;
+        attributeDescriptions[0].location = 0;
+        attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
+        attributeDescriptions[0].offset = offsetof(Particle, position);
+
+        attributeDescriptions[1].binding = 0;
+        attributeDescriptions[1].location = 1;
+        attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
+        attributeDescriptions[1].offset = offsetof(Particle, color);
+
+        return attributeDescriptions;
+    }
+};
+```
+
+Note that we don't add `velocity` to the vertex input attributes, as this is only used by the compute shader.
+
+We then bind and draw it like we would with any vertex buffer:
+
+```c++
+vkCmdBindVertexBuffers(commandBuffer, 0, 1, &shaderStorageBuffer[currentFrame], offsets);
+
+vkCmdDraw(commandBuffer, PARTICLE_COUNT, 1, 0, 0);
+```
+
+## Conclusion
+
+In this chapter, we learned how to use compute shaders to offload work from the CPU to the GPU. Without compute shaders, many effects in modern games and applications would either not be possible or would run a lot slower. But even more than graphics, compute has a lot of use-cases, and this chapter only gives you a glimpse of what's possible. So now that you know how to use compute shaders, you may want to take look at some advanced compute topics like:
+
+- Shared memory
+- [Asynchronous compute](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/performance/async_compute)
+- Atomic operations
+- [Subgroups](https://www.khronos.org/blog/vulkan-subgroup-tutorial)
+
+You can find some advanced compute samples in the [official Khronos Vulkan Samples repository](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/api).
+
+[C++ code](/code/31_compute_shader.cpp) /
+[Vertex shader](/code/31_shader_compute.vert) /
+[Fragment shader](/code/31_shader_compute.frag) /
+[Compute shader](/code/31_shader_compute.comp)
diff --git a/kr/90_FAQ.md b/kr/90_FAQ.md
new file mode 100644
index 00000000..3378362a
--- /dev/null
+++ b/kr/90_FAQ.md
@@ -0,0 +1,58 @@
+This page lists solutions to common problems that you may encounter while
+developing Vulkan applications.
+
+## I get an access violation error in the core validation layer
+
+Make sure
+that MSI Afterburner / RivaTuner Statistics Server is not running, because it
+has some compatibility problems with Vulkan.
+
+## I don't see any messages from the validation layers / Validation layers are not available
+
+First make sure that the validation layers get a chance to print errors by keeping the
+terminal open after your program exits. You can do this from Visual Studio by running
+your program with Ctrl-F5 instead of F5, and on Linux by executing your program from
+a terminal window. If there are still no messages and you are sure that validation
+layers are turned on, then you should ensure that your Vulkan SDK is correctly
+installed by following the "Verify the Installation" instructions [on this page](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html). Also ensure that your SDK version is at least 1.1.106.0 to support the `VK_LAYER_KHRONOS_validation` layer.
+
+## vkCreateSwapchainKHR triggers an error in SteamOverlayVulkanLayer64.dll
+
+This appears to be a compatibility problem in the Steam client beta. There are a
+few possible workarounds:
+    * Opt out of the Steam beta program.
+    * Set the `DISABLE_VK_LAYER_VALVE_steam_overlay_1` environment variable to `1`
+    * Delete the Steam overlay Vulkan layer entry in the registry under `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers`
+
+Example:
+
+![](/images/steam_layers_env.png)
+
+## vkCreateInstance fails with VK_ERROR_INCOMPATIBLE_DRIVER
+
+If you are using MacOS with the latest MoltenVK SDK then `vkCreateInstance` may return the `VK_ERROR_INCOMPATIBLE_DRIVER` error. This is because [Vulkan SDK version 1.3.216 or newer](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html) requires you to enable the `VK_KHR_PORTABILITY_subset` extension to use MoltenVK, because it is currently not fully conformant.
+
+You have to add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` flag to your `VkInstanceCreateInfo` and add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME` to your instance extension list.
+
+Code example:
+
+```c++
+...
+
+std::vector<const char*> requiredExtensions;
+
+for(uint32_t i = 0; i < glfwExtensionCount; i++) {
+    requiredExtensions.emplace_back(glfwExtensions[i]);
+}
+
+requiredExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
+
+createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+
+createInfo.enabledExtensionCount = (uint32_t) requiredExtensions.size();
+createInfo.ppEnabledExtensionNames = requiredExtensions.data();
+
+if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
+    throw std::runtime_error("failed to create instance!");
+}
+```
diff --git a/kr/95_Privacy_policy.md b/kr/95_Privacy_policy.md
new file mode 100644
index 00000000..a4b3d4e4
--- /dev/null
+++ b/kr/95_Privacy_policy.md
@@ -0,0 +1,21 @@
+## General
+
+This privacy policy applies to the information that is collected when you use vulkan-tutorial.com or any of its subdomains. It describes how the owner of this website, Alexander Overvoorde, collects, uses and shares information about you.
+
+## Analytics
+
+This website collects analytics about visitors using a self-hosted instance of Matomo ([https://matomo.org/](https://matomo.org/)), formerly known as Piwik. It records which pages you visit, what type of device and browser you use, how long you view a given page and where you came from. This information is anonymized by only recording the first two bytes of your IP address (e.g. `123.123.xxx.xxx`). These anonymized logs are stored for an indefinite amount of time.
+
+These analytics are used for the purpose of tracking how content on the website is consumed, how many people visit the website in general, and which other websites link here. This makes it easier to engage with the community and determine which areas of the website should be improved, for example if extra time should be spent on facilitating mobile reading.
+
+This data is not shared with third parties.
+
+## Advertisement
+
+This website uses a third-party advertisement server that may use cookies to track activities on the website to measure engagement with advertisements.
+
+## Comments
+
+Each chapter includes a comment section at the end that is provided by the third-party Disqus service. This service collects identity data to facilitate the reading and submission of comments, and aggregate usage information to improve their service.
+
+The full privacy policy of this third-party service can be found at [https://help.disqus.com/terms-and-policies/disqus-privacy-policy](https://help.disqus.com/terms-and-policies/disqus-privacy-policy).
\ No newline at end of file

From f2d96bd3bc337b5fab078673603a19a451f5985e Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 18 Sep 2023 23:03:56 +0900
Subject: [PATCH 02/47] kr translate 02 (except MacOS part)

---
 kr/02_Development_environment.md | 260 ++++++++++++-------------------
 1 file changed, 96 insertions(+), 164 deletions(-)

diff --git a/kr/02_Development_environment.md b/kr/02_Development_environment.md
index 8eac260f..79b30a62 100644
--- a/kr/02_Development_environment.md
+++ b/kr/02_Development_environment.md
@@ -1,106 +1,62 @@
-In this chapter we'll set up your environment for developing Vulkan applications
-and install some useful libraries. All of the tools we'll use, with the
-exception of the compiler, are compatible with Windows, Linux and MacOS, but the
-steps for installing them differ a bit, which is why they're described
-separately here.
+이 챕터에서는 Vulkan 응용 프로그램 개발을 위한 환경을 설정하고 몇 가지 유용한 라이브러리를 설치할 것입니다. 우리가 사용할 툴들은 윈도우즈, 리눅스와 MacOS에서 호환되지만 설치 방법은 약간씩 다르기 때문에 개별적으로 설명합니다.
 
-## Windows
+## 윈도우즈
 
-If you're developing for Windows, then I will assume that you are using Visual
-Studio to compile your code. For complete C++17 support, you need to use either
-Visual Studio 2017 or 2019. The steps outlined below were written for VS 2017.
+윈도우에서 개발하시는 경우엔 코드 컴파일에는 비주얼 스튜디오를 사용한다고 가정하겠습니다. C++17 지원을 위해서는 비주얼 스튜디오 2017이나 2019가 필요합니다. 아래 설명하는 단계들은 2017을 기준으로 작성되었습니다.
 
 ### Vulkan SDK
 
-The most important component you'll need for developing Vulkan applications is
-the SDK. It includes the headers, standard validation layers, debugging tools
-and a loader for the Vulkan functions. The loader looks up the functions in the
-driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that.
+Vulkan 응용 프로그램 개발을 위해 가장 중요한 구성요소는 SDK입니다. SDK는 헤더, 표준 검증 레이어, 디버깅 도구와 Vulkan 함수의 로더(loader)가 포함되어 있습니다. 로더는 런타임에 드라이버의 함수를 탐색하는 OpenGL에서의 GLEW와 유사한 도구입니다.
 
-The SDK can be downloaded from [the LunarG website](https://vulkan.lunarg.com/)
-using the buttons at the bottom of the page. You don't have to create an
-account, but it will give you access to some additional documentation that may
-be useful to you.
+SDK는 [LunarG 웹사이트](https://vulkan.lunarg.com/) 페이지 하단의 버튼을 통해 다운로드 할 수 있습니다. 계정을 만드실 필요는 없지만 계정을 만들면 유용하게 활용할 수 있는 추가적인 문서에 접근할 수 있습니다.
 
 ![](/images/vulkan_sdk_download_buttons.png)
 
-Proceed through the installation and pay attention to the install location of
-the SDK. The first thing we'll do is verify that your graphics card and driver
-properly support Vulkan. Go to the directory where you installed the SDK, open
-the `Bin` directory and run the `vkcube.exe` demo. You should see the following:
+설치 과정을 거치시고, SDK의 설치 경로를 주의깊게 확인하십시오. 처음으로 할 것은 여러분의 그래픽 카드와 드라이버가 Vulkan을 제대로 지원하는지 확인하는 것입니다. SDK를 설치한 폴더로 가서 `Bin` 디렉터리 안의 `vkcube.exe` 데모를 실행해 보세요. 아래와 같은 화면이 나타나야 합니다:
 
 ![](/images/cube_demo.png)
 
-If you receive an error message then ensure that your drivers are up-to-date,
-include the Vulkan runtime and that your graphics card is supported. See the
-[introduction chapter](!en/Introduction) for links to drivers from the major
-vendors.
+오류 메시지가 나타난다면 드라이버가 최신 버전인지 확인하고, 그래픽 카드가 Vulkan을 지원하고 Vulkan 런타임이 드라이브에 포함되어 있는지 확인하세요. 주요 벤더들의 드라이버 링크는 [introduction](!en/Introduction) 챕터를 확인하세요.
 
-There is another program in this directory that will be useful for development. The `glslangValidator.exe` and `glslc.exe` programs will be used to compile shaders from the
-human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to
-bytecode. We'll cover this in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules)
-chapter. The `Bin` directory also contains the binaries of the Vulkan loader
-and the validation layers, while the `Lib` directory contains the libraries.
+이 폴더에는 개발에 유용한 다른 프로그램들도 있습니다. `glslangValidator.exe`와 `glslc.exe`는 사람이 읽을 수 있는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) 코드를 바이트 코드로 변환하기 위해 사용됩니다. [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) 챕터에서 이 내용을 자세히 살펴볼 것입니다. `Bin` 디렉터리에는 또한 Vulkan 로더와 검증 레이어의 바이너리들을 포함하고 있으며, `Lib` 디렉터리에는 라이브러리들이 들어 있습니다.
 
-Lastly, there's the `Include` directory that contains the Vulkan headers. Feel free to explore the other files, but we won't need them for this tutorial.
+마지막으로 `Include` 디렉터리에는 Vulkan 헤더들이 있습니다. 다른 파일들도 자유롭게 살펴보시길 바라지만 이 튜토리얼에서는 필요하지 않습니다.
 
 ### GLFW
 
-As mentioned before, Vulkan by itself is a platform agnostic API and does not
-include tools for creating a window to display the rendered results. To benefit
-from the cross-platform advantages of Vulkan and to avoid the horrors of Win32,
-we'll use the [GLFW library](http://www.glfw.org/) to create a window, which
-supports Windows, Linux and MacOS. There are other libraries available for this
-purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that
-it also abstracts away some of the other platform-specific things in Vulkan
-besides just window creation.
-
-You can find the latest release of GLFW on the [official website](http://www.glfw.org/download.html).
-In this tutorial we'll be using the 64-bit binaries, but you can of course also
-choose to build in 32 bit mode. In that case make sure to link with the Vulkan
-SDK binaries in the `Lib32` directory instead of `Lib`. After downloading it, extract the archive
-to a convenient location. I've chosen to create a `Libraries` directory in the
-Visual Studio directory under documents.
+앞서 언급한 것처럼 Vulkan은 플랫폼 독립적인 API여서 렌더링 결과를 표시할 윈도우 생성을 위한 도구 같은것은 포함되어 있지 않습니다. Vulkan의 크로스 플랫폼 이점을 살리면서도 Win32의 어려움을 회피하는 방법으로 우리는 [GLFW library](http://www.glfw.org/)를 사용하여 윈도우를 만들 것입니다. GLFW는 윈도우, 리눅스와 MacOS를 모두 지원합니다. 비슷한 목적으로 사용 가능한 [SDL](https://www.libsdl.org/)과 같은 라이브러리도 있지만, GLFW는 윈도우 생성뿐만 아니라 Vulkan의 다른 추가적인 플랫폼 의존적인 작업들에 대한 추상화도 제공해 준다는 것입니다.
+
+GLFW의 최신 버전을 [공식 웹사이트](http://www.glfw.org/download.html)에서 찾을 수 있습니다. 이 튜토리얼에서는 64비트 바이너리를 사용할 것인데 32비트 모드로 빌드하셔도 됩니다. 그런 경우 Vulkan SDK의 `Lib` 디렉터리 대신 `Lib32` 디렉터리의 라이브러리들을 링크하셔야 합니다. 다운로드 하시고 나서 편한 곳에 압축을 푸십시오. 저는 내 문서 아래의 비주얼 스튜디오 디렉터리 아래 `Libraries` 폴더를 만들어 그 곳에 넣었습니다.
 
 ![](/images/glfw_directory.png)
 
 ### GLM
 
-Unlike DirectX 12, Vulkan does not include a library for linear algebra
-operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a
-nice library that is designed for use with graphics APIs and is also commonly
-used with OpenGL.
+DirectX 12와는 다르게, Vulkan은 선형대수 연산을 위한 라이브러리가 포함되어 있지 않아서 다운로드 해야 합니다. [GLM](http://glm.g-truc.net/)은 그래픽스 API를 위해 설계된 좋은 라이브러리로 OpenGL에서도 자주 사용됩니다.
 
-GLM is a header-only library, so just download the [latest version](https://github.com/g-truc/glm/releases)
-and store it in a convenient location. You should have a directory structure
-similar to the following now:
+GLM은 헤더만으로 구성된 라이브러리로, [최신 버전](https://github.com/g-truc/glm/releases)을 다운로드하고 적절한 위치에 가져다 놓으세요. 그러면 아래와 같은 디렉터리 구조가 될 겁니다:
 
 ![](/images/library_directory.png)
 
-### Setting up Visual Studio
+### 비주얼 스튜디오 설정
 
-Now that you've installed all of the dependencies we can set up a basic Visual
-Studio project for Vulkan and write a little bit of code to make sure that
-everything works.
+필요한 의존성(dependencies)를 설치했으므로 Vulkan 개발을 위한 비주얼 스튜디오 프로젝트를 설정하고 모든 것들이 올바로 동작하는지 확인하기 위한 짧은 코드를 작성해 보겠습니다.
 
-Start Visual Studio and create a new `Windows Desktop Wizard` project by entering a name and pressing `OK`.
+비주얼 스튜디오를 실행하고 `Windows Desktop Wizard` 프로젝트를 선택한 뒤 이름을 설정하고 `OK`를 누르세요.
 
 ![](/images/vs_new_cpp_project.png)
 
-Make sure that `Console Application (.exe)` is selected as application type so that we have a place to print debug messages to, and check `Empty Project` to prevent Visual Studio from adding boilerplate code.
+`Console Application (.exe)`를 선택해서 우리의 응용 프로그램이 디버깅 메시지를 표시할 수 있도록 하고 `Empty Project`로 비주얼 스튜디오가 보일러플레이트(boilerplate) 코드를 생성하지 않도록 하세요.
 
 ![](/images/vs_application_settings.png)
 
-Press `OK` to create the project and add a C++ source file. You should
-already know how to do that, but the steps are included here for completeness.
+`OK`를 눌러 프로젝트를 만들고 C++ 소스 파일을 추가 하세요. 어떻게 하는지 알고 계실 테지만, 하는 법을 알려 드리겠습니다.
 
 ![](/images/vs_new_item.png)
 
 ![](/images/vs_new_source_file.png)
 
-Now add the following code to the file. Don't worry about trying to
-understand it right now; we're just making sure that you can compile and run
-Vulkan applications. We'll start from scratch in the next chapter.
+이제 아래 코드를 파일에 추가 하세요. 지금은 이해하려 하실 필요 없습니다. 그냥 Vulkan 응용 프로그램을 컴파일하고 실행할 수 있는지 확인하세요. 다음 챕터에서 다시 처음부터 시작할 것입니다.
 
 ```c++
 #define GLFW_INCLUDE_VULKAN
@@ -140,158 +96,138 @@ int main() {
 }
 ```
 
-Let's now configure the project to get rid of the errors. Open the project
-properties dialog and ensure that `All Configurations` is selected, because most
-of the settings apply to both `Debug` and `Release` mode.
+이제 프로젝트 설정을 통해 오류를 해결해 봅시다. 프로젝트 설정 창을 열고 `All Configurations`이 선택되어 있는지 확인하세요. 대부분의 세팅이 `Debug`와 `Release` 모드에 공통적으로 해당됩니다:
 
 ![](/images/vs_open_project_properties.png)
 
 ![](/images/vs_all_configs.png)
 
-Go to `C++ -> General -> Additional Include Directories` and press `<Edit...>`
-in the dropdown box.
+`C++ -> General -> Additional Include Directories`로 가셔서 드롭다운 메뉴에서 `<Edit...>`을 누르세요:
 
 ![](/images/vs_cpp_general.png)
 
-Add the header directories for Vulkan, GLFW and GLM:
+Vulkan, GLFW, GLM을 위한 헤더 디렉터리를 추가하세요:
 
 ![](/images/vs_include_dirs.png)
 
-Next, open the editor for library directories under `Linker -> General`:
+다음으로 `Linker -> General`에서 추가 라이브러리 디렉터리 설정창을 여세요:
 
 ![](/images/vs_link_settings.png)
 
-And add the locations of the object files for Vulkan and GLFW:
+그리고 Vulkan과 GLFW를 위한 오브젝트 파일의 위치를 추가하세요:
 
 ![](/images/vs_link_dirs.png)
 
-Go to `Linker -> Input` and press `<Edit...>` in the `Additional Dependencies`
-dropdown box.
+`Linker -> Input`에서 `Additional Dependencies`의 `<Edit...>`의 드롭다운 메뉴를 누르세요:
 
 ![](/images/vs_link_input.png)
 
-Enter the names of the Vulkan and GLFW object files:
+Vulkan과 GLFW의 오브젝트 파일 이름을 추가하세요:
 
 ![](/images/vs_dependencies.png)
 
-And finally change the compiler to support C++17 features:
+마지막으로 컴파일러가 C++17 기능을 지원하도록 설정하세요:
 
 ![](/images/vs_cpp17.png)
 
-You can now close the project properties dialog. If you did everything right
-then you should no longer see any more errors being highlighted in the code.
+이제 프로젝트 설정 창을 닫아도 됩니다. 모두 제대로 설정되었으면 더이상 에러 메시지가 나타나지 않을 겁니다.
 
-Finally, ensure that you are actually compiling in 64 bit mode:
+마지막으로 64비트 모드에서 컴파일을 하는지 확인하시고:
 
 ![](/images/vs_build_mode.png)
 
-Press `F5` to compile and run the project and you should see a command prompt
-and a window pop up like this:
+`F5`를 눌러 컴파일 후 실행을 해 보면 명령 창(command prompt)과 윈도우가 아래처럼 나타나는 것을 볼 수 있을 겁니다:
 
 ![](/images/vs_test_window.png)
 
-The number of extensions should be non-zero. Congratulations, you're all set for
-[playing with Vulkan](!en/Drawing_a_triangle/Setup/Base_code)!
+extention의 숫자는 0이 아니어야 합니다. 축하합니다. [Vulkan을 즐기기 위한](!en/Drawing_a_triangle/Setup/Base_code)! 모든 준비가 완료되었습니다.
 
-## Linux
+## 리눅스
 
-These instructions will be aimed at Ubuntu, Fedora and Arch Linux users, but you may be able to follow
-along by changing the package manager-specific commands to the ones that are appropriate for you. You should have a compiler that supports C++17 (GCC 7+ or Clang 5+). You'll also need `make`.
+이 가이드는 우분투, 페도라와 Arch 리눅스 유저를 대상으로 하지만, 패키지 매니저별로 다른 명령어만 사용하시면 그대로 따라하시면 됩니다. C++17을 지원하는 컴파일러 (GCC 7+ 또는 Clang 5+)를 사용하셔야 합니다. `make`도 필요합니다.
 
-### Vulkan Packages
+### Vulkan 패키지
 
-The most important components you'll need for developing Vulkan applications on Linux are the Vulkan loader, validation layers, and a couple of command-line utilities to test whether your machine is Vulkan-capable:
+리눅스에서의 Vulkan 응용 프로그램 개발을 위해 가장 중요한 구성요소는 Vulkan 로더, 검증 레이어와 여러분의 기기가 Vulkan을 지원하는지 테스트하기 위한 몇 개의 명령줄 유틸리티들입니다.
 
-* `sudo apt install vulkan-tools` or `sudo dnf install vulkan-tools`: Command-line utilities, most importantly `vulkaninfo` and `vkcube`. Run these to confirm your machine supports Vulkan.
-* `sudo apt install libvulkan-dev` or `sudo dnf install vulkan-loader-devel` : Installs Vulkan loader. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that.
-* `sudo apt install vulkan-validationlayers-dev spirv-tools` or `sudo dnf install mesa-vulkan-devel vulkan-validation-layers-devel`: Installs the standard validation layers and required SPIR-V tools. These are crucial when debugging Vulkan applications, and we'll discuss them in the upcoming chapter.
+- `sudo apt install vulkan-tools` 또는 `sudo dnf install vulkan-tools`: 명령줄 유틸리티로, 가장 중요한 것은 `vulkaninfo`와 `vkcube`입니다. 기기가 Vulkan을 지원하는지 확인하기 위해 실행해 보십시오.
+- `sudo apt install libvulkan-dev` 또는 `sudo dnf install vulkan-loader-devel` : Vulkan 로더를 설치합니다. 로더는 OpenGL에서의 GLEW처럼, 런타임에 드라이버의 함수들을 탐색합니다.
+- `sudo apt install vulkan-validationlayers-dev spirv-tools` 또는 `sudo dnf install mesa-vulkan-devel vulkan-validation-layers-devel`: 표준 검증 레이어와 필요한 SPIR-V 도구들을 설치합니다. Vulkan 응용 프로그램을 디버깅하기 위해 필수적이고, 이어지는 챕터에서 자세히 다룰 것입니다.
 
-On Arch Linux, you can run `sudo pacman -S vulkan-devel` to install all the
-required tools above.
+Arch 리눅스에서는 위 도구들을 설치하기 위해서는 `sudo pacman -S vulkan-devel`를 실행하면 됩니다.
 
-If installation was successful, you should be all set with the Vulkan portion. Remember to run
- `vkcube` and ensure you see the following pop up in a window:
+성공적으로 설치가 되었다면, Vulkan 관련 부분은 완료된 것입니다. `vkcube`를 실행해서 아래와 같은 윈도우가 나타나는 것을 확인하십시오.
 
 ![](/images/cube_demo_nowindow.png)
 
-If you receive an error message then ensure that your drivers are up-to-date,
-include the Vulkan runtime and that your graphics card is supported. See the
-[introduction chapter](!en/Introduction) for links to drivers from the major
-vendors.
+오류 메시지가 나타난다면 드라이버가 최신 버전인지 확인하고, 그래픽 카드가 Vulkan을 지원하고 Vulkan 런타임이 드라이브에 포함되어 있는지 확인하세요. 주요 벤더들의 드라이버 링크는 [introduction](!en/Introduction) 챕터를 확인하세요.
 
 ### X Window System and XFree86-VidModeExtension
-It is possible that these libraries are not on the system, if not, you can install them using the following commands:
-* `sudo apt install libxxf86vm-dev` or `dnf install libXxf86vm-devel`: Provides an interface to the XFree86-VidModeExtension.
-* `sudo apt install libxi-dev` or `dnf install libXi-devel`: Provides an X Window System client interface to the XINPUT extension.
+
+이 라이브러리들이 시스템에 없을 수도 있습니다. 그런 경우엔 다음 명령어를 사용해 설치할 수 있습니다.
+
+- `sudo apt install libxxf86vm-dev` 또는 `dnf install libXxf86vm-devel`: XFree86-VidModeExtension에 대한 인터페이스를 제공합니다.
+- `sudo apt install libxi-dev` or `dnf install libXi-devel`: X Window System에서 XINPUT 확장에 대한 클라이언트 인터페이스를 제공합니다.
 
 ### GLFW
 
-As mentioned before, Vulkan by itself is a platform agnostic API and does not
-include tools for creation a window to display the rendered results. To benefit
-from the cross-platform advantages of Vulkan and to avoid the horrors of X11,
-we'll use the [GLFW library](http://www.glfw.org/) to create a window, which
-supports Windows, Linux and MacOS. There are other libraries available for this
-purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that
-it also abstracts away some of the other platform-specific things in Vulkan
-besides just window creation.
+앞서 언급한 것처럼 Vulkan은 플랫폼 독립적인 API여서 렌더링 결과를 표시할 윈도우 생성을 위한 도구 같은것은 포함되어 있지 않습니다. Vulkan의 크로스 플랫폼 이점을 살리면서도 Win32의 어려움을 회피하는 방법으로 우리는 [GLFW library](http://www.glfw.org/)를 사용하여 윈도우를 만들 것입니다. GLFW는 윈도우, 리눅스와 MacOS를 모두 지원합니다. 비슷한 목적으로 사용 가능한 [SDL](https://www.libsdl.org/)과 같은 라이브러리도 있지만, GLFW는 윈도우 생성뿐만 아니라 Vulkan의 다른 추가적인 플랫폼 의존적인 작업들에 대한 추상화도 제공해 준다는 것입니다.
 
-We'll be installing GLFW from the following command:
+다음 명령문을 통해 GLFW를 설치할 것입니다.:
 
 ```bash
 sudo apt install libglfw3-dev
 ```
+
 or
+
 ```bash
 sudo dnf install glfw-devel
 ```
+
 or
+
 ```bash
 sudo pacman -S glfw-wayland # glfw-x11 for X11 users
 ```
 
 ### GLM
 
-Unlike DirectX 12, Vulkan does not include a library for linear algebra
-operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a
-nice library that is designed for use with graphics APIs and is also commonly
-used with OpenGL.
+DirectX 12와는 다르게, Vulkan은 선형대수 연산을 위한 라이브러리가 포함되어 있지 않아서 다운로드 해야 합니다. [GLM](http://glm.g-truc.net/)은 그래픽스 API를 위해 설계된 좋은 라이브러리로 OpenGL에서도 자주 사용됩니다.
 
-It is a header-only library that can be installed from the `libglm-dev` or
-`glm-devel` package:
+GLM은 헤더만으로 구성된 라이브러리로, `libglm-dev` 또는 `glm-devel` 패키지를 통해 설치 가능합니다:
 
 ```bash
 sudo apt install libglm-dev
 ```
-or
+
+또는
+
 ```bash
 sudo dnf install glm-devel
 ```
-or
+
+또는
+
 ```bash
 sudo pacman -S glm
 ```
 
-### Shader Compiler
+### 셰이더 컴파일러
 
-We have just about all we need, except we'll want a program to compile shaders from the human-readable  [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to bytecode.
+필요한 것들이 거의 다 준비되었는데, 사람이 읽을 수 있는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)를 바이트 코드로 컴파일해 주는 프로그램은 아직 설치되지 않았습니다.
 
-Two popular shader compilers are Khronos Group's `glslangValidator` and Google's `glslc`. The latter has a familiar GCC- and Clang-like usage, so we'll go with that: on Ubuntu, download Google's [unofficial binaries](https://github.com/google/shaderc/blob/main/downloads.md) and copy `glslc` to your `/usr/local/bin`. Note you may need to `sudo` depending on your permissions. On Fedora use `sudo dnf install glslc`, while on Arch Linux run `sudo pacman -S shaderc`.  To test, run `glslc` and it should rightfully complain we didn't pass any shaders to compile:
+두 가지 유명한 셰이더 컴파일러는 크로노스 그룹의 `glslangValidator`와 구글의 `glslc`입니다. 후자는 우리에게 익숙한 GCC와 Clang과 유사한 사용법을 제공하기 때문에 그것을 사용할 것입니다. 우분투에서, 구글의 [공식 바이너리](https://github.com/google/shaderc/blob/main/downloads.md)를 다운로드 하고 `glslc`를 `/usr/local/bin`에 복사하십시오. 권한에 따라 `sudo`를 사용해야 할 수 있습니다. 페도라에서는 `sudo dnf install glslc`, Arch 리눅스에서는 `sudo pacman -S shaderc`를 사용하십시오. 테스트를 위해 `glslc`를 실행하면 컴파일할 셰이더를 올바로 전달하지 않았다는 메시지가 나타날 겁니다.
 
 `glslc: error: no input files`
 
-We'll cover `glslc` in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) chapter.
+[shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) 챕터에서 `glslc`를 자세히 살펴볼 것입니다.
 
-### Setting up a makefile project
+### makefile 프로젝트 구성
 
-Now that you have installed all of the dependencies, we can set up a basic
-makefile project for Vulkan and write a little bit of code to make sure that
-everything works.
+필요한 의존성(dependencies)들을 모두 설치하였으니 Vulkan을 위한 기본 makefile 프로젝트를 만들고 약간의 코드 작성을 통해 모든 것들이 올바로 동작하는지 확인해 봅시다.
 
-Create a new directory at a convenient location with a name like `VulkanTest`.
-Create a source file called `main.cpp` and insert the following code. Don't
-worry about trying to understand it right now; we're just making sure that you
-can compile and run Vulkan applications. We'll start from scratch in the next
-chapter.
+`VulkanTest`와 같은 새 디렉터리를 편한 위치에 만들고 `main.cpp` 소스 파일을 생성한 뒤, 다음 코드를 삽입하세요. 지금은 이해하려 하지 마시고, Vulkan 응용 프로그램을 컴파일하고 실행할 수 있는지만 확인하시면 됩니다. 다음 챕터에서 처음부터 다시 시작할 것입니다.
 
 ```c++
 #define GLFW_INCLUDE_VULKAN
@@ -331,56 +267,52 @@ int main() {
 }
 ```
 
-Next, we'll write a makefile to compile and run this basic Vulkan code. Create a
-new empty file called `Makefile`. I will assume that you already have some basic
-experience with makefiles, like how variables and rules work. If not, you can
-get up to speed very quickly with [this tutorial](https://makefiletutorial.com/).
+다음으로 기본 Vulkan 코드를 컴파일하고 실행하기 위한 makefile을 작성할 것입니다. `Makefile`이라는 이름으로 새 파일을 만드십시오. 저는 여러분들이 기본적인 makefile 사용 경험이 있다고 가정할 것입니다. 예를 들어 변수(variable)와 규칙(rule)이 어떻게 동작하는지 등을 이야기하는 것입니다. 그렇지 않으면, [이 튜토리얼](https://makefiletutorial.com/)을 통해 빠르게 살펴 보십시오.
 
-We'll first define a couple of variables to simplify the remainder of the file.
-Define a `CFLAGS` variable that will specify the basic compiler flags:
+우선 나머지 부분을 간략하 하기 위해 몇 가지 변수를 정의할 것입니다. 기본 컴파일러 플래그를 명시하기 위해 `CFLAGS` 변수를 정의합니다.
 
 ```make
 CFLAGS = -std=c++17 -O2
 ```
 
-We're going to use modern C++ (`-std=c++17`), and we'll set optimization level to O2. We can remove -O2 to compile programs faster, but we should remember to place it back for release builds.
+모던(modern) C++ (`-std=c++17`)를 사용할 것이고, 최적화 수준(optimization level)을 O2로 설정할 것입니다. 빠른 컴파일을 위해 -O2를 제거할 수도 있지만, 릴리즈(release) 빌드에서는 포함해야 한다는 것을 잊으면 안됩니다.
 
-Similarly, define the linker flags in a `LDFLAGS` variable:
+비슷하게 `LDFLAGS` 변수로 링커 플래그를 정의합니다.
 
 ```make
 LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi
 ```
 
-The flag `-lglfw` is for GLFW, `-lvulkan` links with the Vulkan function loader and the remaining flags are low-level system libraries that GLFW needs. The remaining flags are dependencies of GLFW itself: the threading and window management.
+GLFW를 위해 `-lglfw` 플래그를, Vulkan 함수 로더를 위해 `-lvulkan`를 사용하고, 나머지는 GLFW가 필요로 하는 저수준(low-level) 시스템 라이브러리들입니다. 나머지 플래그들은 GLFW의 의존성들은 쓰레딩과 윈도우 관리와 관련한 플래그들입니다.
 
-It is possible that the `Xxf68vm` and `Xi` libraries are not yet installed on your system. You can find them in the following packages:
+`Xxf68vm`와 `Xi` 라이브러리가 여러분의 시스템에 아직 설치되어 있지 않을 수 있습니다. 다음 패키지로부터 찾을 수 있습니다:
 
 ```bash
 sudo apt install libxxf86vm-dev libxi-dev
 ```
-or
+
+또는
+
 ```bash
 sudo dnf install libXi-devel libXxf86vm-devel
 ```
-or
+
+또는
+
 ```bash
 sudo pacman -S libxi libxxf86vm
 ```
 
-Specifying the rule to compile `VulkanTest` is straightforward now. Make sure to
-use tabs for indentation instead of spaces.
+이제 `VulkanTest`를 위한 컴파일 규칙을 명시하는 것은 쉽습니다. 들여쓰기(indentation)에 스페이스 대신 탭을 사용하는 것을 잊지 마세요.
 
 ```make
 VulkanTest: main.cpp
 	g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS)
 ```
 
-Verify that this rule works by saving the makefile and running `make` in the
-directory with `main.cpp` and `Makefile`. This should result in a `VulkanTest`
-executable.
+위와 같은 규칙이 제대로 동작하는 것을, makefile을 저장한 뒤 `make`를 `main.cpp`와 `Makefile`이 있는 디렉터리에서 실행하여 확인하십시오. 그 결과 `VulkanTest` 실행 파일이 생성될 것입니다.
 
-We'll now define two more rules, `test` and `clean`, where the former will
-run the executable and the latter will remove a built executable:
+`test`와 `clean` 두 가지 규칙을 더 정의할 것인데, 앞의 것은 실행 파일을 실행하는 것이고 뒤의 것은 생성된 실행 파일을 삭제하는 것입니다.
 
 ```make
 .PHONY: test clean
@@ -392,7 +324,7 @@ clean:
 	rm -f VulkanTest
 ```
 
-Running `make test` should show the program running successfully, and displaying the number of Vulkan extensions. The application should exit with the success return code (`0`) when you close the empty window. You should now have a complete makefile that resembles the following:
+`make test`를 실행하면 프로그램이 성공적으로 실행될 것이고 Vulkan 확장 숫자를 보여줄 것입니다. 창을 닫으면 성공(`0`) 반환 코드를 반환하면서 응용 프로그램이 종료될 것입니다. 결과적으로 아래와 같은 makefile이 존재하게 됩니다:
 
 ```make
 CFLAGS = -std=c++17 -O2
@@ -410,13 +342,13 @@ clean:
 	rm -f VulkanTest
 ```
 
-You can now use this directory as a template for your Vulkan projects. Make a copy, rename it to something like `HelloTriangle` and remove all of the code in `main.cpp`.
+이제 이 디렉터리를 여러분의 Vulkan 프로젝트를 위한 템플릿으로 사용하시면 됩니다. 복사하고 이름을 `HelloTriangle`과 같은 것으로 바꾸고, `main.cpp`의 모든 내용을 지우면 됩니다.
 
-You are now all set for [the real adventure](!en/Drawing_a_triangle/Setup/Base_code).
+이제 [진정한 탐험](!en/Drawing_a_triangle/Setup/Base_code)을 위한 준비가 끝났습니다.
 
 ## MacOS
 
-These instructions will assume you are using Xcode and the [Homebrew package manager](https://brew.sh/). Also, keep in mind that you will need at least MacOS version 10.11, and your device needs to support the [Metal API](https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs).
+These instructions will assume you are using Xcode and the [Homebrew package manager](https://brew.sh/). Also, keep in mind that you will need at least MacOS version 10.11, and your device needs to support the [Metal API](<https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs>).
 
 ### Vulkan SDK
 
@@ -506,19 +438,19 @@ int main() {
 
 Keep in mind you are not required to understand all this code is doing yet, we are just setting up some API calls to make sure everything is working.
 
-Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the *Project Navigator* panel select your project. Open the *Build Settings* tab and then:
+Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the _Project Navigator_ panel select your project. Open the _Build Settings_ tab and then:
 
-* Find the **Header Search Paths** field and add a link to `/usr/local/include` (this is where Homebrew installs headers, so the glm and glfw3 header files should be there) and a link to `vulkansdk/macOS/include` for the Vulkan headers.
-* Find the **Library Search Paths** field and add a link to `/usr/local/lib` (again, this is where Homebrew installs libraries, so the glm and glfw3 lib files should be there) and a link to `vulkansdk/macOS/lib`.
+- Find the **Header Search Paths** field and add a link to `/usr/local/include` (this is where Homebrew installs headers, so the glm and glfw3 header files should be there) and a link to `vulkansdk/macOS/include` for the Vulkan headers.
+- Find the **Library Search Paths** field and add a link to `/usr/local/lib` (again, this is where Homebrew installs libraries, so the glm and glfw3 lib files should be there) and a link to `vulkansdk/macOS/lib`.
 
 It should look like so (obviously, paths will be different depending on where you placed on your files):
 
 ![](/images/xcode_paths.png)
 
-Now, in the *Build Phases* tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding the dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks).
+Now, in the _Build Phases_ tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding the dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks).
 
-* For glfw open the folder `/usr/local/lib` and there you will find a file name like `libglfw.3.x.dylib` ("x" is the library's version number, it might be different depending on when you downloaded the package from Homebrew). Simply drag that file to the Linked Frameworks and Libraries tab on Xcode.
-* For vulkan, go to `vulkansdk/macOS/lib`. Do the same for the both files `libvulkan.1.dylib` and `libvulkan.1.x.xx.dylib` (where "x" will be the version number of the the SDK you downloaded).
+- For glfw open the folder `/usr/local/lib` and there you will find a file name like `libglfw.3.x.dylib` ("x" is the library's version number, it might be different depending on when you downloaded the package from Homebrew). Simply drag that file to the Linked Frameworks and Libraries tab on Xcode.
+- For vulkan, go to `vulkansdk/macOS/lib`. Do the same for the both files `libvulkan.1.dylib` and `libvulkan.1.x.xx.dylib` (where "x" will be the version number of the the SDK you downloaded).
 
 After adding those libraries, in the same tab on **Copy Files** change `Destination` to "Frameworks", clear the subpath and deselect "Copy only when installing". Click on the "+" sign and add all those three frameworks here aswell.
 
@@ -528,8 +460,8 @@ Your Xcode configuration should look like:
 
 The last thing you need to setup are a couple of environment variables. On Xcode toolbar go to `Product` > `Scheme` > `Edit Scheme...`, and in the `Arguments` tab add the two following environment variables:
 
-* VK_ICD_FILENAMES = `vulkansdk/macOS/share/vulkan/icd.d/MoltenVK_icd.json`
-* VK_LAYER_PATH = `vulkansdk/macOS/share/vulkan/explicit_layer.d`
+- VK_ICD_FILENAMES = `vulkansdk/macOS/share/vulkan/icd.d/MoltenVK_icd.json`
+- VK_LAYER_PATH = `vulkansdk/macOS/share/vulkan/explicit_layer.d`
 
 It should look like so:
 
@@ -541,4 +473,4 @@ Finally, you should be all set! Now if you run the project (remembering to setti
 
 The number of extensions should be non-zero. The other logs are from the libraries, you might get different messages from those depending on your configuration.
 
-You are now all set for [the real thing](!en/Drawing_a_triangle/Setup/Base_code).
+You are now all set for [the real thing](!kr/Drawing_a_triangle/Setup/Base_code).

From fff7580c562916763738756da885674dd78d88f7 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 19 Sep 2023 20:10:22 +0900
Subject: [PATCH 03/47] kr translate 03-00-00 base code

---
 .../00_Setup/00_Base_code.md                  | 124 +++++-------------
 1 file changed, 33 insertions(+), 91 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md b/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md
index df26c6ac..d1e2c22d 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/00_Base_code.md
@@ -1,8 +1,6 @@
-## General structure
+## 일반적인 구조
 
-In the previous chapter you've created a Vulkan project with all of the proper
-configuration and tested it with the sample code. In this chapter we're starting
-from scratch with the following code:
+이전 챕터에서 Vulkan 프로젝트를 만들고 필요한 설정들을 하고 샘플 코드를 통해 테스트 해 봤습니다. 이 챕터에서는 아래 코드로부터 처음부터 시작해 보겠습니다:
 
 ```c++
 #include <vulkan/vulkan.h>
@@ -47,73 +45,32 @@ int main() {
 }
 ```
 
-We first include the Vulkan header from the LunarG SDK, which provides the
-functions, structures and enumerations. The `stdexcept` and `iostream` headers
-are included for reporting and propagating errors. The `cstdlib`
-header provides the `EXIT_SUCCESS` and `EXIT_FAILURE` macros.
-
-The program itself is wrapped into a class where we'll store the Vulkan objects
-as private class members and add functions to initiate each of them, which will
-be called from the `initVulkan` function. Once everything has been prepared, we
-enter the main loop to start rendering frames. We'll fill in the `mainLoop`
-function to include a loop that iterates until the window is closed in a moment.
-Once the window is closed and `mainLoop` returns, we'll make sure to deallocate
-the resources we've used in the `cleanup` function.
-
-If any kind of fatal error occurs during execution then we'll throw a
-`std::runtime_error` exception with a descriptive message, which will propagate
-back to the `main` function and be printed to the command prompt. To handle 
-a variety of standard exception types as well, we catch the more general `std::exception`. One example of an error that we will deal with soon is finding 
-out that a certain required extension is not supported.
-
-Roughly every chapter that follows after this one will add one new function that
-will be called from `initVulkan` and one or more new Vulkan objects to the
-private class members that need to be freed at the end in `cleanup`.
-
-## Resource management
-
-Just like each chunk of memory allocated with `malloc` requires a call to
-`free`, every Vulkan object that we create needs to be explicitly destroyed when
-we no longer need it. In C++ it is possible to perform automatic resource 
-management using [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) 
-or smart pointers provided in the `<memory>` header. However, I've chosen to be
-explicit about allocation and deallocation of Vulkan objects in this tutorial.
-After all, Vulkan's niche is to be explicit about every operation to avoid
-mistakes, so it's good to be explicit about the lifetime of objects to learn how
-the API works.
-
-After following this tutorial, you could implement automatic resource management
-by writing C++ classes that acquire Vulkan objects in their constructor and
-release them in their destructor, or by providing a custom deleter to either
-`std::unique_ptr` or `std::shared_ptr`, depending on your ownership requirements. 
-RAII is the recommended model for larger Vulkan programs, but
-for learning purposes it's always good to know what's going on behind the
-scenes.
-
-Vulkan objects are either created directly with functions like `vkCreateXXX`, or
-allocated through another object with functions like `vkAllocateXXX`. After
-making sure that an object is no longer used anywhere, you need to destroy it
-with the counterparts `vkDestroyXXX` and `vkFreeXXX`. The parameters for these
-functions generally vary for different types of objects, but there is one
-parameter that they all share: `pAllocator`. This is an optional parameter that
-allows you to specify callbacks for a custom memory allocator. We will ignore
-this parameter in the tutorial and always pass `nullptr` as argument.
-
-## Integrating GLFW
-
-Vulkan works perfectly fine without creating a window if you want to use it for
-off-screen rendering, but it's a lot more exciting to actually show something!
-First replace the `#include <vulkan/vulkan.h>` line with
+먼저 LunarG SDK의 Vulkan 헤더를 include합니다. 이 헤더는 함수, 구조체와 열거자들을 제공해 줍니다. `stdexcept`와 `iostream` 헤더는 오류를 보고하고 전파하기 위해 include하였습니다. `cstdlib`은 `EXIT_SUCCESS`와 `EXIT_FAILURE` 매크로를 제공합니다.
+
+프로그램은 클래스로 래핑되어 있는데 Vulkan 객체들을 프라이빗 클래스 멤버로 저장할 것이며, 그 각각을 초기화하는 함수를 추가할 것입니다. 초기화 함수는 `initVulkan` 함수 안에서 호출할 것입니다. 모든 것들이 준비가 되고 나면 메인 루프로 들어가 프레임을 렌더링하기 시작합니다. `mainLoop` 함수 본문을 작성해서 루프를 추가하고 윈도우가 닫히기 전까지 반복하도록 할 것입니다. 윈도우가 닫히고 `mainLoop`가 반환되면, 사용한 리소스들을 `cleanup` 함수를 통해 해제(deallocate)할 것입니다.
+
+실행 도중 치명적인 오류가 발생하면 `std::runtime_error` 예외를 메지시와 함께 throw할 것인데, 이는 `main`함수로 전파되어 명령 창에 출력될 것입니다. 여러 가지 표준 예외 타입들을 다루기 위해 좀 더 일반적인 `std::exception`을 catch하도록 했습니다. 곧 마주하게 될 오류의 한 예는 필요한 특정 확장이 지원되지 않는 경우가 있습니다.
+
+다음 챕터부터는 대부분 `initVulkan` 함수 안에서 호출할 하나의 새로운 함수를 추가하고 하나 이상의 Vulkan 객체를 프라이빗 클래스 멤버로 추가할 것입니다. 해당 객체는 마지막에 `cleanup`을 통해 해제되어야 합니다.
+
+## 리소스 관리
+
+`malloc`을 통해 할당된 메모리는 `free`되어야 하듯이, 모든 우리가 만든 Vulkan 객체는 더 이상 필요하지 않은 시점에서는 명시적으로 소멸되어야 합니다. C++에서는 [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)를 통해 자동으로 리소스를 관리하거나 `<memory>` 헤더를 통해 제공되는 스마트 포인터를 사용할 수 있습니다. 하지만 이 튜토리얼에서는 Vulkan 객체의 할당과 해제를 직접 하는 방식을 선택했습니다. 결국 Vulkan의 특별한 점은 모든 실수를 피하기 위해 모든 작업들이 명시되어야 한다는 점이고, API의 동작 방식을 배우기 위해 객체의 생애주기도 명시적으로 나타내는 것이 좋다고 생각했습니다.
+
+이 튜토리얼을 따라한 뒤에, 여러분은 Vulkan 객체를 생성자에서 획득하고 소멸자에서 해제하는 C++ 클래스를 만들어 자동적으로 리소스 관리를 하도록 할 수 있습니다. 또는 `std::unique_ptr`나 `std::shared_ptr`에 사용자 정의 deleter를 명시하여 사용할 수도 있습니다. 큰 규모의 Vulkan 프로그램에는 RAII 모델의 사용을 추천하지만, 학습 목적으로는 어떤 일이 발생하는지 알고 있는것이 더 좋습니다.
+
+Vulkan 객체는 `vkCreateXXX`같은 함수를 사용해 직접 만들어지거나 `vkAllocateXXX`와 같은 함수로 다른 객체를 통해 할당될 수 있습니다. 객체가 더 이상 사용되지 않는 게 확실하면, 이와 대응되는 `vkDestroyXXX`와 `vkFreeXXX`를 사용해 소멸시켜야 합니다. 이 함수들의 매개변수는 객체의 타입에 따라 다른데, 모든 함수들이 공유하는 매개변수가 하나 있습니다. `pAllocator`입니다. 이는 사용자 정의 메모리 할당자를 위한 콜백을 명시할 수 있도록 하는 선택적인 매개변수입니다. 튜토리얼에서 이 매개변수는 무시할 것이고, 항상 `nullptr`을 인자로 넘길 것입니다.
+
+## GLFW 통합하기
+
+오프스크린 렌더링을 하려는 목적이면 윈도우 없이도 Vulkan은 완벽하게 동작하지만, 실제로 무언가를 보여주는 것이 훨씬 재미있겠죠! 먼저 `#include <vulkan/vulkan.h>`를 아래 코드로 대체하십시오.
 
 ```c++
 #define GLFW_INCLUDE_VULKAN
 #include <GLFW/glfw3.h>
 ```
 
-That way GLFW will include its own definitions and automatically load the Vulkan
-header with it. Add a `initWindow` function and add a call to it from the `run`
-function before the other calls. We'll use that function to initialize GLFW and
-create a window.
+이렇게 하면 GLFW는 자신에게 필요한 definition들과 Vulkan 헤더를 자동으로 include할 것입니다. `initWindow` 함수를 추가하고 `run`함수에 이 함수를 호출하는 라인을 다른 함수 호출에 앞서 삽입하세요. 이 함수를 사용해 GLFW를 초기화하고 윈도우를 생성할 것입니다.
 
 ```c++
 void run() {
@@ -129,49 +86,40 @@ private:
     }
 ```
 
-The very first call in `initWindow` should be `glfwInit()`, which initializes
-the GLFW library. Because GLFW was originally designed to create an OpenGL
-context, we need to tell it to not create an OpenGL context with a subsequent
-call:
+`initWindow`에서 가장 먼저 호출하는 것은 `glfwInit()`이어야 합니다. 이 함수는 GLFW 라이브러리를 초기화합니다. GLFW는 원래 OpenGL 컨텍스트를 생성하게 되어있기 때문에, 이어지는 코드를 통해 OpenGL 컨텍스트를 생성하지 않도록 알려주어야 합니다:
 
 ```c++
 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
 ```
 
-Because handling resized windows takes special care that we'll look into later,
-disable it for now with another window hint call:
+나중에 살펴볼 것이지만 윈도우 크기가 변하면 몇 가지 특수한 처리를 해 주어야 하기 때문에 지금은 윈도우 힌트를 추가적으로 호출하여 그 기능을 꺼 둡니다:
 
 ```c++
 glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
 ```
 
-All that's left now is creating the actual window. Add a `GLFWwindow* window;`
-private class member to store a reference to it and initialize the window with:
+이제 남은 것은 실제 윈도우를 만드는 것입니다. `GLFWwindow* window;` 프라이빗 클래스 멤버를 추가하여 윈도우의 참조를 저장하도록 하고 윈도우를 아래와 같이 초기화합니다:
 
 ```c++
 window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
 ```
 
-The first three parameters specify the width, height and title of the window.
-The fourth parameter allows you to optionally specify a monitor to open the
-window on and the last parameter is only relevant to OpenGL.
+앞의 세 매개변수는 윈도우의 가로, 세로, 타이틀을 명시합니다. 네 번째 매개변수를 통해 윈도우를 열 모니터를 명시할 수 있고, 다섯 번째 매개변수는 OpenGL을 사용할 때만 필요합니다.
 
-It's a good idea to use constants instead of hardcoded width and height numbers
-because we'll be referring to these values a couple of times in the future. I've
-added the following lines above the `HelloTriangleApplication` class definition:
+가로와 세로 크기를 하드코딩하는 대신 상수를 사용하는 것이 좋겠네요. 나중에 몇 번 해당 값을 참조하는 일이 있을 겁니다. 아래 코드를 `HelloTriangleApplication` 클래스 정의 앞쪽에 추가하였습니다:
 
 ```c++
 const uint32_t WIDTH = 800;
 const uint32_t HEIGHT = 600;
 ```
 
-and replaced the window creation call with
+그리고 윈도우 생성 호출을 아래와 같이 바꾸었습니다:
 
 ```c++
 window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
 ```
 
-You should now have a `initWindow` function that looks like this:
+이제 `initWindow` 함수는 아래와 같은 상태입니다:
 
 ```c++
 void initWindow() {
@@ -184,8 +132,7 @@ void initWindow() {
 }
 ```
 
-To keep the application running until either an error occurs or the window is
-closed, we need to add an event loop to the `mainLoop` function as follows:
+오류가 발생하거나 윈도우가 닫힐 때까지 응용 프로그램이 계속 실행되게 하려면 `mainLoop` 함수에 이벤트 루프를 아래와 같이 추가해야 합니다:
 
 ```c++
 void mainLoop() {
@@ -195,12 +142,9 @@ void mainLoop() {
 }
 ```
 
-This code should be fairly self-explanatory. It loops and checks for events like
-pressing the X button until the window has been closed by the user. This is also
-the loop where we'll later call a function to render a single frame.
+보기만 해도 이해가 되실 겁니다. 반복문을 돌면서 사용자가 윈도우를 닫기 위해 X 버튼을 눌렀는지와 같은 이벤트를 체크합니다. 이 루프가 나중에 프레임을 렌더링하기 위한 함수들을 호출하는 부분이 될겁니다.
 
-Once the window is closed, we need to clean up resources by destroying it and
-terminating GLFW itself. This will be our first `cleanup` code:
+윈도우가 닫히면, 리소스를 소멸시켜 정리하고 GLFW도 종료해야 합니다. 아래는 `cleanup` 함수의 첫 단계 코드입니다.
 
 ```c++
 void cleanup() {
@@ -210,8 +154,6 @@ void cleanup() {
 }
 ```
 
-When you run the program now you should see a window titled `Vulkan` show up
-until the application is terminated by closing the window. Now that we have the
-skeleton for the Vulkan application, let's [create the first Vulkan object](!en/Drawing_a_triangle/Setup/Instance)!
+프로그램을 실행하면 `Vulkan`이라는 이름의 윈도우가 보이고 윈도우를 닫기 전까지 응용 프로그램의 실행 상태가 유지될 것입니다. Vulkan 응용 프로그램의 뼈대를 만들었으니, 이제 [첫 번째 Vulkan 객체를 만들어 봅시다](!kr/Drawing_a_triangle/Setup/Instance)!
 
 [C++ code](/code/00_base_code.cpp)

From 95c8eadfb1e1d7a995342fb5c798625741ea2690 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 19 Sep 2023 20:51:39 +0900
Subject: [PATCH 04/47] kr translate 03-00-01 instance

---
 .../00_Setup/01_Instance.md                   | 118 +++++-------------
 1 file changed, 34 insertions(+), 84 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
index d9744a1c..297e2804 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
@@ -1,12 +1,8 @@
-## Creating an instance
+## 인스턴스 생성
 
-The very first thing you need to do is initialize the Vulkan library by creating
-an *instance*. The instance is the connection between your application and the
-Vulkan library and creating it involves specifying some details about your
-application to the driver.
+가장 먼저 할 일은 *인스턴스*를 생성하여 Vulkan 라이브러리를 초기화하는 것입니다. 인스턴스는 여러분의 응용 프로그램과 Vulkan 라이브러리를 연결해 주는 매개체이고 인스턴스를 생성하는 것은 여러분의 응용 프로그램에 대해 드라이버에게 상세한 사항들을 알려주는 것과 같습니다.
 
-Start by adding a `createInstance` function and invoking it in the
-`initVulkan` function.
+먼저 `createInstance` 함수를 `initVulkan` 함수 내에서 호출합시다.
 
 ```c++
 void initVulkan() {
@@ -14,18 +10,14 @@ void initVulkan() {
 }
 ```
 
-Additionally add a data member to hold the handle to the instance:
+또한 인스턴스의 핸들을 저장하기 위한 멤버를 추가합니다:
 
 ```c++
 private:
 VkInstance instance;
 ```
 
-Now, to create an instance we'll first have to fill in a struct with some
-information about our application. This data is technically optional, but it may
-provide some useful information to the driver in order to optimize our specific
-application (e.g. because it uses a well-known graphics engine with
-certain special behavior). This struct is called `VkApplicationInfo`:
+이제, 인스턴스를 만드려면 우리 응용 프로그램에 대한 몇 가지 정보를 구조체에 채워넣어야 합니다. 엄밀히 말하면 선택적인 과정이지만, 이를 통해 드라이버에게 유용한 정보를 전달하고 특정 응용프로그램을 최적화 할 수 있습니다 (e.g. because it uses a well-known graphics engine with certain special behavior). 이 구조체는 `VkApplicationInfo`입니다:
 
 ```c++
 void createInstance() {
@@ -39,17 +31,9 @@ void createInstance() {
 }
 ```
 
-As mentioned before, many structs in Vulkan require you to explicitly specify
-the type in the `sType` member. This is also one of the many structs with a
-`pNext` member that can point to extension information in the future. We're
-using value initialization here to leave it as `nullptr`.
+앞서 언급한 것처럼 Vulkan의 많은 구조체는 `sType` 멤버를 통해 명시적으로 타입을 명시하도록 합니다. 또한 이 구조체는 `pNext` 멤버를 가지는 많은 구조체 중 하나인데, 나중을 위한 확장을 가리킬 수 있도록 합니다. 여기서는 초기화를 통해 그냥 `nullptr`로 두었습니다.
 
-A lot of information in Vulkan is passed through structs instead of function
-parameters and we'll have to fill in one more struct to provide sufficient
-information for creating an instance. This next struct is not optional and tells
-the Vulkan driver which global extensions and validation layers we want to use.
-Global here means that they apply to the entire program and not a specific
-device, which will become clear in the next few chapters.
+Vulkan에서는 많은 정보가 함수의 매개변수 대신 구조체로 전달되고 인스턴스 생성을 위해서는 하나 이상의 구조체를 전달해야 하는 경우가 있습니다. 지금 보시는 두 번째 구조체는 반드시 전달되어야 하는데 Vulkan 드라이버에게 어떤 전역(global) 확장과 검증 레이어를 사용하려고 하는지 알려주는 것입니다. 여기서 전역의 의미는 특정 장치가 아닌 전체 프로그램에 적용된다는 의미인데, 다음 몇 챕터를 보게 되면 확실히 이해될 것입니다.
 
 ```c++
 VkInstanceCreateInfo createInfo{};
@@ -57,11 +41,7 @@ createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
 createInfo.pApplicationInfo = &appInfo;
 ```
 
-The first two parameters are straightforward. The next two layers specify the
-desired global extensions. As mentioned in the overview chapter, Vulkan is a
-platform agnostic API, which means that you need an extension to interface with
-the window system. GLFW has a handy built-in function that returns the
-extension(s) it needs to do that which we can pass to the struct:
+이 두 개는 직관적으로 이해가 될 겁니다. 뒤에 나올 두 개는 의도하는 전역 확장을 명시합니다. overview 챕터에서 이야기한 것처럼 Vulkan은 플랫폼 독립적인 API기 때문에 우리는 윈도우 시스템과 상호작용하기위한 확장이 필요합니다. GLFW에는 여기에 필요한 확장을 반환해주는 편리한 함수가 있어서 이 것을 구조체에 전달해 줍니다:
 
 ```c++
 uint32_t glfwExtensionCount = 0;
@@ -73,33 +53,25 @@ createInfo.enabledExtensionCount = glfwExtensionCount;
 createInfo.ppEnabledExtensionNames = glfwExtensions;
 ```
 
-The last two members of the struct determine the global validation layers to
-enable. We'll talk about these more in-depth in the next chapter, so just leave
-these empty for now.
+구조체의 마지막 두 멤버가 활성화할 전역 검증 레이어를 결정합니다. 다음 챕터에서 자세히 설명할 것이니 지금은 그냥 비워 둡시다.
 
 ```c++
 createInfo.enabledLayerCount = 0;
 ```
 
-We've now specified everything Vulkan needs to create an instance and we can
-finally issue the `vkCreateInstance` call:
+이제 Vulkan이 인스턴스를 생성하기 위한 모든 것들을 명시했으니 `vkCreateInstance`를 호출할 수 있습니다:
 
 ```c++
 VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
 ```
 
-As you'll see, the general pattern that object creation function parameters in
-Vulkan follow is:
+앞으로도 보시겠지만 Vulkan의 객체 생성 함수의 파라메터들의 일반적인 패턴은 아래와 같습니다:
 
-* Pointer to struct with creation info
-* Pointer to custom allocator callbacks, always `nullptr` in this tutorial
-* Pointer to the variable that stores the handle to the new object
+- 생성 정보에 관한 구조체를 가리키는 포인터
+- 생성자에 대한 사용자 정의 콜백을 가리키는 포인터. 튜토리얼에서는 항상 `nullptr`
+- 새로운 객체의 핸들을 저장하기 위한 변수의 포인터
 
-If everything went well then the handle to the instance was stored in the
-`VkInstance` class member. Nearly all Vulkan functions return a value of type
-`VkResult` that is either `VK_SUCCESS` or an error code. To check if the
-instance was created successfully, we don't need to store the result and can
-just use a check for the success value instead:
+문제 없이 잘 동작했다면 `VkInstance` 클래스 멤버에 인스턴스의 핸들이 저장될 것입니다. 거의 대부분 Vulkan의 함수는 `VkResult` 타입의 값을 반환하는데 그 값은 `VK_SUCCESS`이거나 오류 코드입니다. 인스턴스가 성공적으로 생성되었다면, 결과값을 저장할 필요는 없고 그냥 성공 여부만 체크하면 됩니다.
 
 ```c++
 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
@@ -107,18 +79,16 @@ if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
 }
 ```
 
-Now run the program to make sure that the instance is created successfully.
+이제 프로그램을 실행해 인스턴스가 잘 생성되었는지 확인하십시오.
 
-## Encountered VK_ERROR_INCOMPATIBLE_DRIVER:
-If using MacOS with the latest MoltenVK sdk, you may get `VK_ERROR_INCOMPATIBLE_DRIVER`
-returned from `vkCreateInstance`. According to the [Getting Start Notes](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html). Beginning with the 1.3.216 Vulkan SDK, the `VK_KHR_PORTABILITY_subset`
-extension is mandatory.
+## VK_ERROR_INCOMPATIBLE_DRIVER 오류에 맞닥뜨린다면:
 
-To get over this error, first add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` bit
-to `VkInstanceCreateInfo` struct's flags, then add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME`
-to instance enabled extension list.
+최신 MultenVK SDK를 MacOS에서 사용중이하면 `vkCreateInstance`로부터 `VK_ERROR_INCOMPATIBLE_DRIVER`가 반환될 수 있습니다. [Getting Start Notes](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html)를 살펴보십시오. 1.3.216 Vulkan SDK부터는 `VK_KHR_PORTABILITY_subset` 확장이 필수적입니다.
+
+오류를 해결하기 위해서는 먼저 `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` 비트를 `VkInstanceCreateInfo` 구조체 플래그에 추가하고, `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME`를 인스턴스의 확장 리스트에 추가하십시오.
+
+코드는 보통 아래와 같이 될 겁니다:
 
-Typically the code could be like this:
 ```c++
 ...
 
@@ -140,45 +110,35 @@ if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
 }
 ```
 
-## Checking for extension support
+## 확장 지원 체크하기
 
-If you look at the `vkCreateInstance` documentation then you'll see that one of
-the possible error codes is `VK_ERROR_EXTENSION_NOT_PRESENT`. We could simply
+`vkCreateInstance`문서를 보면 `VK_ERROR_EXTENSION_NOT_PRESENT` 오류 코드가 반환될 수 있다는 것을 알 수 있습니다. We could simply
 specify the extensions we require and terminate if that error code comes back.
 That makes sense for essential extensions like the window system interface, but
 what if we want to check for optional functionality?
 
-To retrieve a list of supported extensions before creating an instance, there's
-the `vkEnumerateInstanceExtensionProperties` function. It takes a pointer to a
-variable that stores the number of extensions and an array of
-`VkExtensionProperties` to store details of the extensions. It also takes an
-optional first parameter that allows us to filter extensions by a specific
-validation layer, which we'll ignore for now.
+인스턴스를 생성하기 전에 지원하는 확장들의 리스트를 얻고 싶으면 `vkEnumerateInstanceExtensionProperties`를 사용하면 됩니다. 확장의 개수를 저장할 변수의 포인터와 확장의 상세 사항을 저장할 `VkExtensionProperties` 배열을 매개변수로 받습니다. 선택적으로 첫 번째 파라메터로 특정한 검증 레이어로 필터링하도록 할 수 있는데, 지금은 무시해도 됩니다.
 
-To allocate an array to hold the extension details we first need to know how
-many there are. You can request just the number of extensions by leaving the
-latter parameter empty:
+확장의 세부 사항을 저장할 배열을 할당하려면 먼저 몇 개나 있는지 알아야 합니다. 그냥 마지막 매개변수를 빈 채로 먼저 확장의 개수만 요청합니다:
 
 ```c++
 uint32_t extensionCount = 0;
 vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
 ```
 
-Now allocate an array to hold the extension details (`include <vector>`):
+(`include <vector>`를 추가하고) 확장의 세부 사항들을 저장할 배열을 생성합니다.
 
 ```c++
 std::vector<VkExtensionProperties> extensions(extensionCount);
 ```
 
-Finally we can query the extension details:
+마지막으로 확장의 세부 사항들을 요청합니다:
 
 ```c++
 vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
 ```
 
-Each `VkExtensionProperties` struct contains the name and version of an
-extension. We can list them with a simple for loop (`\t` is a tab for
-indentation):
+`VkExtensionProperties`구조체 각각은 확장의 이름과 버전을 담고 있습니다. 간단한 for문을 사용해 목록을 순회할 수 있습니다. (`\t`는 들여쓰기를 위한 탭 문자입니다)
 
 ```c++
 std::cout << "available extensions:\n";
@@ -188,16 +148,11 @@ for (const auto& extension : extensions) {
 }
 ```
 
-You can add this code to the `createInstance` function if you'd like to provide
-some details about the Vulkan support. As a challenge, try to create a function
-that checks if all of the extensions returned by
-`glfwGetRequiredInstanceExtensions` are included in the supported extensions
-list.
+이 코드를 `createInstance`에 추가하면 Vulkan 지원에 대한 세부 사항을 알 수 있습니다. 문제를 하나 내 드리면, `glfwGetRequiredInstanceExtensions`가 반환한 확장들이 모두 지원되는지를 확인해 보세요.
 
-## Cleaning up
+## 정리하기
 
-The `VkInstance` should be only destroyed right before the program exits. It can
-be destroyed in `cleanup` with the `vkDestroyInstance` function:
+`VkInstance`는 프로그램 종료 전에 제거되어야 합니다. `cleanup`에서 `vkDestroyInstance` 함수를 통해 제거할 수 있습니다.
 
 ```c++
 void cleanup() {
@@ -209,13 +164,8 @@ void cleanup() {
 }
 ```
 
-The parameters for the `vkDestroyInstance` function are straightforward. As
-mentioned in the previous chapter, the allocation and deallocation functions
-in Vulkan have an optional allocator callback that we'll ignore by passing
-`nullptr` to it. All of the other Vulkan resources that we'll create in the
-following chapters should be cleaned up before the instance is destroyed.
+`vkDestroyInstance` 함수의 매개변수는 명확히 이해가 되실겁니다. 이전 장에서 이야기한 것처럼, Vulkan의 할당과 해제 함수에 추가적으로 콜백을 전달할 수 있는데 `nullptr`로 둔 상태입니다. 앞으로 모든 챕터에서 우리가 생성할 Vulkan 리소스들은 인스턴스가 해제되기 전에 정리되어야 합니다.
 
-Before continuing with the more complex steps after instance creation, it's time
-to evaluate our debugging options by checking out [validation layers](!en/Drawing_a_triangle/Setup/Validation_layers).
+인스턴스 생성 이후 보다 복잡한 과정을 살펴보기 전에, [검증 레이어](!kr/Drawing_a_triangle/Setup/Validation_layers)를 통해 디버깅 옵션을 살펴보겠습니다.
 
 [C++ code](/code/01_instance_creation.cpp)

From 603a24c7095a933395128a8c1f96b8e8685657b7 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 27 Sep 2023 19:05:08 +0900
Subject: [PATCH 05/47] kr translate 03-00-02 validation layerr, backup

---
 .../00_Setup/02_Validation_layers.md          | 179 ++++++------------
 1 file changed, 60 insertions(+), 119 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
index 569a0178..4f70ef35 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
@@ -1,27 +1,16 @@
-## What are validation layers?
-
-The Vulkan API is designed around the idea of minimal driver overhead and one of
-the manifestations of that goal is that there is very limited error checking in
-the API by default. Even mistakes as simple as setting enumerations to incorrect
-values or passing null pointers to required parameters are generally not
-explicitly handled and will simply result in crashes or undefined behavior.
-Because Vulkan requires you to be very explicit about everything you're doing,
-it's easy to make many small mistakes like using a new GPU feature and
-forgetting to request it at logical device creation time.
-
-However, that doesn't mean that these checks can't be added to the API. Vulkan
-introduces an elegant system for this known as *validation layers*. Validation
-layers are optional components that hook into Vulkan function calls to apply
-additional operations. Common operations in validation layers are:
-
-* Checking the values of parameters against the specification to detect misuse
-* Tracking creation and destruction of objects to find resource leaks
-* Checking thread safety by tracking the threads that calls originate from
-* Logging every call and its parameters to the standard output
-* Tracing Vulkan calls for profiling and replaying
-
-Here's an example of what the implementation of a function in a diagnostics
-validation layer could look like:
+## 검증 레이어란?
+
+Vulkan API는 최소한의 드라이버 오버헤드를 기반으로 설계되었고 그를 위해서 기본적으로 API에서는 최소한의 오류 체크 기능만을 포함하고 있습니다. 열거자를 잘못된 값으로 설정한다거나 필요한 매개변수에 널 포인터를 넘긴다거나 하는 간단한 오류도 일반적으로는 명시적으로 처리되지 않아서 크래시나 정의되지 않은 동작을 일으키게 됩니다. Vulkan은 여러분이 하는 작업이 매우 명시적이기를 요구하기 떄문에, 새로운 GPU의 기능을 사용한다거나, 논리적 장치 생성 때 필요한 것들을 깜빡한다거나 하는 작은 실수를 저지르기 쉽습니다.
+
+하지만, 그렇다고 그러한 체크 기능이 API에 포함될 수 없는것은 아닙니다. Vulkan은 *검증 레이어*라고 알려진 우아한 해결책을 만들었습니다. 검증 레이어는 선택적인 구성요소로 Vulkan 함수 호출에 후킹(hook)할 수 있는 추가적인 연산입니다. 일반적으로 검증 레이어에서 수행하는 연산은:
+
+- 명세를 기반으로 매개변수의 값을 체크하여 잘못된 사용을 탐지
+- 리소스의 누수를 탐지하기 위해 객체의 생성과 소멸을 추적
+- 호출 지점으로부터 쓰레드를 추적하여 쓰레드 세이프티(safety)를 확인
+- 표준 출력에 모든 호출과 매개변수를 로깅(logging)
+- 프로파일링(profiling)과 리플레이(replaying)를 위한 Vulkan 호출 추적
+
+진단(diagnostics) 검증 레이어의 함수 구현 예시는 아래와 같습니다:
 
 ```c++
 VkResult vkCreateInstance(
@@ -38,42 +27,20 @@ VkResult vkCreateInstance(
 }
 ```
 
-These validation layers can be freely stacked to include all the debugging
-functionality that you're interested in. You can simply enable validation layers
-for debug builds and completely disable them for release builds, which gives you
-the best of both worlds!
-
-Vulkan does not come with any validation layers built-in, but the LunarG Vulkan
-SDK provides a nice set of layers that check for common errors. They're also
-completely [open source](https://github.com/KhronosGroup/Vulkan-ValidationLayers),
-so you can check which kind of mistakes they check for and contribute. Using the
-validation layers is the best way to avoid your application breaking on
-different drivers by accidentally relying on undefined behavior.
-
-Validation layers can only be used if they have been installed onto the system.
-For example, the LunarG validation layers are only available on PCs with the
-Vulkan SDK installed.
-
-There were formerly two different types of validation layers in Vulkan: instance
-and device specific. The idea was that instance layers would only check
-calls related to global Vulkan objects like instances, and device specific layers
-would only check calls related to a specific GPU. Device specific layers have now been
-deprecated, which means that instance validation layers apply to all Vulkan
-calls. The specification document still recommends that you enable validation
-layers at device level as well for compatibility, which is required by some
-implementations. We'll simply specify the same layers as the instance at logical
+이러한 검증 레이어들은 여러분이 의도하는 모든 디버깅 기능들을 얼마든지 누적(stack)할 수 있도록 되어 있습니다. 디버깅 빌드에서 검증 레이어를 활성화 하고 릴리즈 빌드에서는 비활성화 하면 양 쪽 상황에서 모두 문제가 없을 것입니다.
+
+Vulkan은 내장 검증 레이어를 제공하지는 않지만 LunarG Vulkan SDK에서는 흔히 발생하는 오류 검출을 위한 레이어들을 제공하고 있습니다. 완전히 [오픈 소스](https://github.com/KhronosGroup/Vulkan-ValidationLayers)이니, 어떤 종류의 실수를 탐지해 주는지 알 수 있고, 여러분이 기여도 할 수 있습니다. 검증 레이어를 사용하는 것이 여러분의 응용 프로그램이 다른 드라이버에서 정의되지 않은 동작으로 인해 올바로 동작하지 않는 것을 방지하는 가장 좋은 방법입니다.
+
+검증 레이어는 시스템에 설치되어 있어야 사용 가능합니다. 예를 들어 LunarG 검증 레이어는 Vulkan SDK가 설치된 PC에서만 사용 가능합니다.
+
+Vulkan에는 두 가지 종류의 검증 레이어가 존재하는데 인스턴스와 장치(device) 레이어입니다. 인스턴스 레이어는 인스턴스와 같은 전역 Vulkan 객체들만을 체크하고 장치 레이어는 특정 GPU에 관련된 호출만을 체크합니다. 현재 장치 레이어는 더 이상 사용되지 않으며(deprecated), 인스턴스 검증 레이어가 모든 Vulkan 호출에 적용됩니다. 명세 문서에는 여전히 호환성을 위해 장치 수준에서 검증 레이어를 활성화 할 것을 권장하고 있습니다. We'll simply specify the same layers as the instance at logical
 device level, which we'll see [later on](!en/Drawing_a_triangle/Setup/Logical_device_and_queues).
 
-## Using validation layers
+## 검증 레이어 사용하기
 
-In this section we'll see how to enable the standard diagnostics layers provided
-by the Vulkan SDK. Just like extensions, validation layers need to be enabled by
-specifying their name. All of the useful standard validation is bundled into a layer included in the SDK that is known as `VK_LAYER_KHRONOS_validation`.
+이 장에서 우리는 Vulkan SDK에서 제공하는 표준 진단 레이어를 활성화 하는 법을 알아볼 것입니다. 확장과 마찬가지로, 검증 레이어는 그 이름을 명시하여 활성화해야 합니다. 모든 유용한 표준 검증들은 SDK에 포함되어 있는 `VK_LAYER_KHRONOS_validation`이라는 레이어에 포함되어 있습니다.
 
-Let's first add two configuration variables to the program to specify the layers
-to enable and whether to enable them or not. I've chosen to base that value on
-whether the program is being compiled in debug mode or not. The `NDEBUG` macro
-is part of the C++ standard and means "not debug".
+먼저 프로그램에 두 개의 구성 변수를 추가하여 사용할 레이어를 명시하고 그들을 활성화 할것인지 말지를 알려 줍시오. 저는 디버깅 모드인지 아닌지에 따라 값을 설정하도록 했습니다. `NDEBUG` 매크로는 C++ 표준에 포함된 매크로로 "디버그가 아님"을 의미합니다.
 
 ```c++
 const uint32_t WIDTH = 800;
@@ -90,11 +57,7 @@ const std::vector<const char*> validationLayers = {
 #endif
 ```
 
-We'll add a new function `checkValidationLayerSupport` that checks if all of
-the requested layers are available. First list all of the available layers
-using the `vkEnumerateInstanceLayerProperties` function. Its usage is identical
-to that of `vkEnumerateInstanceExtensionProperties` which was discussed in the
-instance creation chapter.
+`checkValidationLayerSupport` 함수를 추가하여 요청한 레이어들이 모두 사용 가능한지를 체크합니다. 먼저 가용한 모든 레이어 목록을 `vkEnumerateInstanceLayerProperties`를 사용해 만듭니다. 사용법은 인스턴스 생성 챕터에서 봤던 `vkEnumerateInstanceExtensionProperties`와 동일합니다.
 
 ```c++
 bool checkValidationLayerSupport() {
@@ -108,8 +71,7 @@ bool checkValidationLayerSupport() {
 }
 ```
 
-Next, check if all of the layers in `validationLayers` exist in the
-`availableLayers` list. You may need to include `<cstring>` for `strcmp`.
+다음으로, `validationLayers` 내의 모든 레이어가 `availableLayers` 내에 존재하는지를 체크합니다. `strcmp` 사용을 위해 `<cstring>`의 include가 필요합니다.
 
 ```c++
 for (const char* layerName : validationLayers) {
@@ -130,7 +92,7 @@ for (const char* layerName : validationLayers) {
 return true;
 ```
 
-We can now use this function in `createInstance`:
+이제 이 함수를 `createInstance`에서 사용할 수 있습니다.
 
 ```c++
 void createInstance() {
@@ -142,11 +104,9 @@ void createInstance() {
 }
 ```
 
-Now run the program in debug mode and ensure that the error does not occur. If
-it does, then have a look at the FAQ.
+이제 프로그램을 디버그 모드에서 실행해 오류가 발생하지 않는지 확인하세요. 오류가 발생하면, FAQ를 살펴보세요.
 
-Finally, modify the `VkInstanceCreateInfo` struct instantiation to include the
-validation layer names if they are enabled:
+마지막으로, `VkInstanceCreateInfo` 구조체 초기화를 구정해서 사용이 가능한 경우 검증 레이어의 이름을 포함하도록 하세요.
 
 ```c++
 if (enableValidationLayers) {
@@ -157,18 +117,15 @@ if (enableValidationLayers) {
 }
 ```
 
-If the check was successful then `vkCreateInstance` should not ever return a
-`VK_ERROR_LAYER_NOT_PRESENT` error, but you should run the program to make sure.
+체크 과정이 성공적이라면 `vkCreateInstance`가 `VK_ERROR_LAYER_NOT_PRESENT` 오류를 반환하지 않을 것이지만, 확인을 위해 실행해 보시기 바랍니다.
 
-## Message callback
+## 메시지 콜백
 
-The validation layers will print debug messages to the standard output by default, but we can also handle them ourselves by providing an explicit callback in our program. This will also allow you to decide which kind of messages you would like to see, because not all are necessarily (fatal) errors. If you don't want to do that right now then you may skip to the last section in this chapter.
+검증 레이어는 기본적으로 디버그 메시지를 표준 출력창에 표시하지만, 프로그램에서 명시적으로 콜밸을 제공하여 우리가 원하는 방식대로 처리할 수도 있습니다. 이렇게 하면 어떤 종류의 메시지를 보기 원하는지 선택할 수 있는데 모든 메시지들이 (치명적인) 오류에 관한 것은 아니기 때문입니다. 지금은 그냥 그대로 두고 싶다면 이 챕터의 마지막으로 넘어가셔도 됩니다.
 
-To set up a callback in the program to handle messages and the associated details, we have to set up a debug messenger with a callback using the `VK_EXT_debug_utils` extension.
+메시지 처리를 위한 콜백을 설정하고 관련한 설정들을 조정하고 싶다면, `VK_EXT_debug_utils` 확장을 사용해 디버그 메신저(messenger) 콜백을 설정해야 합니다.
 
-We'll first create a `getRequiredExtensions` function that will return the
-required list of extensions based on whether validation layers are enabled or
-not:
+먼저 검증 레이어가 활성화 되었는지 여부에 따라 필요한 확장 목록을 반환하는 `getRequiredExtensions` 함수를 만들겠습니다.
 
 ```c++
 std::vector<const char*> getRequiredExtensions() {
@@ -186,12 +143,9 @@ std::vector<const char*> getRequiredExtensions() {
 }
 ```
 
-The extensions specified by GLFW are always required, but the debug messenger
-extension is conditionally added. Note that I've used the
-`VK_EXT_DEBUG_UTILS_EXTENSION_NAME` macro here which is equal to the literal
-string "VK_EXT_debug_utils". Using this macro lets you avoid typos.
+GLFW가 명시한 확장은 항상 필요하지만, 디버그 메시지를 위한 확장은 조건에 따라 추가하였습니다. 여기서 `VK_EXT_DEBUG_UTILS_EXTENSION_NAME` 매크로를 사용하였는데 이는 문자열 리터럴 "VK_EXT_debug_utils"와 동일하는 것에 유의하세요. 이러한 매크로를 사용하면 오타로 인한 오류를 방지할 수 있습니다.
 
-We can now use this function in `createInstance`:
+이제 이 함수를 `createInstance`에서 사용합니다:
 
 ```c++
 auto extensions = getRequiredExtensions();
@@ -199,15 +153,9 @@ createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
 createInfo.ppEnabledExtensionNames = extensions.data();
 ```
 
-Run the program to make sure you don't receive a
-`VK_ERROR_EXTENSION_NOT_PRESENT` error. We don't really need to check for the
-existence of this extension, because it should be implied by the availability of
-the validation layers.
+`VK_ERROR_EXTENSION_NOT_PRESENT` 오류가 발생하지 않는지 프로그램을 실행해 확인하세요. 확장들이 존재하는지는 확인할 필요 없습니다. 왜냐하면 검증 레이어가 가용하다면 당연히 해당 확장들도 사용 가능하기 떄문입니다.
 
-Now let's see what a debug callback function looks like. Add a new static member
-function called `debugCallback` with the `PFN_vkDebugUtilsMessengerCallbackEXT`
-prototype. The `VKAPI_ATTR` and `VKAPI_CALL` ensure that the function has the
-right signature for Vulkan to call it.
+이제 디버그 콜백의 생김새를 한 번 봅시다. `PFN_vkDebugUtilsMessengerCallbackEXT` 프로토타입을 갖는 `debugCallback`이라는 새 스태틱 멤버 함수를 추가합시다. `VKAPI_ATTR`과 `VKAPI_CALL`를 통해 Vulkan에서 호출하기 위해 적절한 시그니처를 갖고 있는지 확인합니다.
 
 ```c++
 static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
@@ -222,14 +170,14 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
 }
 ```
 
-The first parameter specifies the severity of the message, which is one of the following flags:
+첫 번쨰 파라메터는 메시지의 심각도를 나타내고, 아래 플래그 중 하나입니다.
 
-* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Diagnostic message
-* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Informational message like the creation of a resource
-* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Message about behavior that is not necessarily an error, but very likely a bug in your application
-* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Message about behavior that is invalid and may cause crashes
+- `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: 진단 메시지
+- `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: 리소스의 생성과 같은 정보 메시지
+- `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: 오류는 아니지만 응용 프로그램의 버그일 수 있는 메시지
+- `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: 프로그램이 중단될 수 있는 허용되지 않는 동작에 대한 메시지
 
-The values of this enumeration are set up in such a way that you can use a comparison operation to check if a message is equal or worse compared to some level of severity, for example:
+이러한 열거자들의 값은 비교 연산자를 통해 메시지가 특정 심각도와 같거나 더 심각한지를 설정할 수 있습니다. 예를 들어 아래와 같습니다:
 
 ```c++
 if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
@@ -237,37 +185,29 @@ if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
 }
 ```
 
-The `messageType` parameter can have the following values:
+`messageType` 매개변수는 아래와 같은 값을 가질 수 있습니다:
 
-* `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT`: Some event has happened that is unrelated to the specification or performance
-* `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT`: Something has happened that violates the specification or indicates a possible mistake
-* `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT`: Potential non-optimal use of Vulkan
+- `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT`: 명세 또는 성능과 관계없는 이벤트가 발생함
+- `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT`: 명세를 위반했거나 실수일 수 있는 경우
+- `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT`: Vulkan에서 효율적이지 않을 수 있음
 
-The `pCallbackData` parameter refers to a `VkDebugUtilsMessengerCallbackDataEXT` struct containing the details of the message itself, with the most important members being:
+`pCallbackData` 매개변수는 메세지의 상세 사항을 포함하여 아래와 같은 아주 중요한 매개변수를 갖는 `VkDebugUtilsMessengerCallbackDataEXT` 구조체를 참조합니다:
 
-* `pMessage`: The debug message as a null-terminated string
-* `pObjects`: Array of Vulkan object handles related to the message
-* `objectCount`: Number of objects in array
+- `pMessage`: 널문자로 끝나는 문자열(string) 디버그 메시지
+- `pObjects`: 메시지와 관련 있는 Vulkan 객체 핸들의 배열
+- `objectCount`: 배열 내 객체의 숫자
 
-Finally, the `pUserData` parameter contains a pointer that was specified during the setup of the callback and allows you to pass your own data to it.
+마지막으로, `pUserData` 매개변수는 콜백 설정 시 명시한 포인터를 포함하며 사용자가 원하는 데이터를 전달할 수 있도록 합니다.
 
-The callback returns a boolean that indicates if the Vulkan call that triggered
-the validation layer message should be aborted. If the callback returns true,
-then the call is aborted with the `VK_ERROR_VALIDATION_FAILED_EXT` error. This
-is normally only used to test the validation layers themselves, so you should
-always return `VK_FALSE`.
+콜백은 불리언(boolean)을 반환하는데 해당 검증 레이어를 촉발한(triggered) Vulkan 호출이 중단(aborted)되어야 하는지 여부를 의미합니다. true가 반환되었다면 `VK_ERROR_VALIDATION_FAILED_EXT` 오류와 함께 호출이 중단됩니다. 보통 true 반환은 검증레이어 자체를 테스트 할 때에만 사용되므로 항상 `VK_FALSE`를 반환하는 것이 맞습니다.
 
-All that remains now is telling Vulkan about the callback function. Perhaps
-somewhat surprisingly, even the debug callback in Vulkan is managed with a
-handle that needs to be explicitly created and destroyed. Such a callback is part of a *debug messenger* and you can have as many of them as you want. Add a class member for
-this handle right under `instance`:
+이제 남은 것은 Vulkan에 콜백 함수를 알려주는 것 뿐입니다. 놀랍게도 Vulkan에서는 디버그 콜백조차 명시적으로 생성되고 소멸되는 핸들을 가지고 관리해야 합니다. 이러한 콜백은 *debug messenger*의 일부분으로 원하는 개수만큼 가질 수 있습니다. `instance` 바로 아래에 이 핸들을 위한 멤버를 추가 합시다:
 
 ```c++
 VkDebugUtilsMessengerEXT debugMessenger;
 ```
 
-Now add a function `setupDebugMessenger` to be called from `initVulkan` right
-after `createInstance`:
+이제 `initVulkan`에서 호출할 `setupDebugMessenger` 함수를 `createInstance` 바로 다음에 추가합니다:
 
 ```c++
 void initVulkan() {
@@ -281,7 +221,7 @@ void setupDebugMessenger() {
 }
 ```
 
-We'll need to fill in a structure with details about the messenger and its callback:
+메신저에 대한 세부 사항과 콜백에 대한 내용을 구조체에 채웁니다:
 
 ```c++
 VkDebugUtilsMessengerCreateInfoEXT createInfo{};
@@ -292,8 +232,9 @@ createInfo.pfnUserCallback = debugCallback;
 createInfo.pUserData = nullptr; // Optional
 ```
 
-The `messageSeverity` field allows you to specify all the types of severities you would like your callback to be called for. I've specified all types except for `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT` here to receive notifications about possible problems while leaving out verbose general debug info.
+`messageSeverity` 필드는 여러분의 콜백이 호출될 심각도 타입을 모두 명시할 수 있도록 되어 있습니다. 저는 `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`를 제외하고 모두 명시해서, 잠재적 문제는 모두 받고, 일반적인 디버깅 정보는 포함하지 않도록 했습니다.
 
+`messageType`은 유사하게
 Similarly the `messageType` field lets you filter which types of messages your callback is notified about. I've simply enabled all types here. You can always disable some if they're not useful to you.
 
 Finally, the `pfnUserCallback` field specifies the pointer to the callback function. You can optionally pass a pointer to the `pUserData` field which will be passed along to the callback function via the `pUserData` parameter. You could use this to pass a pointer to the `HelloTriangleApplication` class, for example.
@@ -332,7 +273,7 @@ The second to last parameter is again the optional allocator callback that we
 set to `nullptr`, other than that the parameters are fairly straightforward.
 Since the debug messenger is specific to our Vulkan instance and its layers, it
 needs to be explicitly specified as first argument. You will also see this
-pattern with other *child* objects later on.
+pattern with other _child_ objects later on.
 
 The `VkDebugUtilsMessengerEXT` object also needs to be cleaned up with a call to
 `vkDestroyDebugUtilsMessengerEXT`. Similarly to `vkCreateDebugUtilsMessengerEXT`
@@ -434,7 +375,7 @@ Now let's intentionally make a mistake to see the validation layers in action. T
 
 ![](/images/validation_layer_test.png)
 
->If you don't see any messages then [check your installation](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation).
+> If you don't see any messages then [check your installation](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation).
 
 If you want to see which call triggered a message, you can add a breakpoint to the message callback and look at the stack trace.
 

From a725ed06d48093bf253f3a29924f3f51f5ffe64f Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 12 Oct 2023 20:26:27 +0900
Subject: [PATCH 06/47] kr translate 03-00-02 validation layer, finish

---
 .../00_Setup/02_Validation_layers.md          | 68 ++++++-------------
 1 file changed, 22 insertions(+), 46 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
index 4f70ef35..02c5391c 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
@@ -234,19 +234,13 @@ createInfo.pUserData = nullptr; // Optional
 
 `messageSeverity` 필드는 여러분의 콜백이 호출될 심각도 타입을 모두 명시할 수 있도록 되어 있습니다. 저는 `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`를 제외하고 모두 명시해서, 잠재적 문제는 모두 받고, 일반적인 디버깅 정보는 포함하지 않도록 했습니다.
 
-`messageType`은 유사하게
-Similarly the `messageType` field lets you filter which types of messages your callback is notified about. I've simply enabled all types here. You can always disable some if they're not useful to you.
+`messageType` 필드는 콜백이 알림을 받을 메시지 타입을 필터링 할 수 있도록 합니다. 여기서는 모든 타입을 활성화 하였습니다. 필요하지 않으면 몇 개를 비활성화 해도 됩니다.
 
-Finally, the `pfnUserCallback` field specifies the pointer to the callback function. You can optionally pass a pointer to the `pUserData` field which will be passed along to the callback function via the `pUserData` parameter. You could use this to pass a pointer to the `HelloTriangleApplication` class, for example.
+마지막으로 `pfnUserCallback`필드는 콜백 함수에 대한 포인터를 명시합니다. 선택적으로 `pUserData` 필드에 포인터를 전달하여 콜백 함수와 함께 전달될 파라메터를 명시할 수 있습니다. 예를 들어 `HelloTriangleApplication` 클래스의 포인터를 전달하는 데 사용할 수 있습니다.
 
-Note that there are many more ways to configure validation layer messages and debug callbacks, but this is a good setup to get started with for this tutorial. See the [extension specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils) for more info about the possibilities.
+검증 레이어 메시지와 디버그 콜백을 구성하는 다양한 방법이 있지만, 이 튜토리얼을 위한 시작으로는 이 정도가 좋습니다. [확장 명세](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils)에서 보다 다양한 정보를 볼 수 있습니다.
 
-This struct should be passed to the `vkCreateDebugUtilsMessengerEXT` function to
-create the `VkDebugUtilsMessengerEXT` object. Unfortunately, because this
-function is an extension function, it is not automatically loaded. We have to
-look up its address ourselves using `vkGetInstanceProcAddr`. We're going to
-create our own proxy function that handles this in the background. I've added it
-right above the `HelloTriangleApplication` class definition.
+`VkDebugUtilsMessengerEXT` 객체를 생성하기 위해 `vkCreateDebugUtilsMessengerEXT` 함수에 이 구조체를 전달해야 합니다. 안타깝게도 이 함수는 확장 함수이기 때문에 자동적으로 로드되지 않습니다. `vkGetInstanceProcAddr`를 사용해 주소값을 얻어야 합니다. 이를 처리하기 위한 방안으로 프록시 함수를 만들 것입니다. 저는 `HelloTriangleApplication` 클래스 정의 바로 다음에 추가 하였습니다.
 
 ```c++
 VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
@@ -259,9 +253,7 @@ VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMes
 }
 ```
 
-The `vkGetInstanceProcAddr` function will return `nullptr` if the function
-couldn't be loaded. We can now call this function to create the extension
-object if it's available:
+함수가 로드될 수 없으면 `vkGetInstanceProcAddr`함수는 `nullptr`을 반환합니다. 이제 이 함수를 호출하여 가능한 경우에 확장 객체를 생성할 수 있습니다.
 
 ```c++
 if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
@@ -269,17 +261,11 @@ if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger
 }
 ```
 
-The second to last parameter is again the optional allocator callback that we
-set to `nullptr`, other than that the parameters are fairly straightforward.
-Since the debug messenger is specific to our Vulkan instance and its layers, it
-needs to be explicitly specified as first argument. You will also see this
-pattern with other _child_ objects later on.
+두 번째부터 마지막까지의 매개변수는 선택적인 할당 콜백으로 `nullptr`로 설정할 것이고, 이를 제외하면 나머지는 직관적입니다. 디버그 메신저는 우리의 Vulkan 인스턴스와 레이어에 한정되어 있으므로 첫 인자에서 명시해야만 합니다. 이러한 패턴과 다른 _child_ 객체들은 나중에 다시 보게 될 것입니다.
 
-The `VkDebugUtilsMessengerEXT` object also needs to be cleaned up with a call to
-`vkDestroyDebugUtilsMessengerEXT`. Similarly to `vkCreateDebugUtilsMessengerEXT`
-the function needs to be explicitly loaded.
+`VkDebugUtilsMessengerEXT` 객체는 `vkDestroyDebugUtilsMessengerEXT`호출로 정리되어야 합니다. `vkCreateDebugUtilsMessengerEXT`와 마찬가지로 이 함수는 명시적으로 로드되어야 합니다.
 
-Create another proxy function right below `CreateDebugUtilsMessengerEXT`:
+`CreateDebugUtilsMessengerEXT` 바로 밑에 또 다른 프록시 함수를 만듭시다.
 
 ```c++
 void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
@@ -290,8 +276,7 @@ void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT
 }
 ```
 
-Make sure that this function is either a static class function or a function
-outside the class. We can then call it in the `cleanup` function:
+이 함수는 정적(static) 멤버 함수 또는 클래스 밖의 함수로 만드는 것을 잊지 마십시오. 이 함수를 `cleanup` 함수에서 호출합니다.
 
 ```c++
 void cleanup() {
@@ -307,11 +292,11 @@ void cleanup() {
 }
 ```
 
-## Debugging instance creation and destruction
+## 인스턴스의 생성과 소멸 디버깅
 
-Although we've now added debugging with validation layers to the program we're not covering everything quite yet. The `vkCreateDebugUtilsMessengerEXT` call requires a valid instance to have been created and `vkDestroyDebugUtilsMessengerEXT` must be called before the instance is destroyed. This currently leaves us unable to debug any issues in the `vkCreateInstance` and `vkDestroyInstance` calls.
+검증 레이어와 디버깅을 프로그램에 추가하였지만 아직 모든 것이 완료된 것은 아닙니다. `vkCreateDebugUtilsMessengerEXT` 호출을 위해서는 그 전에 유효한 인스턴스가 생성되어야 하고, `vkDestroyDebugUtilsMessengerEXT`은 인스턴스가 소멸되기 전에 호출되어야 합니다. 따라서 `vkCreateInstance`와 `vkDestroyInstance` 호출에 있어서 발생하는 문제는 디버깅이 불가능합니다.
 
-However, if you closely read the [extension documentation](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/appendices/VK_EXT_debug_utils.adoc#examples), you'll see that there is a way to create a separate debug utils messenger specifically for those two function calls. It requires you to simply pass a pointer to a `VkDebugUtilsMessengerCreateInfoEXT` struct in the `pNext` extension field of `VkInstanceCreateInfo`. First extract population of the messenger create info into a separate function:
+[확장 문서](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/appendices/VK_EXT_debug_utils.adoc#examples)를 자세히 읽어보면 위 두 함수 호출에 대해 별도의 디버깅 메신서를 생성하는 방법이 있다는 것을 알 수 있으실 겁니다. 이를 위해서는 `VkDebugUtilsMessengerCreateInfoEXT` 구조체에 대한 포인터를 `VkInstanceCreateInfo`의 `pNext` 확장 필드에 넘겨주기만 하면 됩니다. 먼저 메신저 생성 정보만 추출해 벌도의 함수로 만듭니다.
 
 ```c++
 void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
@@ -336,7 +321,7 @@ void setupDebugMessenger() {
 }
 ```
 
-We can now re-use this in the `createInstance` function:
+이제 이를 `createInstance` 함수에서 재사용 할 수 있습니다:
 
 ```c++
 void createInstance() {
@@ -367,33 +352,24 @@ void createInstance() {
 }
 ```
 
-The `debugCreateInfo` variable is placed outside the if statement to ensure that it is not destroyed before the `vkCreateInstance` call. By creating an additional debug messenger this way it will automatically be used during `vkCreateInstance` and `vkDestroyInstance` and cleaned up after that.
+`debugCreateInfo` 변수를 if문 밖에 작성하여 `vkCreateInstance` 호출 전에 소멸되지 않도록 하였습니다. 추가적인 디버깅 메신저를 이런 식으로 만들면 `vkCreateInstance` 호출 동안에, 그리고 `vkDestroyInstance` 호출과 이후 정리 과정에 자동적으로 사용됩니다.
 
-## Testing
+## 테스팅
 
-Now let's intentionally make a mistake to see the validation layers in action. Temporarily remove the call to `DestroyDebugUtilsMessengerEXT` in the `cleanup` function and run your program. Once it exits you should see something like this:
+이제 일부러 실수를 만들어서 검증 레이어가 동작하는지 봅시다. `DestroyDebugUtilsMessengerEXT`를 `cleanup`에서 잠시 제거하고 프로그램을 실행해 봅시다. 종료되면 이런 화면을 보시게 될겁니다.
 
 ![](/images/validation_layer_test.png)
 
-> If you don't see any messages then [check your installation](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation).
+> 아무런 메시지가 보이지 않으면 [설치를 확인해 보세요](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation).
 
-If you want to see which call triggered a message, you can add a breakpoint to the message callback and look at the stack trace.
+어떤 호출이 메시지를 트리거하였는지 보려면 메시지 콜백에 중단점을 걸고 호출 스택을 확인하면 됩니다.
 
-## Configuration
+## 구성(Configuration)
 
-There are a lot more settings for the behavior of validation layers than just
-the flags specified in the `VkDebugUtilsMessengerCreateInfoEXT` struct. Browse
-to the Vulkan SDK and go to the `Config` directory. There you will find a
-`vk_layer_settings.txt` file that explains how to configure the layers.
+`VkDebugUtilsMessengerCreateInfoEXT` 구조체에 명시한 플래그 이외에도 더 많은 검증 레이어의 동작에 관한 세팅을 할 수 있습니다. Vulkan SDK로 가서 `Config` 디렉터리를 보세요. `vk_layer_settings.txt` 파일을 찾을 수 있을텐데 레이어를 설정하기 위한 설명이 적혀 있습니다.
 
-To configure the layer settings for your own application, copy the file to the
-`Debug` and `Release` directories of your project and follow the instructions to
-set the desired behavior. However, for the remainder of this tutorial I'll
-assume that you're using the default settings.
+여러분의 응용 프로그램을 위한 레이어 설정을 하기 위해서는 그 파일을 프로젝트의 `Debug` 와 `Release` 디렉터리에 복사하고 원하는 동작을 하도록 안내를 따라 하면 됩니다. 하지만, 이 튜토리얼에서는 기본 세팅을 사용하는 것으로 가정할 것입니다.
 
-Throughout this tutorial I'll be making a couple of intentional mistakes to show
-you how helpful the validation layers are with catching them and to teach you
-how important it is to know exactly what you're doing with Vulkan. Now it's time
-to look at [Vulkan devices in the system](!en/Drawing_a_triangle/Setup/Physical_devices_and_queue_families).
+앞으로 튜토리얼에서 검증 레이어의 유용함을 보여드리기 위해서, 그리고 Vulkan이 정확히 어떤 일을 하는지를 아는 것이 얼마나 중요한지 알려드리기 위해 일부러 실수를 하는 경우들이 있을 겁니다. 이제 [시스템의 Vulkan 장치](!en/Drawing_a_triangle/Setup/Physical_devices_and_queue_families)에 대해 알아봅시다.
 
 [C++ code](/code/02_validation_layers.cpp)

From 5a8e52902c84cd6e3be59505f61e4b55aa9f4b98 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 12 Oct 2023 21:30:18 +0900
Subject: [PATCH 07/47] kr translate 03-00-03 physical device

---
 .../03_Physical_devices_and_queue_families.md | 123 +++++-------------
 1 file changed, 34 insertions(+), 89 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
index 5761b9bc..d1de72e5 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
@@ -1,12 +1,8 @@
-## Selecting a physical device
+## 물리적 장치 선택
 
-After initializing the Vulkan library through a VkInstance we need to look for
-and select a graphics card in the system that supports the features we need. In
-fact we can select any number of graphics cards and use them simultaneously, but
-in this tutorial we'll stick to the first graphics card that suits our needs.
+VkInstance를 통해 Vulkan 라이브러리를 초기화 한 이후에는 우리가 필요로 하는 기능을 지원하는 시스템의 그래픽 카드를 찾고 선택해야 합니다. 사실 여러 대의 그래픽 카드를 선택하고 동시에 사용할 수도 있습니다. 하지만 이 튜토리얼에서는 우리의 요구에 맞는 첫 번째 그래픽 카드만을 사용하도록 할 것입니다.
 
-We'll add a function `pickPhysicalDevice` and add a call to it in the
-`initVulkan` function.
+`pickPhysicalDevice` 함수를 추가하고 `initVulkan` 함수에서 이 함수를 호출하도록 합시다.
 
 ```c++
 void initVulkan() {
@@ -20,24 +16,20 @@ void pickPhysicalDevice() {
 }
 ```
 
-The graphics card that we'll end up selecting will be stored in a
-VkPhysicalDevice handle that is added as a new class member. This object will be
-implicitly destroyed when the VkInstance is destroyed, so we won't need to do
-anything new in the `cleanup` function.
+우리가 선택할 그래픽 카드는 새롭게 클래스 멤버로 추가된 VkPhysicalDevice 핸들에 저장됩니다. 이 객체는 VkInstance가 소멸될 때 암시적(implicitly)으로 소멸되므로, `cleanup`에 무언가를 추가할 필요는 없습니다.
 
 ```c++
 VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
 ```
 
-Listing the graphics cards is very similar to listing extensions and starts with
-querying just the number.
+그래픽 카드의 목록을 불러오는 것은 확장의 목록을 불러오는 것과 비슷하며 그 개수를 질의(query)하는 것으로 시작됩니다.
 
 ```c++
 uint32_t deviceCount = 0;
 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
 ```
 
-If there are 0 devices with Vulkan support then there is no point going further.
+Vulkan을 지원하는 장치가 없으면 더 진행할 이유가 없겠죠.
 
 ```c++
 if (deviceCount == 0) {
@@ -45,17 +37,14 @@ if (deviceCount == 0) {
 }
 ```
 
-Otherwise we can now allocate an array to hold all of the VkPhysicalDevice
-handles.
+그렇지 않으면 모든 VkPhysicalDevice 핸들을 저장할 배열을 할당합니다.
 
 ```c++
 std::vector<VkPhysicalDevice> devices(deviceCount);
 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
 ```
 
-Now we need to evaluate each of them and check if they are suitable for the
-operations we want to perform, because not all graphics cards are created equal.
-For that we'll introduce a new function:
+이제 각 장치를 순회하면서 우리가 하고자 하는 작업에 적합한지 확인합니다. 모든 그래픽 카드가 같지는 않기 때문입니다. 이를 위해 아래와 같은 새 함수를 만듭니다:
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -63,8 +52,7 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-And we'll check if any of the physical devices meet the requirements that we'll
-add to that function.
+그리고 어떤 물리적 장치든 요구사항에 맞는는 것이 있는지를 확인합니다.
 
 ```c++
 for (const auto& device : devices) {
@@ -79,36 +67,27 @@ if (physicalDevice == VK_NULL_HANDLE) {
 }
 ```
 
-The next section will introduce the first requirements that we'll check for in
-the `isDeviceSuitable` function. As we'll start using more Vulkan features in
-the later chapters we will also extend this function to include more checks.
+다음 섹션에서는 우리가 `isDeviceSuitable`에서 확인할 첫 번째 요구사항을 소개할 것입니다. 이후 챕터에서 보다 많은 Vulkan 기능을 사용할 것이기 때문에 보다 많은 요구사항을 확인하도록 확장해 나갈 것입니다.
 
-## Base device suitability checks
+## 기본 장치 적합성(suitability) 확인
 
-To evaluate the suitability of a device we can start by querying for some
-details. Basic device properties like the name, type and supported Vulkan
-version can be queried using vkGetPhysicalDeviceProperties.
+장치의 적합성을 확인하기 위해 몇 가지 세부사항을 질의할 것입니다. 장치의 기본적인 속성인 이름, 타입, 지원하는 Vulkan 버전 등을 vkGetPhysicalDeviceProperties를 사용해 질의할 수 있습니다.
 
 ```c++
 VkPhysicalDeviceProperties deviceProperties;
 vkGetPhysicalDeviceProperties(device, &deviceProperties);
 ```
 
-The support for optional features like texture compression, 64 bit floats and
-multi viewport rendering (useful for VR) can be queried using
-vkGetPhysicalDeviceFeatures:
+텍스처 압축, 64비트 float, 다중 뷰포트 렌더링(VR에서 유용합니다) 등과 같은 추가적인 기능을 지원하는지 여부는 vkGetPhysicalDeviceFeatures를 사용해 질의할 수 있습니다.
 
 ```c++
 VkPhysicalDeviceFeatures deviceFeatures;
 vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
 ```
 
-There are more details that can be queried from devices that we'll discuss later
-concerning device memory and queue families (see the next section).
+장치 메모리라던가, 큐 패밀리(queue family)와 같은 더 세부적인 사항에 대한 질의도 가능하며, 이에 대해서는 이후에 논의할 것입니다(다음 섹션 참고).
 
-As an example, let's say we consider our application only usable for dedicated
-graphics cards that support geometry shaders. Then the `isDeviceSuitable`
-function would look like this:
+예를 들어, 우리 응용 프로그램이 지오메트리(geometry) 셰이더를 지원하는 장치에서만 사용할 수 있도록 하고 싶습니다. 그러면 `isDeviceSuitable` 함수는 아래와 같이 구현할 수 있습니다.
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -122,11 +101,7 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-Instead of just checking if a device is suitable or not and going with the first
-one, you could also give each device a score and pick the highest one. That way
-you could favor a dedicated graphics card by giving it a higher score, but fall
-back to an integrated GPU if that's the only available one. You could implement
-something like that as follows:
+장치가 적합한지 아닌지만 체크해서 첫 번째 장치를 선택하는 대신, 각 장치에 점수를 부여하고 가장 높은 점수의 장치를 선택하게 할 수도 있습니다. 이렇게 하면 적합한 장치에 더 많은 점수를 부여할 수 있지만 그러한 경우 적합한 장치가 내장 그래픽(integrated GPU) 카드일 경우 그 장치가 선택될 수도 있습니다. 이러한 방식은 다음과 같이 구현할 수 있습니다.
 
 ```c++
 #include <map>
@@ -174,12 +149,9 @@ int rateDeviceSuitability(VkPhysicalDevice device) {
 }
 ```
 
-You don't need to implement all that for this tutorial, but it's to give you an
-idea of how you could design your device selection process. Of course you can
-also just display the names of the choices and allow the user to select.
+이 튜토리얼에서 이런 모든 기능을 구현할 필요는 없지만 여러분의 장치 선택 과정을 어떻게 설계할 수 있을지에 대한 아이디어는 얻게 되셨을겁니다. 물론 후보 장치들의 이름을 보여주고 유저가 선택하도록 할 수도 있습니다.
 
-Because we're just starting out, Vulkan support is the only thing we need and
-therefore we'll settle for just any GPU:
+지금은 시작하는 단계이므로 Vulkan 지원 여부만 있으면 되고 그러니 그냥 아무 GPU나 선택하도록 하겠습니다.
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -187,23 +159,15 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-In the next section we'll discuss the first real required feature to check for.
+다음 섹션에서는 첫 번째 실제 필요로 하는 기능을 체크해 보겠습니다.
 
-## Queue families
+## 큐 패밀리(Queue families)
 
-It has been briefly touched upon before that almost every operation in Vulkan,
-anything from drawing to uploading textures, requires commands to be submitted
-to a queue. There are different types of queues that originate from different
-*queue families* and each family of queues allows only a subset of commands. For
-example, there could be a queue family that only allows processing of compute
-commands or one that only allows memory transfer related commands.
+그리기부터 텍스처 업로드까지 거의 대부분의 Vulkan 명령 실행 전에, 명령(command)들이 큐에 제출되어야만 합니다. 다양한 종류의 *큐 패밀리*로부터 도출된 다양한 종류의 큐가 있으며, 각 큐 패밀리는 처리할 수 있는 명령이 제한되어 있습니다. 예를 들어 계산(compute) 명령만을 처리할 수 있는 큐 패밀리가 있고, 메모리 전송 관련 명령만을 처리할 수 있는 큐 패밀리도 있습니다.
 
-We need to check which queue families are supported by the device and which one
-of these supports the commands that we want to use. For that purpose we'll add a
-new function `findQueueFamilies` that looks for all the queue families we need.
+장치가 어떤 큐 패밀리를 지원하는지와 이들 중 어떤 것이 우리가 사용하고자 하는 명령을 지원하는지를 체크해야만 합니다. 이를 위해 `findQueueFamilies` 함수를 추가하고 우리가 필요로하는 큐 패밀리들을 찾도록 해 봅시다.
 
-Right now we are only going to look for a queue that supports graphics commands,
-so the function could look like this:
+지금은 그래픽스 명령을 지원하는 큐만 확인할 것이므로 함수는 아래와 같습니다:
 
 ```c++
 uint32_t findQueueFamilies(VkPhysicalDevice device) {
@@ -211,8 +175,7 @@ uint32_t findQueueFamilies(VkPhysicalDevice device) {
 }
 ```
 
-However, in one of the next chapters we're already going to look for yet another
-queue, so it's better to prepare for that and bundle the indices into a struct:
+하지만, 이후 챕터부터 또다른 큐가 필요하기 때문에 이를 대비해 인덱스를 구조체로 만드는 것이 낫습니다:
 
 ```c++
 struct QueueFamilyIndices {
@@ -226,16 +189,9 @@ QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
 }
 ```
 
-But what if a queue family is not available? We could throw an exception in
-`findQueueFamilies`, but this function is not really the right place to make
-decisions about device suitability. For example, we may *prefer* devices with a
-dedicated transfer queue family, but not require it. Therefore we need some way
-of indicating whether a particular queue family was found.
+큐 패밀리를 지원하지 않으면 어떻게 될까요? `findQueueFamilies`에서 예외를 throw할 수도 있지만, 이 함수는 장치 적합성을 확인하기 위한 목적으로는 적합하지 않습니다. 예를 들어 전송(transfer) 큐 패밀리가 있는 장치를 *선호*하긴 하지만 필수 요구사항은 아닐수도 있습니다. 따라서 특정한 큐 패밀리가 있는지 알려주는 방법이 필요합니다.
 
-It's not really possible to use a magic value to indicate the nonexistence of a
-queue family, since any value of `uint32_t` could in theory be a valid queue
-family index including `0`. Luckily C++17 introduced a data structure to
-distinguish between the case of a value existing or not:
+큐 패밀리가 존재하지 않는것에 대한 마법같은 인덱스를 사용하는 방법은 없습니다. `0`을 포함해서 모든 `uint32_t` 값이 사실상 유요한 큐 패밀리의 인덱스일 수 있기 때문입니다. 다행히 C++17에서는 값이 존재하는지 아닌지를 구분할 수 있는 자료 구조를 지원합니다.
 
 ```c++
 #include <optional>
@@ -251,9 +207,7 @@ graphicsFamily = 0;
 std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true
 ```
 
-`std::optional` is a wrapper that contains no value until you assign something
-to it. At any point you can query if it contains a value or not by calling its
-`has_value()` member function. That means that we can change the logic to:
+`std::optional`은 무언가 값을 할당하기 전에는 값이 없는 상태를 나타낼 수 있는 래퍼(wrapper)입니다. 어느 시점에 여러분은 그 안에 값이 있는지 없는지를 `has_value()` 멤버 함수를 통해 확인할 수 있습니다. 따라서 로직을 아래와 같이 바꿀 수 있습니다:
 
 ```c++
 #include <optional>
@@ -271,7 +225,7 @@ QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
 }
 ```
 
-We can now begin to actually implement `findQueueFamilies`:
+이제 실제로 `findQueueFamilies`를 구현할 수 있습니다:
 
 ```c++
 QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
@@ -283,8 +237,7 @@ QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
 }
 ```
 
-The process of retrieving the list of queue families is exactly what you expect
-and uses `vkGetPhysicalDeviceQueueFamilyProperties`:
+큐 패밀리의 목록을 가져오는 과정은 예상하실 수 있듯이 `vkGetPhysicalDeviceQueueFamilyProperties`를 사용하는 것입니다.
 
 ```c++
 uint32_t queueFamilyCount = 0;
@@ -294,10 +247,7 @@ std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
 vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
 ```
 
-The VkQueueFamilyProperties struct contains some details about the queue family,
-including the type of operations that are supported and the number of queues
-that can be created based on that family. We need to find at least one queue
-family that supports `VK_QUEUE_GRAPHICS_BIT`.
+VkQueueFamilyProperties 구조체는 지원하는 연산의 종류와 해당 패밀리로부터 생성될 수 있는 큐의 개수 등의 큐 패밀리 세부 사항을 포함하고 있습니다. 우리는 `VK_QUEUE_GRAPHICS_BIT`을 지원하는 최소한 하나의 큐 패밀리를 찾아야만 합니다.
 
 ```c++
 int i = 0;
@@ -310,9 +260,7 @@ for (const auto& queueFamily : queueFamilies) {
 }
 ```
 
-Now that we have this fancy queue family lookup function, we can use it as a
-check in the `isDeviceSuitable` function to ensure that the device can process
-the commands we want to use:
+이제 멋진 큐 패밀리 룩업(lookup) 함수가 있으니 `isDeviceSuitable` 함수에서 이를 사용해 장치가 우리가 사용하고자 하는 명령을 처리할 수 있는지 확인합니다:
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -322,8 +270,7 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-To make this a little bit more convenient, we'll also add a generic check to the
-struct itself:
+좀 더 편리하게 사용하기 위해, 구조체 안에도 체크 기능을 추가합니다:
 
 ```c++
 struct QueueFamilyIndices {
@@ -343,7 +290,7 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-We can now also use this for an early exit from `findQueueFamilies`:
+`findQueueFamilies` 에서 빠른 종료를 위해서도 사용합니다:
 
 ```c++
 for (const auto& queueFamily : queueFamilies) {
@@ -357,8 +304,6 @@ for (const auto& queueFamily : queueFamilies) {
 }
 ```
 
-Great, that's all we need for now to find the right physical device! The next
-step is to [create a logical device](!en/Drawing_a_triangle/Setup/Logical_device_and_queues)
-to interface with it.
+좋습니다. 우선은 적절한 물리적 장치를 찾는 것은 이것으로 끝입니다! 다음 단계는 [논리적 장치](!en/Drawing_a_triangle/Setup/Logical_device_and_queues)와의 인터페이스를 만드는 것입니다.
 
 [C++ code](/code/03_physical_device_selection.cpp)

From 806aa5924902cd1b0036799f32adb1e156effa83 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sat, 14 Oct 2023 12:23:28 +0900
Subject: [PATCH 08/47] kr translate 03-00-04 logical device

---
 .../00_Setup/04_Logical_device_and_queues.md  | 32 ++++++-------------
 1 file changed, 10 insertions(+), 22 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
index f2677d08..a6474ef1 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
@@ -1,19 +1,14 @@
-## Introduction
+## 개요
 
-After selecting a physical device to use we need to set up a *logical device* to
-interface with it. The logical device creation process is similar to the
-instance creation process and describes the features we want to use. We also
-need to specify which queues to create now that we've queried which queue
-families are available. You can even create multiple logical devices from the
-same physical device if you have varying requirements.
+사용할 물리적 장치를 선택한 뒤에는 이와 상호작용할 *논리적 장치*를 설정해야 합니다. 논리적 장치의 생성 과정은 인스턴스 생성 과정과 비슷하고 우리가 사용할 기능들을 기술해야 합니다. 또한 이제는 어떤 큐 패밀리가 가용한지를 알아냈기 때문에 어떤 큐를 생성할지도 명시해야 합니다. 만일 요구사항이 다양하다면, 하나의 물리적 장치로부터 여러 개의 논리적 장치를 만들 수도 있습니다.
 
-Start by adding a new class member to store the logical device handle in.
+논리적 장치에 대한 핸들을 저장한 멤버를 클래스에 생성하는 것부터 시작합니다.
 
 ```c++
 VkDevice device;
 ```
 
-Next, add a `createLogicalDevice` function that is called from `initVulkan`.
+다음으로 `initVulkan`에서 호출할 `createLogicalDevice` 함수를 추가합니다.
 
 ```c++
 void initVulkan() {
@@ -28,12 +23,9 @@ void createLogicalDevice() {
 }
 ```
 
-## Specifying the queues to be created
+## 생성할 큐 명시하기
 
-The creation of a logical device involves specifying a bunch of details in
-structs again, of which the first one will be `VkDeviceQueueCreateInfo`. This
-structure describes the number of queues we want for a single queue family.
-Right now we're only interested in a queue with graphics capabilities.
+논리적 장치를 생성하는 것은 이전처럼 여러 세부사항을 구조체에 명시하는 과정을 포함하며, 그 첫번째가 `VkDeviceQueueCreateInfo`입니다. 이 구조체는 하나의 큐 패밀리에 대한 큐의 개수를 명시합니다. 현재 우리는 그래픽스 기능 관련 큐에만 관심이 있습니다.
 
 ```c++
 QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
@@ -44,21 +36,17 @@ queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
 queueCreateInfo.queueCount = 1;
 ```
 
-The currently available drivers will only allow you to create a small number of
-queues for each queue family and you don't really need more than one. That's
-because you can create all of the command buffers on multiple threads and then
-submit them all at once on the main thread with a single low-overhead call.
+현재의 드라이버들은 큐 패밀리 하나당 적은 수의 큐만을 생성할 수 있도록 제한되어 있고, 여러분도 하나 이상 필요하지는 않을겁니다. 왜냐하면 여러 쓰레드(thread)에 필요한 커맨드 버퍼들을 모두 생성해 두고 메인 쓰레드에서 적은 오버헤드의 호출로 이들을 한꺼번에 제출(submit)할 수 있기 떄문입니다.
 
-Vulkan lets you assign priorities to queues to influence the scheduling of
-command buffer execution using floating point numbers between `0.0` and `1.0`.
-This is required even if there is only a single queue:
+Vulkan에서는 커맨드 버퍼의 실행 스케줄에 영향을 주는 큐의 우선순위를 `0.0`과 `1.0` 사이의 부동소수점 수로 명시할 수 있게 되어 있습니다. 큐가 하나밖에 없더라도 이를 명시해 주어야만 합니다:
 
 ```c++
 float queuePriority = 1.0f;
 queueCreateInfo.pQueuePriorities = &queuePriority;
 ```
 
-## Specifying used device features
+## 사용할 장치 기능 명시하기
+
 
 The next information to specify is the set of device features that we'll be
 using. These are the features that we queried support for with

From ad2d13fcf09b24b23d5876b36cb790c2988cf71f Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sat, 14 Oct 2023 12:29:17 +0900
Subject: [PATCH 09/47] kr translate 03-00-04 logical device, final

---
 .../00_Setup/04_Logical_device_and_queues.md  | 61 ++++++-------------
 1 file changed, 17 insertions(+), 44 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
index a6474ef1..3a4060ea 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
@@ -47,29 +47,22 @@ queueCreateInfo.pQueuePriorities = &queuePriority;
 
 ## 사용할 장치 기능 명시하기
 
-
-The next information to specify is the set of device features that we'll be
-using. These are the features that we queried support for with
-`vkGetPhysicalDeviceFeatures` in the previous chapter, like geometry shaders.
-Right now we don't need anything special, so we can simply define it and leave
-everything to `VK_FALSE`. We'll come back to this structure once we're about to
-start doing more interesting things with Vulkan.
+다음으로는 우리가 사용할 장치의 기능을 명시해야 합니다. 이는 이전 챕터의 지오메트리 셰이더를 `vkGetPhysicalDeviceFeatures`로 질의했던 것과 비슷합니다. 지금은 특별헌 기능이 필요 없으니 그냥 정의만 해 두고 모든 값을 `VK_FALSE`로 둡시다. 나중에 Vulkan을 사용해 좀 더 흥미로운 것들을 할 때 다시 이 구조체를 사용할 것입니다.
 
 ```c++
 VkPhysicalDeviceFeatures deviceFeatures{};
 ```
 
-## Creating the logical device
+## 논리적 장치 생성하기
 
-With the previous two structures in place, we can start filling in the main
-`VkDeviceCreateInfo` structure.
+이전 두 개의 구조체가 준비되었으니 `VkDeviceCreateInfo` 구조체를 채워 봅시다.
 
 ```c++
 VkDeviceCreateInfo createInfo{};
 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
 ```
 
-First add pointers to the queue creation info and device features structs:
+먼저 큐 생성 정보와 장치 기능 구조체에 대한 포인터를 추가합니다.
 
 ```c++
 createInfo.pQueueCreateInfos = &queueCreateInfo;
@@ -78,17 +71,11 @@ createInfo.queueCreateInfoCount = 1;
 createInfo.pEnabledFeatures = &deviceFeatures;
 ```
 
-The remainder of the information bears a resemblance to the
-`VkInstanceCreateInfo` struct and requires you to specify extensions and
-validation layers. The difference is that these are device specific this time.
+나머지 정보는 `VkInstanceCreateInfo` 구조체와 비슷해서 확장이나 검증 레이어를 명시할 수 있습니다. 차이점은 이것들이 이번에는 장치에 종속적(device specific)이라는 것입니다.
 
-An example of a device specific extension is `VK_KHR_swapchain`, which allows
-you to present rendered images from that device to windows. It is possible that
-there are Vulkan devices in the system that lack this ability, for example
-because they only support compute operations. We will come back to this
-extension in the swap chain chapter.
+장치에 종속적인 확장 중 하나의 예시로는 `VK_KHR_swapchain`가 있는데, 렌더링된 이미지를 장치로부터 윈도우로 전달하는 기능입니다. 시스템의 Vulkan 장치가 이 기능을 지원하지 않을 수 있습니다. 예를 들어 계산 명령만 수행하는 장치일 경우에 그렇습니다. 이 확장에 대한 설명은 나중에 스왑 체인 챕터에서 다시 살펴볼 것입니다.
 
-Previous implementations of Vulkan made a distinction between instance and device specific validation layers, but this is [no longer the case](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). That means that the `enabledLayerCount` and `ppEnabledLayerNames` fields of `VkDeviceCreateInfo` are ignored by up-to-date implementations. However, it is still a good idea to set them anyway to be compatible with older implementations:
+Vulkan의 이전 구현에서는 인스턴스와 장치 종속적인 검증 레이어가 구분되어 있었으나, [지금은 아닙니다](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). 즉, `VkDeviceCreateInfo`의 `enabledLayerCount` 와 `ppEnabledLayerNames` 필드가 최신 구현에서는 무시됩니다. 하지만, 이전 버전과의 호환성을 위해 어쨌든 설정해 주는 것이 좋습니다.
 
 ```c++
 createInfo.enabledExtensionCount = 0;
@@ -101,10 +88,9 @@ if (enableValidationLayers) {
 }
 ```
 
-We won't need any device specific extensions for now.
+지금은 장치 종속적인 확장은 필요하지 않습니다.
 
-That's it, we're now ready to instantiate the logical device with a call to the
-appropriately named `vkCreateDevice` function.
+이제 `vkCreateDevice` 함수를 사용해 논리적 장치를 생성할 준비가 되었습니다.
 
 ```c++
 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
@@ -112,13 +98,9 @@ if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS)
 }
 ```
 
-The parameters are the physical device to interface with, the queue and usage
-info we just specified, the optional allocation callbacks pointer and a pointer
-to a variable to store the logical device handle in. Similarly to the instance
-creation function, this call can return errors based on enabling non-existent
-extensions or specifying the desired usage of unsupported features.
+매개변수들은 상호작용할 물리적 장치, 큐와 방금 명시한 사용 정보, 선택적으로 명시할 수 있는 콜백에 대한 포인터, 마지막으로 논리적 장치를 저장할 핸들에 대한 포인터입니다. 인스턴스 생성 함수와 유사하게 이 호출은 존재하지 않는 확장을 활성화 한다거나, 지원하지 않는 기능을 명시하는 경우 오류를 반환합니다.
 
-The device should be destroyed in `cleanup` with the `vkDestroyDevice` function:
+장치는 `cleanup`에서 `vkDestroyDevice`함수를 통해 소멸되어야 합니다.
 
 ```c++
 void cleanup() {
@@ -127,33 +109,24 @@ void cleanup() {
 }
 ```
 
-Logical devices don't interact directly with instances, which is why it's not
-included as a parameter.
+논리적 장치는 인스턴스와 직접적으로 상호작용하지 않으므로 매개변수에 포함되지 않습니다.
 
-## Retrieving queue handles
+## 큐 핸들 얻기(Retrieving)
 
-The queues are automatically created along with the logical device, but we don't
-have a handle to interface with them yet. First add a class member to store a
-handle to the graphics queue:
+큐는 논리적 장치와 함께 생성되지만 아직 이들과 상호작용하기 위한 핸들은 얻지 못했습니다. 먼저 그래픽스 큐에 대한 핸들을 클래스 멤버에 추가해 줍시다.
 
 ```c++
 VkQueue graphicsQueue;
 ```
 
-Device queues are implicitly cleaned up when the device is destroyed, so we
-don't need to do anything in `cleanup`.
+장치 큐는 장치가 소멸될 때 자동으로 정리되므로 `cleanup`에서 해 주어야 할 일은 따로 없습니다.
 
-We can use the `vkGetDeviceQueue` function to retrieve queue handles for each
-queue family. The parameters are the logical device, queue family, queue index
-and a pointer to the variable to store the queue handle in. Because we're only
-creating a single queue from this family, we'll simply use index `0`.
+`vkGetDeviceQueue`함수를 사용해 각 큐 패밀리에 대한 핸들을 얻어올 수 있습니다. 매개변수는 논리적 장치, 큐 패밀리, 큐 인덱스, 큐 핸들을 저장할 변수의 포인터 입니다. 이 패밀리에서 하나의 큐만 생성하고 있으므로 인덱스는 간단히 `0`으로 설정하면 됩니다.
 
 ```c++
 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
 ```
 
-With the logical device and queue handles we can now actually start using the
-graphics card to do things! In the next few chapters we'll set up the resources
-to present results to the window system.
+논리적 장치와 큐의 핸들이 확보 되었으니 이제 실제로 그래픽 카드를 사용해 무언가를 할 수 있습니다! 다음 몇 개 챕터에서는 윈도우 시스템에 결과를 표시하기 위한 리소스들을 설정해 보겠습니다.
 
 [C++ code](/code/04_logical_device.cpp)

From dfdb3d2d8f1369d8a1200a4d21ccc98012f0d971 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sat, 14 Oct 2023 12:30:46 +0900
Subject: [PATCH 10/47] kr translate update links of 03-00-02, 03

---
 kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md    | 5 ++---
 .../00_Setup/03_Physical_devices_and_queue_families.md       | 2 +-
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
index 02c5391c..a04094d7 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
@@ -33,8 +33,7 @@ Vulkan은 내장 검증 레이어를 제공하지는 않지만 LunarG Vulkan SDK
 
 검증 레이어는 시스템에 설치되어 있어야 사용 가능합니다. 예를 들어 LunarG 검증 레이어는 Vulkan SDK가 설치된 PC에서만 사용 가능합니다.
 
-Vulkan에는 두 가지 종류의 검증 레이어가 존재하는데 인스턴스와 장치(device) 레이어입니다. 인스턴스 레이어는 인스턴스와 같은 전역 Vulkan 객체들만을 체크하고 장치 레이어는 특정 GPU에 관련된 호출만을 체크합니다. 현재 장치 레이어는 더 이상 사용되지 않으며(deprecated), 인스턴스 검증 레이어가 모든 Vulkan 호출에 적용됩니다. 명세 문서에는 여전히 호환성을 위해 장치 수준에서 검증 레이어를 활성화 할 것을 권장하고 있습니다. We'll simply specify the same layers as the instance at logical
-device level, which we'll see [later on](!en/Drawing_a_triangle/Setup/Logical_device_and_queues).
+Vulkan에는 두 가지 종류의 검증 레이어가 존재하는데 인스턴스와 장치(device) 레이어입니다. 인스턴스 레이어는 인스턴스와 같은 전역 Vulkan 객체들만을 체크하고 장치 레이어는 특정 GPU에 관련된 호출만을 체크합니다. 현재 장치 레이어는 더 이상 사용되지 않으며(deprecated), 인스턴스 검증 레이어가 모든 Vulkan 호출에 적용됩니다. 명세 문서에는 여전히 호환성을 위해 장치 수준에서 검증 레이어를 활성화 할 것을 권장하고 있습니다. [나중에](!kr/Drawing_a_triangle/Setup/Logical_device_and_queues) 보게 될 것인데, 우리는 같은 레이어를 인스턴스와 논리 장치 수준에서 사용할 것입니다.
 
 ## 검증 레이어 사용하기
 
@@ -370,6 +369,6 @@ void createInstance() {
 
 여러분의 응용 프로그램을 위한 레이어 설정을 하기 위해서는 그 파일을 프로젝트의 `Debug` 와 `Release` 디렉터리에 복사하고 원하는 동작을 하도록 안내를 따라 하면 됩니다. 하지만, 이 튜토리얼에서는 기본 세팅을 사용하는 것으로 가정할 것입니다.
 
-앞으로 튜토리얼에서 검증 레이어의 유용함을 보여드리기 위해서, 그리고 Vulkan이 정확히 어떤 일을 하는지를 아는 것이 얼마나 중요한지 알려드리기 위해 일부러 실수를 하는 경우들이 있을 겁니다. 이제 [시스템의 Vulkan 장치](!en/Drawing_a_triangle/Setup/Physical_devices_and_queue_families)에 대해 알아봅시다.
+앞으로 튜토리얼에서 검증 레이어의 유용함을 보여드리기 위해서, 그리고 Vulkan이 정확히 어떤 일을 하는지를 아는 것이 얼마나 중요한지 알려드리기 위해 일부러 실수를 하는 경우들이 있을 겁니다. 이제 [시스템의 Vulkan 장치](!kr/Drawing_a_triangle/Setup/Physical_devices_and_queue_families)에 대해 알아봅시다.
 
 [C++ code](/code/02_validation_layers.cpp)
diff --git a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
index d1de72e5..d0259ae2 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
@@ -304,6 +304,6 @@ for (const auto& queueFamily : queueFamilies) {
 }
 ```
 
-좋습니다. 우선은 적절한 물리적 장치를 찾는 것은 이것으로 끝입니다! 다음 단계는 [논리적 장치](!en/Drawing_a_triangle/Setup/Logical_device_and_queues)와의 인터페이스를 만드는 것입니다.
+좋습니다. 우선은 적절한 물리적 장치를 찾는 것은 이것으로 끝입니다! 다음 단계는 [논리적 장치](!kr/Drawing_a_triangle/Setup/Logical_device_and_queues)와의 인터페이스를 만드는 것입니다.
 
 [C++ code](/code/03_physical_device_selection.cpp)

From c7059934bac1d1344f937e467110851a8ac49d94 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sat, 14 Oct 2023 19:12:35 +0900
Subject: [PATCH 11/47] kr translate 03-01-00 window surface

---
 .../01_Presentation/00_Window_surface.md      | 128 +++++-------------
 1 file changed, 32 insertions(+), 96 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md b/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
index 966a8946..de781cd4 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
@@ -1,48 +1,22 @@
-Since Vulkan is a platform agnostic API, it can not interface directly with the
-window system on its own. To establish the connection between Vulkan and the
-window system to present results to the screen, we need to use the WSI (Window
-System Integration) extensions. In this chapter we'll discuss the first one,
-which is `VK_KHR_surface`. It exposes a `VkSurfaceKHR` object that represents an
-abstract type of surface to present rendered images to. The surface in our
-program will be backed by the window that we've already opened with GLFW.
-
-The `VK_KHR_surface` extension is an instance level extension and we've actually
-already enabled it, because it's included in the list returned by
-`glfwGetRequiredInstanceExtensions`. The list also includes some other WSI
-extensions that we'll use in the next couple of chapters.
-
-The window surface needs to be created right after the instance creation,
-because it can actually influence the physical device selection. The reason we
-postponed this is because window surfaces are part of the larger topic of
-render targets and presentation for which the explanation would have cluttered
-the basic setup. It should also be noted that window surfaces are an entirely
-optional component in Vulkan, if you just need off-screen rendering. Vulkan
-allows you to do that without hacks like creating an invisible window
-(necessary for OpenGL).
-
-## Window surface creation
-
-Start by adding a `surface` class member right below the debug callback.
+Vulkan은 플랫폼 독립적인 API이기 때문에, 윈도우 시스템과 직접적으로 소통할 수는 없습니다. Vulkan과 윈도우 시스템간의 연결을 만들어 결과물을 화면에 보이도록 하기 위해서 우리는 WIS (Window System Integration)을 사용해야만 합니다. 이 챕터에서는 먼저 `VK_KHR_surface`를 살펴볼 것입니다. `VK_KHR_surface` 객체는 렌더링된 이미지를 표현할 표면(surface)의 추상화된 객체입니다. 우리 프로그램에서 표면은 GLFW를 사용해 열어놓은 윈도우의 뒷받침이 될 것입니다.
+
+`VK_KHR_surface` 확장은 인스턴스 수준의 확장이고, 우리는 이미 활성화 시켜 놓았습니다. 왜냐하면 `glfwGetRequiredInstanceExtensions`를 통해 반환된 리스트에 포함되어 있거든요. 이 리스트는 다음 몇 챕터에서 사용할 다른 WSI 확장도 포함하고 있습니다.
+
+윈도우 표면은 인스턴스 생성 이후에 곧바로 생성해야 하는데 이는 윈도우 표면이 물리적 장치의 선택에 영향을 주기 때문입니다. 이 내용을 여기까지 미룬 이유는 윈도우 표면이 렌더 타겟과 표현과 관련된 큰 주제이고, 이러한 내용으로 기본적인 세팅 설명을 복잡하게 만들고 싶지 않았기 때문입니다. 또한 윈도우 표면은 Vulkan에서 선택적인 구성요소로, 오프 스크린(off-screen) 렌더링을 할 경우에는 필요하지 않습니다. Vulkan은 보이지 않는 윈도우를 생성하는 등의 편법을 동원하지 않고서도 이런 기능을 사용 가능합니다 (OpenGL에서는 이러한 방식으로 구현해야만 합니다).
+
+## 윈도우 표면 생성
+
+`surface` 클래스 멤버를 디버그 콜백 바로 뒤에 추가하는 것 부터 시작합니다.
 
 ```c++
 VkSurfaceKHR surface;
 ```
 
-Although the `VkSurfaceKHR` object and its usage is platform agnostic, its
-creation isn't because it depends on window system details. For example, it
-needs the `HWND` and `HMODULE` handles on Windows. Therefore there is a
-platform-specific addition to the extension, which on Windows is called
-`VK_KHR_win32_surface` and is also automatically included in the list from
-`glfwGetRequiredInstanceExtensions`.
+`VkSurfaceKHR`객체와 그 활용은 플랫폼 독립적이지만, 생성에 있어서는 윈도우 시스템의 세부 사항에 의존적입니다. 예를 들어, 윈도우에서는 `HWND` 와 `HMODULE` 핸들이 필요합니다. 따라서 플랫폼 의존적인 확장들이 존재하고 윈도우의 경우 이 확장은 `VK_KHR_win32_surface`입니다. 이 확장은 `glfwGetRequiredInstanceExtensions`에 자동적으로 포함되어 있습니다.
 
-I will demonstrate how this platform specific extension can be used to create a
-surface on Windows, but we won't actually use it in this tutorial. It doesn't
-make any sense to use a library like GLFW and then proceed to use
-platform-specific code anyway. GLFW actually has `glfwCreateWindowSurface` that
-handles the platform differences for us. Still, it's good to see what it does
-behind the scenes before we start relying on it.
+윈도우즈에서 표면을 생성하기 위해 이러한 플랫폼별 확장을 사용하는 예시를 보여드리겠습니다. 하지만 이 튜토리얼에서 이를 실제 사용하진 않을 것입니다. GLFW와 같은 라이브러리를 사용하면서 플랫폼별 코드를 사용하는 것은 적절하지 않습니다. GLFW에는 이미 `glfwCreateWindowSurface`를 통해 플랫폼별 차이에 따른 코드를 처리해 줍니다. 그래도, 사용하기 전에 뒤쪽에서 어떤 일이 벌어지는지는 알아두는 것이 좋겠죠.
 
-To access native platform functions, you need to update the includes at the top:
+네이티브 플랫폼 기능에 접근하기 위해서는 위쪽에 include를 바꿔줘야 합니다.
 
 ```c++
 #define VK_USE_PLATFORM_WIN32_KHR
@@ -52,10 +26,7 @@ To access native platform functions, you need to update the includes at the top:
 #include <GLFW/glfw3native.h>
 ```
 
-Because a window surface is a Vulkan object, it comes with a
-`VkWin32SurfaceCreateInfoKHR` struct that needs to be filled in. It has two
-important parameters: `hwnd` and `hinstance`. These are the handles to the
-window and the process.
+윈도우 표면은 Vulkan 객체기 때문에 우리가 값을 채워야 하는 `VkWin32SurfaceCreateInfoKHR` 구조체를 사용해야 합니다. 여기에는 두 개의 중요한 매개변수가 있는데 `hwnd` 와 `hinstance`입니다. 이는 윈도우와 프로세스에 대한 핸들입니다.
 
 ```c++
 VkWin32SurfaceCreateInfoKHR createInfo{};
@@ -64,11 +35,9 @@ createInfo.hwnd = glfwGetWin32Window(window);
 createInfo.hinstance = GetModuleHandle(nullptr);
 ```
 
-The `glfwGetWin32Window` function is used to get the raw `HWND` from the GLFW
-window object. The `GetModuleHandle` call returns the `HINSTANCE` handle of the
-current process.
+`glfwGetWin32Window`함수는 GLFW 윈도웅 객체로부터 `HWND`를 얻기위해 사용됩니다. `GetModuleHandle` 호출은 현재 프로세스의 `HINSTANCE` 핸들을 반환해줍니다.
 
-After that the surface can be created with `vkCreateWin32SurfaceKHR`, which includes a parameter for the instance, surface creation details, custom allocators and the variable for the surface handle to be stored in. Technically this is a WSI extension function, but it is so commonly used that the standard Vulkan loader includes it, so unlike other extensions you don't need to explicitly load it.
+이루에는 `vkCreateWin32SurfaceKHR`를 통해 표면을 생성할 수 있는데, 인스턴스, 표면 생성 세부사항, 사용자 정의 할당자와 표면 핸들 저장을 위한 변수가 매개변수입니다. 정확히 하자면 이는 WSI 확장 함수이지만, 자주 사용되는 관계로 표준 Vulkan 로더에 포함되어 있고, 그렇기 때문에 명시적으로 로드할 필요가 없습니다.
 
 ```c++
 if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
@@ -76,14 +45,9 @@ if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCC
 }
 ```
 
-The process is similar for other platforms like Linux, where
-`vkCreateXcbSurfaceKHR` takes an XCB connection and window as creation details
-with X11.
+이 과정은 리눅스 등 다른 플랫폼에서도 유사한데, 이 경우 `vkCreateXcbSurfaceKHR`는 XCB 커넥션과 윈도우, X11 등 세부사항을 생성 시에 명시해 주어야 합니다.
 
-The `glfwCreateWindowSurface` function performs exactly this operation with a
-different implementation for each platform. We'll now integrate it into our
-program. Add a function `createSurface` to be called from `initVulkan` right
-after instance creation and `setupDebugMessenger`.
+`glfwCreateWindowSurface` 함수는 이러한 과정을 각 플랫폼별로 구현해 두었습니다. 우리는 이를 우리의 프로그램에 통합해서 사용하기만 하면 됩니다. `initVulkan`에서 호출할 `createSurface` 함수를 인스턴스 생성과 `setupDebugMessenger` 뒤에 추가하겠습니다.
 
 ```c++
 void initVulkan() {
@@ -99,8 +63,7 @@ void createSurface() {
 }
 ```
 
-The GLFW call takes simple parameters instead of a struct which makes the
-implementation of the function very straightforward:
+GLFW 호출은 구조체 대신 간단한 매개변수들을 받기 때문에 구현이 직관적입니다.
 
 ```c++
 void createSurface() {
@@ -110,10 +73,7 @@ void createSurface() {
 }
 ```
 
-The parameters are the `VkInstance`, GLFW window pointer, custom allocators and
-pointer to `VkSurfaceKHR` variable. It simply passes through the `VkResult` from
-the relevant platform call. GLFW doesn't offer a special function for destroying
-a surface, but that can easily be done through the original API:
+매개변수는 `VkInstance`, GLFW 윈도우에 대한 포인터, 사용자 정의 할당자와 `VkSurfaceKHR` 변수에 대한 포인터입니다. 내부적으로는 플랫폼 관련 호출을 할 뒤에 `VkResult` 값을 전달하여 반환해 줍니다. GLFW는 표면 소멸을 위한 특별한 함수를 제공하지는 않고, 기본(original) API를 통해 간단히 구현하면 됩니다:
 
 ```c++
 void cleanup() {
@@ -124,21 +84,13 @@ void cleanup() {
     }
 ```
 
-Make sure that the surface is destroyed before the instance.
+표면이 인스턴스보다 먼저 소멸되도록 해야 합니다.
 
-## Querying for presentation support
+## 표현 지원에 대한 질의(Querying for presentation support)
 
-Although the Vulkan implementation may support window system integration, that
-does not mean that every device in the system supports it. Therefore we need to
-extend `isDeviceSuitable` to ensure that a device can present images to the
-surface we created. Since the presentation is a queue-specific feature, the
-problem is actually about finding a queue family that supports presenting to the
-surface we created.
+Vulkan 구현이 윈도우 시스템 통합을 지원하지만, 그렇다고 모든 시스템의 장치가 이를 지원한다는 의미는 아닙니다. 따라서 `isDeviceSuitable`를 확장하여 장치가 우리가 만든 표면에 이미지를 표현할 수 있는지를 확인해야 합니다. 표현(presentation)은 큐의 기능이므로 이는 우리가 만든 표면에 표현 기능을 지원하는 큐 패밀리를 찾는 문제로 귀결됩니다.
 
-It's actually possible that the queue families supporting drawing commands and
-the ones supporting presentation do not overlap. Therefore we have to take into
-account that there could be a distinct presentation queue by modifying the
-`QueueFamilyIndices` structure:
+그리기 명령(drawing command)을 지원하는 큐 패밀리와 표현 기능을 지원하는 큐 패밀리가 동일하지 않을 수 있습니다. 따라서 `QueueFamilyIndices` 구조체를 수정해 별도의 표현 큐 상황을 고려하겠습니다.
 
 ```c++
 struct QueueFamilyIndices {
@@ -151,19 +103,14 @@ struct QueueFamilyIndices {
 };
 ```
 
-Next, we'll modify the `findQueueFamilies` function to look for a queue family
-that has the capability of presenting to our window surface. The function to
-check for that is `vkGetPhysicalDeviceSurfaceSupportKHR`, which takes the
-physical device, queue family index and surface as parameters. Add a call to it
-in the same loop as the `VK_QUEUE_GRAPHICS_BIT`:
+다음으로, `findQueueFamilies`함수를 수정해서 윈도우 표면에 표현 기능이 있는 큐 패밀리를 찾아 봅시다. 이를 체크하기 위한 함수는 `vkGetPhysicalDeviceSurfaceSupportKHR` 함수히고, 물리적 장치, 큐 패밀리 인덱스와 표면을 매개변수로 받습니다. `VK_QUEUE_GRAPHICS_BIT`와 동일한 루프에 이 함수의 호출을 추가합니다:
 
 ```c++
 VkBool32 presentSupport = false;
 vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
 ```
 
-Then simply check the value of the boolean and store the presentation family
-queue index:
+불리언 값을 확인하고 표현 패밀리의 큐 인덱스를 저장합니다:
 
 ```c++
 if (presentSupport) {
@@ -171,25 +118,17 @@ if (presentSupport) {
 }
 ```
 
-Note that it's very likely that these end up being the same queue family after
-all, but throughout the program we will treat them as if they were separate
-queues for a uniform approach. Nevertheless, you could add logic to explicitly
-prefer a physical device that supports drawing and presentation in the same
-queue for improved performance.
+알아두셔야 할 것은 결국 이 두 큐 패밀리는 동일할 가능성이 아주 높다는 것입니다. 하지만 이 프로그램에서는 접근법의 일관성을 위해 이 둘을 별개의 큐인 것처럼 처리할 것입니다. 그리기와 표현을 동일한 큐에서 지원하는 물리적 장치를 선호하도록 로직을 구현하면 성능이 개선될 수 있습니다.
 
-## Creating the presentation queue
+## 표현 큐 생성하기
 
-The one thing that remains is modifying the logical device creation procedure to
-create the presentation queue and retrieve the `VkQueue` handle. Add a member
-variable for the handle:
+이제 남은 것은 논리적 장치 생성 과정을 수정하여 표현 큐를 생성하고 `VkQueue` 핸들을 찾는 과정입니다. 핸들을 위한 멤버 변수를 추가합니다:
 
 ```c++
 VkQueue presentQueue;
 ```
 
-Next, we need to have multiple `VkDeviceQueueCreateInfo` structs to create a
-queue from both families. An elegant way to do that is to create a set of all
-unique queue families that are necessary for the required queues:
+다음으로 여러 개의 `VkDeviceQueueCreateInfo` 구조체를 추가하여 두 개의 패밀리로부터 큐를 생성해야 합니다. 깔끔한 방법은 요구되는 모든 큐에 대한 고유한(unique) 큐 패밀리 집합을 생성하는 방법입니다:
 
 ```c++
 #include <set>
@@ -212,22 +151,19 @@ for (uint32_t queueFamily : uniqueQueueFamilies) {
 }
 ```
 
-And modify `VkDeviceCreateInfo` to point to the vector:
+그리고 `VkDeviceCreateInfo`에서 해당 벡터를 가리키도록 수정합니다:
 
 ```c++
 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
 createInfo.pQueueCreateInfos = queueCreateInfos.data();
 ```
 
-If the queue families are the same, then we only need to pass its index once.
-Finally, add a call to retrieve the queue handle:
+큐 패밀리가 같다면 인덱스를 한 번만 넘겨주면 됩니다. 마지막으로 큐 핸들을 얻기 위한 호출을 추가합니다.
 
 ```c++
 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
 ```
 
-In case the queue families are the same, the two handles will most likely have
-the same value now. In the next chapter we're going to look at swap chains and
-how they give us the ability to present images to the surface.
+큐 패밀리가 같은 경우 두 개의 핸들은 이제 같은 값을 가질 것입니다. 다음 챕터에서는 스왑 체인을 살펴보고 어떻게 스왑 체인이 표면에 이미지를 표현하는 기능을 제공하는지 알아볼 것입니다.
 
 [C++ code](/code/05_window_surface.cpp)

From a1e53f2c438163e546156e149e6f48fdb5877a3d Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sun, 15 Oct 2023 17:16:23 +0900
Subject: [PATCH 12/47] kr translate 03-01-01 swap chain

---
 .../01_Presentation/01_Swap_chain.md          | 327 +++++-------------
 1 file changed, 84 insertions(+), 243 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
index ef1ceabd..26808457 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
@@ -1,30 +1,12 @@
-Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own the buffers we will render to before we visualize them on the screen. This infrastructure is
-known as the *swap chain* and must be created explicitly in Vulkan. The swap
-chain is essentially a queue of images that are waiting to be presented to the
-screen. Our application will acquire such an image to draw to it, and then
-return it to the queue. How exactly the queue works and the conditions for
-presenting an image from the queue depend on how the swap chain is set up, but
-the general purpose of the swap chain is to synchronize the presentation of
-images with the refresh rate of the screen.
-
-## Checking for swap chain support
-
-Not all graphics cards are capable of presenting images directly to a screen for
-various reasons, for example because they are designed for servers and don't
-have any display outputs. Secondly, since image presentation is heavily tied
-into the window system and the surfaces associated with windows, it is not
-actually part of the Vulkan core. You have to enable the `VK_KHR_swapchain`
-device extension after querying for its support.
-
-For that purpose we'll first extend the `isDeviceSuitable` function to check if
-this extension is supported. We've previously seen how to list the extensions
-that are supported by a `VkPhysicalDevice`, so doing that should be fairly
-straightforward. Note that the Vulkan header file provides a nice macro
-`VK_KHR_SWAPCHAIN_EXTENSION_NAME` that is defined as `VK_KHR_swapchain`. The
-advantage of using this macro is that the compiler will catch misspellings.
-
-First declare a list of required device extensions, similar to the list of
-validation layers to enable.
+Vulkan은 "기본 프레임버퍼(default framebuffer)"의 개념이 없습니다. 따라서 렌더링을 수행할 버퍼를 소유한 하부 구조(infrastructure)를 만들어서 화면에 그려지게 해야 합니다. 이 하부 구조는 *스왑 체인(swap chain)*이라고 하며 Vulkan의 경우 명시적으로 생성되어야 합니다. 스왑 체인은 기본적으로 화면에 표시되길 기다리는 이미지들의 큐입니다. 우리 응용 프로그램에서는 이러한 이미지를 만들고 큐에 반환할 것입니다. 큐가 어떻게 동작하고 어떤 조건에서 큐의 이미지가 표시될 것인지와 같은 사항들은 스왑 체인의 설정에 따라 달라집니다. 하지만 일반적으로 스왑 체인의 역할은 화면의 주사율(refresh rate)과 이미지의 표시를 동기화(synchronize)하는 것입니다.
+
+## 스왑 체인 지원 확인하기
+
+모든 그래픽 카드가 이미지를 곧바로 화면에 표시하는 기능을 지원하는 것은 아닙니다. 예를 들어 서버를 위해 설계된 그래픽 카드는 디스플레이 출력이 없을 수 있습니다. 또한, 이미지의 표현은 윈도우 시스템, 그 윈도우와 연관된 표면(surface)와 밀접하게 관련되어 있기 때문에 Vulkan 코어(core)에는 포함되어 있지 않습니다. 지원하는지를 확인한 후에 `VK_KHR_swapchain` 장치 확장을 활성화시켜줘야만 합니다.
+
+이러한 목적으로 우리는 먼저 `isDeviceSuitable` 함수를 수정해 이러한 확장을 지원하는지 확인할 것입니다. `VkPhysicalDevice`를 사용해 지원하는 확장의 목록을 얻는 법을 이미 봤기 때문에 어렵지 않을 겁니다. Vulkan 헤더 파일은 `VK_KHR_swapchain`로 정의된 `VK_KHR_SWAPCHAIN_EXTENSION_NAME` 매크로를 지원합니다. 매크로를 사용하면 컴파일러가 타이핑 오류를 탐지할 수 있습니다.
+
+먼저 필요로 하는 장치 확장의 목록을 정의합니다. 이는 검증 레이어 목록을 얻은 것과 유사합니다.
 
 ```c++
 const std::vector<const char*> deviceExtensions = {
@@ -32,8 +14,7 @@ const std::vector<const char*> deviceExtensions = {
 };
 ```
 
-Next, create a new function `checkDeviceExtensionSupport` that is called from
-`isDeviceSuitable` as an additional check:
+다음으로 `checkDeviceExtensionSupport` 함수를 새로 만듭니다. 이는 `isDeviceSuitable`에서 추가적인 체크를 위해 호출됩니다:
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -49,8 +30,7 @@ bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
 }
 ```
 
-Modify the body of the function to enumerate the extensions and check if all of
-the required extensions are amongst them.
+함수의 본문을 수정해 확장들을 열거하고 요구되는 확장들이 있는지를 확인합니다.
 
 ```c++
 bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
@@ -70,46 +50,30 @@ bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
 }
 ```
 
-I've chosen to use a set of strings here to represent the unconfirmed required
-extensions. That way we can easily tick them off while enumerating the sequence
-of available extensions. Of course you can also use a nested loop like in
-`checkValidationLayerSupport`. The performance difference is irrelevant. Now run
-the code and verify that your graphics card is indeed capable of creating a
-swap chain. It should be noted that the availability of a presentation queue,
-as we checked in the previous chapter, implies that the swap chain extension
-must be supported. However, it's still good to be explicit about things, and
-the extension does have to be explicitly enabled.
+요구사항에 있지만 확인되지 않은 확장들을 표현하기 위해 문자열(string) 집합(set)을 사용했습니다. 이렇게 하면 가용한 확장들을 열거하면서 쉽게 제외할 수 있습니다. 물론 `checkValidationLayerSupport`에서처럼 중첩된 루프(nested loop)를 사용해도 됩니다. 성능에 영향은 없습니다. 이제 코드를 실행해 여러분의 그래픽 카드가 스왑 체인을 생성할 수 있는지 확인하세요. 사실 이전 장에서 확인한 표현 큐가 사용 가능하다면 스왑 체인 확장도 반드시 지원해야만 합니다. 하지만 이러한 사항들을 명시적으로 확인하고, 확장 또한 명시적으로 활성화 하는 것이 더 좋습니다.
 
-## Enabling device extensions
+## 장치 확장 활성화
 
-Using a swapchain requires enabling the `VK_KHR_swapchain` extension first.
-Enabling the extension just requires a small change to the logical device
-creation structure:
+스왑 체인을 사용하려면 먼저 `VK_KHR_swapchain` 확장을 활성화 해야 합니다. 확장을 활성화하기 위해서는 논리적 장치 생성 구조체에 약간의 변경만 해 주면 됩니다:
 
 ```c++
 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
 ```
 
-Make sure to replace the existing line `createInfo.enabledExtensionCount = 0;` when you do so.
+기존의 `createInfo.enabledExtensionCount = 0;` 명령문을 대체하도록 해야 합니다.
 
-## Querying details of swap chain support
+## 스왑 체인 지원 세부사항 질의
 
-Just checking if a swap chain is available is not sufficient, because it may not
-actually be compatible with our window surface. Creating a swap chain also
-involves a lot more settings than instance and device creation, so we need to
-query for some more details before we're able to proceed.
+스왑 체인이 사용 가능한지만 확인하는 것으로 끝이 아닙니다. 왜냐하면 우리의 윈도우 표면과 실제로 호환이 되지 않을 수도 있기 떄문입니다. 스왑 체인을 생성하는 것 또한 인스턴스나 장치 생성보다 복잡한 설정이 필요하므로 더 진행하기 전에 필요한 세부 사항들을 질의하는 것 부터 시작해야 합니다.
 
-There are basically three kinds of properties we need to check:
+확인해야 할 속성은 기본적으로 세 종류입니다:
 
-* Basic surface capabilities (min/max number of images in swap chain, min/max
-width and height of images)
-* Surface formats (pixel format, color space)
-* Available presentation modes
+* 기본 표면 기능 (스왑 체인의 최대/최소 이미지 개수, 이미지의 최대/최소 너비와 높이)
+* 표면 포맷 (픽셀 포맷, 컬러 공간)
+* 사용 가능한 표시 모드
 
-Similar to `findQueueFamilies`, we'll use a struct to pass these details around
-once they've been queried. The three aforementioned types of properties come in
-the form of the following structs and lists of structs:
+`findQueueFamilies`와 유사하게, 구조체를 사용하여 이러한 세부 사항들을 질의 과정에서 사용할 것입니다. 위에 이야기한 세 종류의 속성들은 다음과 같은 구조체와 구조체 리스트로 만듭립니다.
 
 ```c++
 struct SwapChainSupportDetails {
@@ -119,8 +83,7 @@ struct SwapChainSupportDetails {
 };
 ```
 
-We'll now create a new function `querySwapChainSupport` that will populate this
-struct.
+다음으로 `querySwapChainSupport` 라는 이름의 새 함수를 만들고 이 구조체를 생성합니다.
 
 ```c++
 SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
@@ -130,24 +93,17 @@ SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
 }
 ```
 
-This section covers how to query the structs that include this information. The
-meaning of these structs and exactly which data they contain is discussed in the
-next section.
+이 장에서는 이러한 정보를 포함한 구조체를 질의하는 방법을 설명합니다. 이 구조체의 의미와 정확히 어떤 데이터들을 가지고 있는지는 다음 장에서 설명할 것입니다.
 
-Let's start with the basic surface capabilities. These properties are simple to
-query and are returned into a single `VkSurfaceCapabilitiesKHR` struct.
+기본 표면 기능으로 시작해 봅시다. 이러한 속성들은 질의하기 쉽고 `VkSurfaceCapabilitiesKHR` 타입의 단일 구조체로 반환됩니다.
 
 ```c++
 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
 ```
 
-This function takes the specified `VkPhysicalDevice` and `VkSurfaceKHR` window
-surface into account when determining the supported capabilities. All of the
-support querying functions have these two as first parameters because they are
-the core components of the swap chain.
+이 함수는 지원되는 기능을 판단할 때, 명시된 `VkPhysicalDevice`와 `VkSurfaceKHR` 윈도우 표면을 고려하도록 구현되어 있습니다. 모든 질의 지원 함수들은 이러한 두 매개변수를 받도록 되어 있는데 이들이 스왑 체인의 핵심 구성요소이기 때문입니다.
 
-The next step is about querying the supported surface formats. Because this is a
-list of structs, it follows the familiar ritual of 2 function calls:
+다음 단계는 지원하는 표면 포맷을 질의하는 것입니다. 이는 구조체의 리스트로, 두 개의 함수 호출과 유사한 방식입니다.
 
 ```c++
 uint32_t formatCount;
@@ -159,9 +115,7 @@ if (formatCount != 0) {
 }
 ```
 
-Make sure that the vector is resized to hold all the available formats. And
-finally, querying the supported presentation modes works exactly the same way
-with `vkGetPhysicalDeviceSurfacePresentModesKHR`:
+모든 가능한 포맷을 저장할 수 있도록 벡터의 크기가 변하게 해야 합니다. 마지막으로, 지원하는 표현 모드를 질의하는 것도 `vkGetPhysicalDeviceSurfacePresentModesKHR`를 사용해 동일한 방식으로 이루어집니다:
 
 ```c++
 uint32_t presentModeCount;
@@ -173,11 +127,7 @@ if (presentModeCount != 0) {
 }
 ```
 
-All of the details are in the struct now, so let's extend `isDeviceSuitable`
-once more to utilize this function to verify that swap chain support is
-adequate. Swap chain support is sufficient for this tutorial if there is at
-least one supported image format and one supported presentation mode given the
-window surface we have.
+이제 모든 세부 사항이 구조체 안에 있으니 `isDeviceSuitable`를 한번 더 확장하여 적절하게 스왑 체인이 지원되고 있는지 확인하도록 해 봅시다. 이 튜토리얼에서의 스왑 체인 지원은 우리가 가진 윈도우 표면에 대해 최소 하나의 이미지 포맷과 표면 모드를 지원하는 것이면 충분합니다.
 
 ```c++
 bool swapChainAdequate = false;
@@ -187,32 +137,25 @@ if (extensionsSupported) {
 }
 ```
 
-It is important that we only try to query for swap chain support after verifying
-that the extension is available. The last line of the function changes to:
+확장이 사용 가능한지 확인 후에 스왑 체인 지원 여부를 얻은 것일 뿐입니다. 함수의 마지막 라인은 아래와 같이 변경되어야 합니다.
 
 ```c++
 return indices.isComplete() && extensionsSupported && swapChainAdequate;
 ```
 
-## Choosing the right settings for the swap chain
+## 스왑 체인에 대한 적절한 설정 선택하기
 
-If the `swapChainAdequate` conditions were met then the support is definitely
-sufficient, but there may still be many different modes of varying optimality.
-We'll now write a couple of functions to find the right settings for the best
-possible swap chain. There are three types of settings to determine:
+우리가 얻은 `swapChainAdequate` 조건이 만족되었다면 충분하지만, 서로 다른 최적화 요구사항에 대한 모드들이 여전히 존재합니다. 이제는 최적의 스왑 체인 설정을 찾기 위한 몇 가지 함수를 작성해 볼 것입니다. 결정해야 할 설정은 세 가지입니다:
 
-* Surface format (color depth)
-* Presentation mode (conditions for "swapping" images to the screen)
-* Swap extent (resolution of images in swap chain)
+* 표면 포맷 (색상 깊이(depth))
+* 표시 모드 (이미지를 화면에 "스왑"하는 조건)
+* 스왑 크기 (스왑 체인 이미지의 해상도)
 
-For each of these settings we'll have an ideal value in mind that we'll go with
-if it's available and otherwise we'll create some logic to find the next best
-thing.
+이러한 각각의 설정에 대해 생각하고 있는 이상적인 값이 있을 것이고, 가능하면 이러한 값을 사용하도록 합니다. 그렇지 않다면 그 다음으로 괜찮은 값을 사용하도록 하는 로직을 만들 것입니다.
 
-### Surface format
+### 표면 포맷
 
-The function for this setting starts out like this. We'll later pass the
-`formats` member of the `SwapChainSupportDetails` struct as argument.
+이 설정에 대한 함수는 아래와 같이 시작합니다. 뒤에서 `SwapChainSupportDetails` 구조체의 `formats` 멤버를 인자로 넘겨줄 것입니다.
 
 ```c++
 VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
@@ -220,18 +163,11 @@ VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>
 }
 ```
 
-Each `VkSurfaceFormatKHR` entry contains a `format` and a `colorSpace` member. The
-`format` member specifies the color channels and types. For example,
-`VK_FORMAT_B8G8R8A8_SRGB` means that we store the B, G, R and alpha channels in
-that order with an 8 bit unsigned integer for a total of 32 bits per pixel. The
-`colorSpace` member indicates if the SRGB color space is supported or not using
-the `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR` flag. Note that this flag used to be
-called `VK_COLORSPACE_SRGB_NONLINEAR_KHR` in old versions of the specification.
+각 `VkSurfaceFormatKHR`는 `format` 과 `colorSpace` 멤버를 가지고 있습니다. `format`은 컬러 채널과 타입을 명시합니다. 예를 들어 `VK_FORMAT_B8G8R8A8_SRGB`는 B,G,R과 알파 채널을 그 순서대로 8비트 부호없는(unsigned) 정수로 저장하여 픽셀달 32비트를 사용합니다. `colorSpace` 멤버는 `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`를 사용해 SRGB 컬러 공간을 지원하는지 여부를 표시합니다. 참고로 이 플래그는 이전 버전 명세에서는 `VK_COLORSPACE_SRGB_NONLINEAR_KHR`였습니다.
 
-For the color space we'll use SRGB if it is available, because it [results in more accurate perceived colors](http://stackoverflow.com/questions/12524623/). It is also pretty much the standard color space for images, like the textures we'll use later on.
-Because of that we should also use an SRGB color format, of which one of the most common ones is `VK_FORMAT_B8G8R8A8_SRGB`.
+컬러 공간에 대해서 우리는 가능하면 SRGB를 사용할 것인데, 이것이 [보다 정확한 색상 인지가 가능하기 때문입니다](http://stackoverflow.com/questions/12524623/). 또한 이는 나중에 살펴볼 (예를들면 텍스처와 같은) 이미지에 대한 표준 컬러 공간입니다. 이러한 이유로 컬러 포맷도 SRGB 컬러 포맷을 사용하는 것이고 가장 흔히 사용되는 것이 `VK_FORMAT_B8G8R8A8_SRGB`입니다.
 
-Let's go through the list and see if the preferred combination is available:
+리스트를 순회하며 이러한 선호하는 조합이 사용 가능한지 확인합니다.
 
 ```c++
 for (const auto& availableFormat : availableFormats) {
@@ -241,9 +177,7 @@ for (const auto& availableFormat : availableFormats) {
 }
 ```
 
-If that also fails then we could start ranking the available formats based on
-how "good" they are, but in most cases it's okay to just settle with the first
-format that is specified.
+실패한다면 사용 가능한 포맷들에 얼마나 "좋은지" 여부를 바탕으로 순위를 매길 수 있습니다. 하지만 대부분 명시된 첫 번째 포맷을 그냥 사용하는 것으로 충분합니다.
 
 ```c++
 VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
@@ -257,31 +191,16 @@ VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>
 }
 ```
 
-### Presentation mode
+### 표시 모드(Presentation mode)
 
-The presentation mode is arguably the most important setting for the swap chain,
-because it represents the actual conditions for showing images to the screen.
-There are four possible modes available in Vulkan:
+표시 모드는 스왑 체인에서 가장 중요한 설정인데, 이미지를 화면에 표시하는 실제 조건을 나타내는 부분이기 때문입니다. Vulkan에서는 네 가지 모드가 가능합니다:
 
-* `VK_PRESENT_MODE_IMMEDIATE_KHR`: Images submitted by your application are
-transferred to the screen right away, which may result in tearing.
-* `VK_PRESENT_MODE_FIFO_KHR`: The swap chain is a queue where the display takes
-an image from the front of the queue when the display is refreshed and the
-program inserts rendered images at the back of the queue. If the queue is full
-then the program has to wait. This is most similar to vertical sync as found in
-modern games. The moment that the display is refreshed is known as "vertical
-blank".
-* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`: This mode only differs from the previous
-one if the application is late and the queue was empty at the last vertical
-blank. Instead of waiting for the next vertical blank, the image is transferred
-right away when it finally arrives. This may result in visible tearing.
-* `VK_PRESENT_MODE_MAILBOX_KHR`: This is another variation of the second mode.
-Instead of blocking the application when the queue is full, the images that are
-already queued are simply replaced with the newer ones. This mode can be used to
-render frames as fast as possible while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked.
+* `VK_PRESENT_MODE_IMMEDIATE_KHR`: 여러분 응용 프로그램에서 제출(submit)된 이미지가 곧바로 화면에 전송되어 테어링(tearing) 현상이 발생할 수 있습니다.
+* `VK_PRESENT_MODE_FIFO_KHR`: 스왑 체인은 큐가 되어, 디스플레이는 화면이 갱신될 때 큐의 앞에서 이미지를 가져오고, 프로그램은 렌더링된 이미지를 큐의 뒤에 삽입합니다. 큐가 꽉 차면 프로그램은 대기해야 합니다. 현대 게임에서 볼 수 있는 수직 동기화(vertical sync)와 유사합니다. 화면이 갱신되는 순간은 "수직 공백(vertical blank)"라 불립니다.
+* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`: 이 모드는 이전 모드와 프로그램이 지연되어서 마지막 수직 공백때 큐가 비는 경우에만 다르게 동작합니다. 다음 수직 공백을 기다리는 대신, 그 다음 이미지가 도착하는 즉시 전송됩니다. 이러한 경우 눈에 띄는 테어링이 발생하게 됩니다.
+* `VK_PRESENT_MODE_MAILBOX_KHR`: 이것도 두 번째 모드의 또다른 버전입니다. 큐가 꽉 찼을 때 응용 프로그램을 대기시키는 대신, 큐에 있는 이미지가 새로운 이미지로 대체됩니다. 이 모드는 가능한 빠르게 렌더링을 수행하면서 테어링을 방지할 수 있고, 표준적인 수직 동기화보다 지연시간(latency) 문제를 줄일 수 있습니다. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked.
 
-Only the `VK_PRESENT_MODE_FIFO_KHR` mode is guaranteed to be available, so we'll
-again have to write a function that looks for the best mode that is available:
+`VK_PRESENT_MODE_FIFO_KHR` 모드만 사용 가능한 것이 보장되기 때문에 사용 가능한 최선의 모드를 찾는 함수를 작성합니다:
 
 ```c++
 VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
@@ -289,7 +208,7 @@ VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& avai
 }
 ```
 
-I personally think that `VK_PRESENT_MODE_MAILBOX_KHR` is a very nice trade-off if energy usage is not a concern. It allows us to avoid tearing while still maintaining a fairly low latency by rendering new images that are as up-to-date as possible right until the vertical blank. On mobile devices, where energy usage is more important, you will probably want to use `VK_PRESENT_MODE_FIFO_KHR` instead. Now, let's look through the list to see if `VK_PRESENT_MODE_MAILBOX_KHR` is available:
+에너지 사용량 문제가 없는 경우라면, 개인적으로 `VK_PRESENT_MODE_MAILBOX_KHR`가 좋은 대체 모드라고 생각합니다. 테어링을 방지하면서도 새로운 이미지를 가급적 최신 이미지로 수직 공백 전까지 유지하기 때문에 꽤 낮은 지연시간을 갖습니다. 모바일 장치와 같이 에너지 사용량 문제가 중요한 경우에는 대신 `VK_PRESENT_MODE_FIFO_KHR`를 사용하는 것이 좋을 것입니다. 이제 `VK_PRESENT_MODE_MAILBOX_KHR` 가 사용 가능한지 리스트 내에서 찾아 봅니다:
 
 ```c++
 VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
@@ -303,9 +222,9 @@ VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& avai
 }
 ```
 
-### Swap extent
+### 스왑 크기(extent)
 
-That leaves only one major property, for which we'll add one last function:
+이제 주요 속성 하나만 남았고, 마지막 함수로 추가할 것입니다:
 
 ```c++
 VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
@@ -313,29 +232,9 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 }
 ```
 
-The swap extent is the resolution of the swap chain images and it's almost
-always exactly equal to the resolution of the window that we're drawing to _in
-pixels_ (more on that in a moment). The range of the possible resolutions is
-defined in the `VkSurfaceCapabilitiesKHR` structure. Vulkan tells us to match
-the resolution of the window by setting the width and height in the
-`currentExtent` member. However, some window managers do allow us to differ here
-and this is indicated by setting the width and height in `currentExtent` to a
-special value: the maximum value of `uint32_t`. In that case we'll pick the
-resolution that best matches the window within the `minImageExtent` and
-`maxImageExtent` bounds. But we must specify the resolution in the correct unit.
-
-GLFW uses two units when measuring sizes: pixels and
-[screen coordinates](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems).
-For example, the resolution `{WIDTH, HEIGHT}` that we specified earlier when
-creating the window is measured in screen coordinates. But Vulkan works with
-pixels, so the swap chain extent must be specified in pixels as well.
-Unfortunately, if you are using a high DPI display (like Apple's Retina
-display), screen coordinates don't correspond to pixels. Instead, due to the
-higher pixel density, the resolution of the window in pixel will be larger than
-the resolution in screen coordinates. So if Vulkan doesn't fix the swap extent
-for us, we can't just use the original `{WIDTH, HEIGHT}`. Instead, we must use
-`glfwGetFramebufferSize` to query the resolution of the window in pixel before
-matching it against the minimum and maximum image extent.
+스왑 크기는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 _픽셀 단위에서_ 우리가 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)으로 해상도를 명시해야 합니다.
+
+GLFW는 크기를 측정하는 두 단위가 있고 이는 픽셀과 [스크린 좌표계](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems) 입니다. 예를 들어 우리가 이전에 윈도우를 생성할 때 명시한 `{WIDTH, HEIGHT}` 해상도는 스크린 좌표계 기준으로 측정한 값입니다. 하지만 Vulkan은 픽셀 단위로 동작하기 때문에, 스왑 체인의 크기도 픽셀 단위로 명시해 주어야만 합니다. 안타깝게도 여러분이 (애플릐 레티나 디스플레이와 같은) 고DPI 디스플레이를 사용하는 경우, 스크린 좌표계가 픽셀 단위와 달라집니다. 높은 픽셀 밀도로 인해 픽셀 단위의 윈도우 해상도는 스크린 좌표계 단위의 윈도우 해상도보다 커집니다. Vulkan이 스왑 크기에 관한 것을 수정해 주지 않는 한, 그냥 `{WIDTH, HEIGHT}`를 사용할 수는 없습니다. 대신에 `glfwGetFramebufferSize`를 사용해서 윈도우의 해상도를 최대 및 최소 이미지 크기와 맞추기 전에 픽셀 단위로 받아와야만 합니다.
 
 ```c++
 #include <cstdint> // Necessary for uint32_t
@@ -364,16 +263,13 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 }
 ```
 
-The `clamp` function is used here to bound the values of `width` and `height` between the allowed minimum and maximum extents that are supported by the implementation.
+여기서 `clamp` 함수는 `width`와 `height` 값을 구현에서 허용 가능한 최대와 최소 크기로 제한하기 위해 사용되었습니다.
 
-## Creating the swap chain
+## 스왑 체인 생성하기
 
-Now that we have all of these helper functions assisting us with the choices we
-have to make at runtime, we finally have all the information that is needed to
-create a working swap chain.
+이제 런타임 선택을 위해 필요한 모든 헬퍼 함수들이 준비되었으니 동작하는 스왑 체인을 만들기 위한 모든 정보를 얻을 수 있습니다.
 
-Create a `createSwapChain` function that starts out with the results of these
-calls and make sure to call it from `initVulkan` after logical device creation.
+`createSwapChain`함수는 이러한 함수 호출의 결과를 받는 함수이고 `initVulkan`에서  논리적 장치 생성 이후에 호출됩니다.
 
 ```c++
 void initVulkan() {
@@ -394,19 +290,19 @@ void createSwapChain() {
 }
 ```
 
-Aside from these properties we also have to decide how many images we would like to have in the swap chain. The implementation specifies the minimum number that it requires to function:
+이러한 속성들 이외에도 스왑 체인에 몇 개의 이미지를 사용할 것인지 결정해야 합니다. 구현을 통해 동작하기 위한 최소 개수를 명시할 수 있습니다:
 
 ```c++
 uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
 ```
 
-However, simply sticking to this minimum means that we may sometimes have to wait on the driver to complete internal operations before we can acquire another image to render to. Therefore it is recommended to request at least one more image than the minimum:
+하지만 이러한 최소 개수를 사용하면, 렌더링을 수행할 또다른 이미지를 얻기위해 드라이버의 내부 연산을 기다리는 결과를 낳을 수 있습니다. 따라서 최소로 요구되는 것보다 하나 더 많은 이미지를 요구하는 것이 권장됩니다.
 
 ```c++
 uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
 ```
 
-We should also make sure to not exceed the maximum number of images while doing this, where `0` is a special value that means that there is no maximum:
+또한 이 과정에서 최대 이미지 개수를 넘지 않도록 해야 하며 여기서 `0`은 최대 개수의 제한이 없다는 것을 의미하는 특별한 값입니다.
 
 ```c++
 if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
@@ -414,8 +310,7 @@ if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSup
 }
 ```
 
-As is tradition with Vulkan objects, creating the swap chain object requires
-filling in a large structure. It starts out very familiarly:
+Vulkan 객체들이 그렇듯이, 스왑 체인 객체를 생성하는 것도 커다란 구조체에 값을 채우는 과정이 필요합니다. 익숙한 코드로 시작합니다:
 
 ```c++
 VkSwapchainCreateInfoKHR createInfo{};
@@ -423,8 +318,7 @@ createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
 createInfo.surface = surface;
 ```
 
-After specifying which surface the swap chain should be tied to, the details of
-the swap chain images are specified:
+어떤 표면에 스왑 체인이 연결되어야 하는지를 명시한 뒤에, 스왑 체인 이미지의 세부 사항들을 명시합니다:
 
 ```c++
 createInfo.minImageCount = imageCount;
@@ -435,15 +329,7 @@ createInfo.imageArrayLayers = 1;
 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
 ```
 
-The `imageArrayLayers` specifies the amount of layers each image consists of.
-This is always `1` unless you are developing a stereoscopic 3D application. The
-`imageUsage` bit field specifies what kind of operations we'll use the images in
-the swap chain for. In this tutorial we're going to render directly to them,
-which means that they're used as color attachment. It is also possible that
-you'll render images to a separate image first to perform operations like
-post-processing. In that case you may use a value like
-`VK_IMAGE_USAGE_TRANSFER_DST_BIT` instead and use a memory operation to transfer
-the rendered image to a swap chain image.
+`imageArrayLayers`는 각 이미지가 구성하는 레이어의 개수를 명시합니다. 여러분이 스테레오 3D(stereoscopic 3D) 응용 프로그램을 개발하는 것이 아니라면 이 값은 항상 `1`입니다. `imageUsage` 비트 필드는 스왑 체인의 이미지에 어떤 연산을 적용할 것인지를 명시합니다. 이 튜토리얼에서 우리는 여기에 직접 렌더링을 수행할 것이므로 color attachment로 사용될 것입니다. 먼저 별도의 이미지에 렌더링한 뒤 후처리(post-processing)을 적용하는 것도 가능합니다. 이러한 경우 `VK_IMAGE_USAGE_TRANSFER_DST_BIT`과 같은 값을 사용하고 렌더링된 이미지를 스왑 체인 이미지로 전송하기 위한 메모리 연산을 사용해야 합니다.
 
 ```c++
 QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
@@ -460,75 +346,45 @@ if (indices.graphicsFamily != indices.presentFamily) {
 }
 ```
 
-Next, we need to specify how to handle swap chain images that will be used
-across multiple queue families. That will be the case in our application if the
-graphics queue family is different from the presentation queue. We'll be drawing
-on the images in the swap chain from the graphics queue and then submitting them
-on the presentation queue. There are two ways to handle images that are
-accessed from multiple queues:
+다음으로 여러 큐 패밀리에 걸쳐 사용될 스왑 체인의 이미지들이 어떻게 처리될 것인지를 명시해 주어야 합니다. 우리 응용 프로그램에서는 그래픽스 큐 패밀리와 표시 큐가 다른 경우가 이에 해당됩니다. 그래픽스 큐로부터 스왑 체인 이미지에 그리기가 수행될 것이고, 이를 표시 큐에 제출할 것입니다. 여러 큐에서 접근 가능한 이미지를 다루는 두 가지 방법이 있습니다:
 
-* `VK_SHARING_MODE_EXCLUSIVE`: An image is owned by one queue family at a time
-and ownership must be explicitly transferred before using it in another queue
-family. This option offers the best performance.
-* `VK_SHARING_MODE_CONCURRENT`: Images can be used across multiple queue
-families without explicit ownership transfers.
+* `VK_SHARING_MODE_EXCLUSIVE`: 하나의 이미지가 한 번에 하나의 큐 패밀리에 의해 소유(own)되고 다른 큐에서 사용되기 전에 명시적으로 전송되어야 합니다. 이 옵션이 성능이 가장 좋습니다.
+* `VK_SHARING_MODE_CONCURRENT`: 소유권의 명시적 이동 없이 이미지가 여러 큐에서 동시에 접근 가능합니다.
 
-If the queue families differ, then we'll be using the concurrent mode in this
-tutorial to avoid having to do the ownership chapters, because these involve
-some concepts that are better explained at a later time. Concurrent mode
-requires you to specify in advance between which queue families ownership will
-be shared using the `queueFamilyIndexCount` and `pQueueFamilyIndices`
-parameters. If the graphics queue family and presentation queue family are the
-same, which will be the case on most hardware, then we should stick to exclusive
-mode, because concurrent mode requires you to specify at least two distinct
-queue families.
+큐 패밀리가 다르다면 이 튜토리얼에서는 소유권 챕터로 넘어가기 전에 동시성 모드(concurrent mode)를 사용할 것인데 동시성에 대한 설명은 몇몇 개념 때문에 나중에 설명하는 것이 낫기 때문입니다. 동시성 모드는 어떤 큐 패밀리의 소유권이 공유될 것인지 `queueFamilyIndexCount`와 `pQueueFamilyIndices` 매개변수를 사용해 미리 명시하게 되어 있습니다. 그래픽스 큐 패밀리와 표시 큐 패밀리가 동일하다면 (대부분의 하드웨어에서는 동일함) 독점(exclusive) 모드를 사용할 것입니다. 동시성 모드에서는 최소한 두 개의 서로다른 큐 패밀리는 명시해야만 하기 떄문입니다.
 
 ```c++
 createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
 ```
 
-We can specify that a certain transform should be applied to images in the swap
-chain if it is supported (`supportedTransforms` in `capabilities`), like a 90
-degree clockwise rotation or horizontal flip. To specify that you do not want
-any transformation, simply specify the current transformation.
+이제 스왑 체인의 이미지에 적용할 특정 변환(transform)을 명시할 수 있습니다. 이를 그 기능이 지원될 때(`capabilities`의 `supportedTransforms`)에 가능한데 예를 들면 시계방향으로 90도 회전이라던가, 수평 뒤집기(flip) 등이 있습니다. 이러한 변환을 적용하지 않을 것이면, 현재 변환(current transformation)으로 명시하면 됩니다.
 
 ```c++
 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
 ```
 
-The `compositeAlpha` field specifies if the alpha channel should be used for
-blending with other windows in the window system. You'll almost always want to
-simply ignore the alpha channel, hence `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`.
+`compositeAlpha` 필드는 윈도우 시스템의 다른 윈도우와의 블렌딩(blending)을 위해 알파 채널이 사용될 것인지를 명시합니다. 거의 개부분의 경우 알파 채널은 무시하는 것이 좋으므로 `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`를 사용합니다.
 
 ```c++
 createInfo.presentMode = presentMode;
 createInfo.clipped = VK_TRUE;
 ```
 
-The `presentMode` member speaks for itself. If the `clipped` member is set to
-`VK_TRUE` then that means that we don't care about the color of pixels that are
-obscured, for example because another window is in front of them. Unless you
-really need to be able to read these pixels back and get predictable results,
-you'll get the best performance by enabling clipping.
+`presentMode`는 이름만 봐도 아실 수 있겠죠. `clipped`가 `VK_TRUE` 면 다려진 픽셀의 색상에 대해서는 신경쓰지 않겠다는 의미인데, 예를 들면 다른 윈도우가 그 픽셀 위에 있는 경우입니다. 뒤쪽의 픽셀 값을 읽어와 의도하는 결과를 얻을 것이 아니라면 그냥 클리핑(clipping)을 활성화 하는게 성능에 좋습니다.
 
 ```c++
 createInfo.oldSwapchain = VK_NULL_HANDLE;
 ```
 
-That leaves one last field, `oldSwapChain`. With Vulkan it's possible that your swap chain becomes invalid or unoptimized while your application is
-running, for example because the window was resized. In that case the swap chain
-actually needs to be recreated from scratch and a reference to the old one must
-be specified in this field. This is a complex topic that we'll learn more about
-in [a future chapter](!en/Drawing_a_triangle/Swap_chain_recreation). For now we'll
-assume that we'll only ever create one swap chain.
+이제 마지막 필드인 `oldSwapChain` 입니다. Vulkan을 사용하면 응용 프로그램이 실행되는 동안 스왑 체인이 사용 불가능해지거나, 최적화되지 않을 수 있습니다. 예를 들어 윈도우가 리사이즈(resize)되는 경우에 그렇습니다. 이러한 경우 스왑 체인이 처음부터 다시 만들어져야만 하고 이전 스왑 체인에 대한 참조가 여기에 명시되어야만 합니다. 이 주제는 복잡하기 때문에 [이후 챕터](!kr/Drawing_a_triangle/Swap_chain_recreation)에서 보다 자세히 배워볼 것입니다. 지금은 그냥 하나의 스왑 체인만 만드는 것으로 가정합시다.
 
-Now add a class member to store the `VkSwapchainKHR` object:
+이제 `VkSwapchainKHR` 객체를 저장할 클래스 멤버를 추가합니다:
 
 ```c++
 VkSwapchainKHR swapChain;
 ```
 
-Creating the swap chain is now as simple as calling `vkCreateSwapchainKHR`:
+이제 스왑 체인을 만드는 것은 단순히 `vkCreateSwapchainKHR`를 호출하는 것으로 간단해졌습니다.
 
 ```c++
 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
@@ -536,9 +392,7 @@ if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS
 }
 ```
 
-The parameters are the logical device, swap chain creation info, optional custom
-allocators and a pointer to the variable to store the handle in. No surprises
-there. It should be cleaned up using `vkDestroySwapchainKHR` before the device:
+매개변수는 논리적 장치, 스왑 체인 생성 정보, 선택적인 사용자 정의 할당자와 핸들을 저장할 변수에 대한 포인터입니다. 새로울 것 없죠. 소멸은 장치 소멸에 앞서 `vkDestroySwapchainKHR`를 사용해 이루어져야 합니다:
 
 ```c++
 void cleanup() {
@@ -547,32 +401,23 @@ void cleanup() {
 }
 ```
 
-Now run the application to ensure that the swap chain is created successfully! If at this point you get an access violation error in `vkCreateSwapchainKHR` or see a message like `Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll`, then see the [FAQ entry](!en/FAQ) about the Steam overlay layer.
+이제 프로그램을 실행해 스왑 체인이 올바로 생성되었는지 확인하세요! `vkCreateSwapchainKHR`에서 접근 위반 오류가 발생하거나 `Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll`와 같은 메시지를 마주치게 되면, [FAQ](!en/FAQ)에서 Steam 오버레이 레이어 내용을 살펴보세요.
 
-Try removing the `createInfo.imageExtent = extent;` line with validation layers
-enabled. You'll see that one of the validation layers immediately catches the
-mistake and a helpful message is printed:
+검증 레이어가 활성화 된 상태에서 `createInfo.imageExtent = extent;` 명령문을 지워 보세요. 검증 레이어가 바로 실수를 탐지하고 도움이 되는 메시지를 출력하는 것을 볼 수 있을 겁니다.
 
 ![](/images/swap_chain_validation_layer.png)
 
-## Retrieving the swap chain images
+## 스왑 체인 이미지의 획득(Retrieving)
 
-The swap chain has been created now, so all that remains is retrieving the
-handles of the `VkImage`s in it. We'll reference these during rendering
-operations in later chapters. Add a class member to store the handles:
+이제 스왑 체인이 생성되었으니, 그 안의 `VkImage`들에 대한 핸들을 획득하는 과정이 남았습니다. 나중 챕터에서 렌더링 연산을 위해 이를 사용하게 됩니다. 핸들을 저장하기 위한 클래스 멤버를 추가합니다:
 
 ```c++
 std::vector<VkImage> swapChainImages;
 ```
 
-The images were created by the implementation for the swap chain and they will
-be automatically cleaned up once the swap chain has been destroyed, therefore we
-don't need to add any cleanup code.
+스왑 체인 구현에 의해 이미지들이 만들어지고 스왑 체인이 소멸될 때 자동으로 정리되므로 `cleanup` 코드에 뭔가를 추가할 필요는 없습니다.
 
-I'm adding the code to retrieve the handles to the end of the `createSwapChain`
-function, right after the `vkCreateSwapchainKHR` call. Retrieving them is very
-similar to the other times where we retrieved an array of objects from Vulkan. Remember that we only specified a minimum number of images in the swap chain, so the implementation is allowed to create a swap chain with more. That's why we'll first query the final number of images with `vkGetSwapchainImagesKHR`, then resize the container and finally call it again
-to retrieve the handles.
+`createSwapChain` 함수의 마지막 부분 `vkCreateSwapchainKHR` 뒤에 획득을 위한 코드를 추가할 것입니다. 획득 과정은 Vulkan으로부터 객체의 배열을 획득하는 일반적인 과정입니다. 기억하셔야 할 것은 우리는 스왑 체인의 이미지 최소 개수만 명시하였으므로 구현에 따라 더 많은 이미지가 생성되었을 수 있습니다. 그래서 `vkGetSwapchainImagesKHR`로 최종 생성된 이미지 개수를 먼저 얻고 컨테이너(container) 크기를 조정한 뒤 핸들을 얻어오도록 구현하였습니다.
 
 ```c++
 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
@@ -580,8 +425,7 @@ swapChainImages.resize(imageCount);
 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
 ```
 
-One last thing, store the format and extent we've chosen for the swap chain
-images in member variables. We'll need them in future chapters.
+마지막으로 멤버 변수에 스왑 체인 이미지를 위해 우리가 명시한 포맷과 크기를 저장합니다. 나중 챕터에서 이 값들을 사용할 것입니다.
 
 ```c++
 VkSwapchainKHR swapChain;
@@ -595,9 +439,6 @@ swapChainImageFormat = surfaceFormat.format;
 swapChainExtent = extent;
 ```
 
-We now have a set of images that can be drawn onto and can be presented to the
-window. The next chapter will begin to cover how we can set up the images as
-render targets and then we start looking into the actual graphics pipeline and
-drawing commands!
+이제 그림을 그리고 화면에 표시될 이미지가 준비되었습니다. 다음 챕터에서부터는 이미지를 렌더링 타겟(render target)으로 설정하는 법, 실제 그래픽스 파이프라인과 그리지 명령에 대해 살펴볼 것입니다!
 
 [C++ code](/code/06_swap_chain_creation.cpp)

From 7455fa4ebff8b8910fa01bac5b845add80d7bd0b Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sun, 15 Oct 2023 20:55:58 +0900
Subject: [PATCH 13/47] kr translate 03-01-02 image view

---
 .../01_Presentation/02_Image_views.md         | 51 +++++--------------
 1 file changed, 14 insertions(+), 37 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md b/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
index 5988468a..fa2db5ba 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
@@ -1,21 +1,14 @@
-To use any `VkImage`, including those in the swap chain, in the render pipeline
-we have to create a `VkImageView` object. An image view is quite literally a
-view into an image. It describes how to access the image and which part of the
-image to access, for example if it should be treated as a 2D texture depth
-texture without any mipmapping levels.
+스왑 체인에 포함된 `VkImage`를 사용하기 위해서는 렌더링 파이프라인에서 `VkImageView` 객체를 생성해야 합니다. 이미지 뷰(image view)는 말 그대로 이미지에 대한 뷰 입니다. 이를 통해 이미지에 어떻게 접근하는지와 이미지의 어느 부분에 접근할 것인지를 명시하는데, 예를 들어 2D 텍스처로 취급될 것인지, 밉맵(mipmap) 수준이 없는 깊이 텍스차(depth texture)로 취급될 것인지와 같은 사항입니다.
 
-In this chapter we'll write a `createImageViews` function that creates a basic
-image view for every image in the swap chain so that we can use them as color
-targets later on.
+이 장에서 우리는 `createImageViews` 함수를 작성하여 스왑 체인에 있는 모든 이미지에 대한 이미지 뷰를 생성하고 이는 나중에 컬러 타겟으로 사용될 것입니다.
 
-First add a class member to store the image views in:
+먼저 이미지 뷰를 저장할 클래스 멤버를 추가합니다:
 
 ```c++
 std::vector<VkImageView> swapChainImageViews;
 ```
 
-Create the `createImageViews` function and call it right after swap chain
-creation.
+`createImageViews` 함수를 만들고 스왑 체인 생성 후에 호출합니다.
 
 ```c++
 void initVulkan() {
@@ -33,8 +26,7 @@ void createImageViews() {
 }
 ```
 
-The first thing we need to do is resize the list to fit all of the image views
-we'll be creating:
+우선적으로 해애 할 일은 리스트의 크기를 조정해 우리가 생성할 이미지 뷰가 모두 들어갈 수 있도록 하는 것입니다.
 
 ```c++
 void createImageViews() {
@@ -43,7 +35,7 @@ void createImageViews() {
 }
 ```
 
-Next, set up the loop that iterates over all of the swap chain images.
+다음으로 모든 스왑 체인 이미지에 대한 반복문을 만듭니다.
 
 ```c++
 for (size_t i = 0; i < swapChainImages.size(); i++) {
@@ -51,8 +43,7 @@ for (size_t i = 0; i < swapChainImages.size(); i++) {
 }
 ```
 
-The parameters for image view creation are specified in a
-`VkImageViewCreateInfo` structure. The first few parameters are straightforward.
+이미지 뷰 생성에 대한 매개변수는 `VkImageViewCreateInfo` 구조체에 명시됩니다. 처음 몇 개의 매개변수는 직관적입니다.
 
 ```c++
 VkImageViewCreateInfo createInfo{};
@@ -60,19 +51,14 @@ createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
 createInfo.image = swapChainImages[i];
 ```
 
-The `viewType` and `format` fields specify how the image data should be
-interpreted. The `viewType` parameter allows you to treat images as 1D textures,
-2D textures, 3D textures and cube maps.
+`viewType`과 `format` 필드는 이미지 데이터가 어떻게 해석되어야 할지를 명시합니다. `viewType` 매개변수는 이미지를 1차원, 2차원, 3차원 혹은 큐브 맵(cube map)으로 취급할 수 있도록 합니다.
 
 ```c++
 createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
 createInfo.format = swapChainImageFormat;
 ```
 
-The `components` field allows you to swizzle the color channels around. For
-example, you can map all of the channels to the red channel for a monochrome
-texture. You can also map constant values of `0` and `1` to a channel. In our
-case we'll stick to the default mapping.
+`components` 필드는 컬러 채널을 뒤섞을 수 있도록 합니다. 예를 들어 흑백(monochrome) 텍스처를 위해서는 모든 채널을 빨간색 채널로 맵핑할 수 있습니다. 또한 `0`이나 `1`과 같은 상수를 채널에 맵핑할 수도 있습니다. 우리의 경우 기본(default) 맵핑을 사용할 것입니다.
 
 ```c++
 createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
@@ -81,9 +67,7 @@ createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
 createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
 ```
 
-The `subresourceRange` field describes what the image's purpose is and which
-part of the image should be accessed. Our images will be used as color targets
-without any mipmapping levels or multiple layers.
+`subresourceRange` 필드는 이미지의 목적이 무엇인지와 어떤 부분이 접근 가능할지를 기술합니다. 우리 이미지는 컬러 타겟이고 밉맵핑이나 다중 레이어는 사용하지 않습니다.
 
 ```c++
 createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
@@ -93,12 +77,9 @@ createInfo.subresourceRange.baseArrayLayer = 0;
 createInfo.subresourceRange.layerCount = 1;
 ```
 
-If you were working on a stereographic 3D application, then you would create a
-swap chain with multiple layers. You could then create multiple image views for
-each image representing the views for the left and right eyes by accessing
-different layers.
+스테레오 3D 응용 프로그램을 만든다면, 스왑 체인을 다중 레이어로 만들 것입니다. 그런 경우 각 이미지에 대한 다중 이미지 뷰를 만들 수 있고, 이는 왼쪽과 오른쪽 눈에 대한 이미지 표현을 서로 다른 레이어를 통해 접근할 수 있도록 합니다.
 
-Creating the image view is now a matter of calling `vkCreateImageView`:
+이제 이미지 뷰를 만드는 것은 `vkCreateImageView`를 호출하면 됩니다.
 
 ```c++
 if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
@@ -106,8 +87,7 @@ if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) !=
 }
 ```
 
-Unlike images, the image views were explicitly created by us, so we need to add
-a similar loop to destroy them again at the end of the program:
+이미지와는 다르게 이미지 뷰는 우리가 명시적으로 만든 것이기 때문에 소멸을 위해서는 프로그램 종료 시점에 반복문을 추가해야 합니다.
 
 ```c++
 void cleanup() {
@@ -119,9 +99,6 @@ void cleanup() {
 }
 ```
 
-An image view is sufficient to start using an image as a texture, but it's not
-quite ready to be used as a render target just yet. That requires one more step
-of indirection, known as a framebuffer. But first we'll have to set up the
-graphics pipeline.
+이미지를 텍스처로 사용하기 위한 목적으로는 이미지 뷰를 만드는 것으로 충분하지만 렌더 타겟으로 만들기 위해서는 아직 할 일이 남아 있습니다. 이를 위해서는 프레임버퍼(framebuffer)와 관련된 추가적인 작업이 필요합니다. 하지만 우선 그래픽스 파이프라인부터 설정하도록 하겠습니다.
 
 [C++ code](/code/07_image_views.cpp)

From 9fa6af782f9f0c458540600fee5ac7ff061487c0 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 16 Oct 2023 21:40:10 +0900
Subject: [PATCH 14/47] kr translate 03-02-00 graphics pipeline, intro

---
 .../00_Introduction.md                        | 101 +++++-------------
 1 file changed, 26 insertions(+), 75 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
index 9ee7f739..4bff8d68 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
@@ -1,81 +1,32 @@
-Over the course of the next few chapters we'll be setting up a graphics pipeline
-that is configured to draw our first triangle. The graphics pipeline is the
-sequence of operations that take the vertices and textures of your meshes all
-the way to the pixels in the render targets. A simplified overview is displayed
-below:
+다음 몇 챕터에서 우리는 첫 번째 삼각형을 그리기 위한 그래픽스 파이프라인을 설정할 것입니다. 그래픽스 파이프라인이란 메쉬(mesh)의 정점(vertex)들과 텍스처들을 받아서 렌더 타겟의 픽셀로 출력하기 위한 일련의 연산들을 말합니다. 간단한 개요가 아래 그림에 표현되어 있습니다:
 
 ![](/images/vulkan_simplified_pipeline.svg)
 
-The *input assembler* collects the raw vertex data from the buffers you specify
-and may also use an index buffer to repeat certain elements without having to
-duplicate the vertex data itself.
-
-The *vertex shader* is run for every vertex and generally applies
-transformations to turn vertex positions from model space to screen space. It
-also passes per-vertex data down the pipeline.
-
-The *tessellation shaders* allow you to subdivide geometry based on certain
-rules to increase the mesh quality. This is often used to make surfaces like
-brick walls and staircases look less flat when they are nearby.
-
-The *geometry shader* is run on every primitive (triangle, line, point) and can
-discard it or output more primitives than came in. This is similar to the
-tessellation shader, but much more flexible. However, it is not used much in
-today's applications because the performance is not that good on most graphics
-cards except for Intel's integrated GPUs.
-
-The *rasterization* stage discretizes the primitives into *fragments*. These are
-the pixel elements that they fill on the framebuffer. Any fragments that fall
-outside the screen are discarded and the attributes outputted by the vertex
-shader are interpolated across the fragments, as shown in the figure. Usually
-the fragments that are behind other primitive fragments are also discarded here
-because of depth testing.
-
-The *fragment shader* is invoked for every fragment that survives and determines
-which framebuffer(s) the fragments are written to and with which color and depth
-values. It can do this using the interpolated data from the vertex shader, which
-can include things like texture coordinates and normals for lighting.
-
-The *color blending* stage applies operations to mix different fragments that
-map to the same pixel in the framebuffer. Fragments can simply overwrite each
-other, add up or be mixed based upon transparency.
-
-Stages with a green color are known as *fixed-function* stages. These stages
-allow you to tweak their operations using parameters, but the way they work is
-predefined.
-
-Stages with an orange color on the other hand are `programmable`, which means
-that you can upload your own code to the graphics card to apply exactly the
-operations you want. This allows you to use fragment shaders, for example, to
-implement anything from texturing and lighting to ray tracers. These programs
-run on many GPU cores simultaneously to process many objects, like vertices and
-fragments in parallel.
-
-If you've used older APIs like OpenGL and Direct3D before, then you'll be used
-to being able to change any pipeline settings at will with calls like
-`glBlendFunc` and `OMSetBlendState`. The graphics pipeline in Vulkan is almost
-completely immutable, so you must recreate the pipeline from scratch if you want
-to change shaders, bind different framebuffers or change the blend function. The
-disadvantage is that you'll have to create a number of pipelines that represent
-all of the different combinations of states you want to use in your rendering
-operations. However, because all of the operations you'll be doing in the
-pipeline are known in advance, the driver can optimize for it much better.
-
-Some of the programmable stages are optional based on what you intend to do. For
-example, the tessellation and geometry stages can be disabled if you are just
-drawing simple geometry. If you are only interested in depth values then you can
-disable the fragment shader stage, which is useful for [shadow map](https://en.wikipedia.org/wiki/Shadow_mapping)
-generation.
-
-In the next chapter we'll first create the two programmable stages required to
-put a triangle onto the screen: the vertex shader and fragment shader. The
-fixed-function configuration like blending mode, viewport, rasterization will be
-set up in the chapter after that. The final part of setting up the graphics
-pipeline in Vulkan involves the specification of input and output framebuffers.
-
-Create a `createGraphicsPipeline` function that is called right after
-`createImageViews` in `initVulkan`. We'll work on this function throughout the
-following chapters.
+*입력 조립(input assembler)*은 명시한 버퍼로부터 정점 데이터를 수집합니다. 또한 인덱스 버퍼를 사용하여 특정 요소들을 정점 데이터의 중복 없이 반복 사용할 수 있도록 할 수도 있습니다.
+
+*정점 셰이더(vertex shader)*는 각 정점에 대해 실행되며, 일반적으로 정점의 위치를 모델 공간으로부터 스크린 공간으로 변환하는 작업을 합니다. 또한 정점별(per-vertex) 데이터를 파이프라인의 다음 단계로 전달합니다.
+
+*테셀레이션 셰이더(tessellation shaders)*는 특정한 규칙에 따라 기하(geometry)를 분할하여 메쉬의 품질을 향상시킬 수 있는 과정입니다. 이는 벽돌로 된 벽이라던가, 계단과 같은 표면에 적용되어 가까이서 봤을 때 덜 평평하게(flat) 보이도록 하는 데 자주 사용됩니다.
+
+*기하 셰이더(geometry shader)*는 모든 프리미티브(삼각형, 선, 점)에 대해 실행되며 그 정보를 탈락(discard)시키거나 입력된 것보다 더 많은 프리미티브를 생성할 수 있습니다. 테셀레이션 셰이더와 비슷하지만 훨씬 유연합니다. 하지만 요즘 응용 프로그램에서는 자주 사용되지 않는데 인텔의 내장 그래픽 카드를 제외하고 대부분 그래픽 카드에서는 성능이 그리 좋지 않기 때문입니다.
+
+_래스터화(rasterization)_ 단계는 프리미티브를 *프래그먼트*로 이산화하는 단계입니다. 프래그먼트는 픽셀 요로소 프레임버퍼에 채워집니다. 화면 밖에 놓여 있는 프래그먼트는 버려지고 정점 셰이더의 출력 어트리뷰트(attribute)는 프래그먼트들에 걸쳐 그림에 보이는 것과 같이 보간됩니다. 다른 프리미티브 프래그먼트 뒤에 놓여있는 프래그먼트도 깊이 테스트(depth test)에 의해 버려집니다.
+
+*프래그먼트 셰이더(fragment shader)*는 모든 살아남은 프래그먼트에 대해 실행되며 어떤 프레임버퍼에 프래그먼트가 쓰여질지, 어떤 색상과 깊이값이 쓰여질지를 결정합니다. 이는 정점 셰이더에서 보간된 데이터를 바탕으로 이루어지며 데이터는 텍스처 좌표계와 라이팅(lighting)을 위한 법선(normal) 정보 같은 것들이 포함됩니다.
+
+_컬러 블렌딩(color blending)_ 단계는 프레임버퍼의 같은 픽셀에 맵핑되는 다른 프래드먼트들을 섞는 연산을 적용합니다. 프래그먼트 값들이 다른 값들을 대체할 수도 있고, 투명도에 따라 더해지거나 섞일 수 있습니다.
+
+녹색으로 표현된 단계는 _고정 함수(fixed-function)_ 단계로 알려져 있습니다. 이 단계들은 매개변수를 사용해 연산을 약간 변경할 수 있지만, 동작 방식 자체는 미리 정의되어 있습니다.
+
+주황색으로 표시된 단계는 `programmable`한 단계인데, 여러분이 작성한 코드를 그래픽 카드에 업로드할 수 있어서 원하는 대로 연산을 할 수 있다는 뜻입니다. 이렇게 되면 예를 들어 프래그먼트 셰이더에서 텍스처링이라던지 레이 트레이싱(ray tracing)을 위한 라이팅 등을 구현할 수 있게 됩니다. 이러한 프로그램은 여러 객체(예를들어 정점 또는 프래그먼트)들을 처리하기 위해 여러 GPU 코어에서 동시에 병렬적으로 실행됩니다.
+
+OpenGL이나 Direct3D같은 예전 API를 사용해 봤다면, 파이프라인의 설정을 바꾸는 `glBlendFunc`와 `OMSetBlendState` 같은 함수의 사용에 익숙할 것입니다. Vulkan의 그래픽스 파이프라인은 거의 완전히 불변적(immutable)이라서, 셰이더를 바꾸거나 다른 프레임버퍼를 바인딩(bind)한다거나 블렌딩 함수를 바꾸거나 할 떄에는 파이프라인을 처음부터 다시 만들어야 합니다. 이에 대한 단점으로는 우리가 사용하고자 하는 렌더링 연산을 위한 다양한 상태를 표현하는 모든 조합에 대해 파이프라인들을 만들어야 한다는 것입니다. 하지만, 파이프라인에서 수행하는 모든 연산에 대해 미리 말게되기 때문에, 드라이브가 훨씬 최적화를 잘 할 수 있는 장점도 있습니다.
+
+여러분이 하려는 작업에 따라서 몇 개의 프로그램가능한(programmable) 단계는 선택적으로 사용해도 됩니다. 예를 들어 테셀레이션과 기하 단계는 간단한 형상을 그릴 떄에는 활성화하지 않아도 됩니다. 깊이 값에만 관심이 있다면 프래그먼트 셰이더 단계를 비활성화 할수도 있는데 [그림자 맵](https://en.wikipedia.org/wiki/Shadow_mapping) 생성을 할 때에는 유용할 것입니다.
+
+다음 장에서는 삼각형을 화면에 표시하기 위한 두 개의 프로그램 가능한 단계(정점 셰이더와 프래그먼트 셰이더)를 만들어 볼 것입니다. 고정된 함수 구성인 블렌딩 모드, 뷰포트(viewport), 래스터화 같은 단계는 그 다음 챕터에서 설정할 것입니다. Vulkan에서의 그래픽스 파이프라인을 위한 마지막 설정 단계는 입력과 출력 프레임버퍼와 관련되어 있습니다.
+
+`initVulkan`의 `createImageViews` 바로 뒤에 호출할 `createGraphicsPipeline` 함수를 만들겠습니다. 이후 챕터에서 이 함수를 만들어 나갈 것입니다.
 
 ```c++
 void initVulkan() {

From 5b79abf6d53526f991106c5cd3140d926b1b31c6 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 17 Oct 2023 20:28:08 +0900
Subject: [PATCH 15/47] kr translate 03-02-01 shader modules

---
 .../01_Shader_modules.md                      | 279 +++++-------------
 1 file changed, 71 insertions(+), 208 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
index 20aee6b5..b10f3582 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
@@ -1,87 +1,29 @@
-Unlike earlier APIs, shader code in Vulkan has to be specified in a bytecode
-format as opposed to human-readable syntax like [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)
-and [HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language). This
-bytecode format is called [SPIR-V](https://www.khronos.org/spir) and is designed
-to be used with both Vulkan and OpenCL (both Khronos APIs). It is a format that
-can be used to write graphics and compute shaders, but we will focus on shaders
-used in Vulkan's graphics pipelines in this tutorial.
-
-The advantage of using a bytecode format is that the compilers written by GPU
-vendors to turn shader code into native code are significantly less complex. The
-past has shown that with human-readable syntax like GLSL, some GPU vendors were
-rather flexible with their interpretation of the standard. If you happen to
-write non-trivial shaders with a GPU from one of these vendors, then you'd risk
-other vendor's drivers rejecting your code due to syntax errors, or worse, your
-shader running differently because of compiler bugs. With a straightforward
-bytecode format like SPIR-V that will hopefully be avoided.
-
-However, that does not mean that we need to write this bytecode by hand. Khronos
-has released their own vendor-independent compiler that compiles GLSL to SPIR-V.
-This compiler is designed to verify that your shader code is fully standards
-compliant and produces one SPIR-V binary that you can ship with your program.
-You can also include this compiler as a library to produce SPIR-V at runtime,
-but we won't be doing that in this tutorial. Although we can use this compiler directly via `glslangValidator.exe`, we will be using `glslc.exe` by Google instead. The advantage of `glslc` is that it uses the same parameter format as well-known compilers like GCC and Clang and includes some extra functionality like *includes*. Both of them are already included in the Vulkan SDK, so you don't need to download anything extra.
-
-GLSL is a shading language with a C-style syntax. Programs written in it have a
-`main` function that is invoked for every object. Instead of using parameters
-for input and a return value as output, GLSL uses global variables to handle
-input and output. The language includes many features to aid in graphics
-programming, like built-in vector and matrix primitives. Functions for
-operations like cross products, matrix-vector products and reflections around a
-vector are included. The vector type is called `vec` with a number indicating
-the amount of elements. For example, a 3D position would be stored in a `vec3`.
-It is possible to access single components through members like `.x`, but it's
-also possible to create a new vector from multiple components at the same time.
-For example, the expression `vec3(1.0, 2.0, 3.0).xy` would result in `vec2`. The
-constructors of vectors can also take combinations of vector objects and scalar
-values. For example, a `vec3` can be constructed with
-`vec3(vec2(1.0, 2.0), 3.0)`.
-
-As the previous chapter mentioned, we need to write a vertex shader and a
-fragment shader to get a triangle on the screen. The next two sections will
-cover the GLSL code of each of those and after that I'll show you how to produce
-two SPIR-V binaries and load them into the program.
-
-## Vertex shader
-
-The vertex shader processes each incoming vertex. It takes its attributes, like
-world position, color, normal and texture coordinates as input. The output is
-the final position in clip coordinates and the attributes that need to be passed
-on to the fragment shader, like color and texture coordinates. These values will
-then be interpolated over the fragments by the rasterizer to produce a smooth
-gradient.
-
-A *clip coordinate* is a four dimensional vector from the vertex shader that is
-subsequently turned into a *normalized device coordinate* by dividing the whole
-vector by its last component. These normalized device coordinates are
-[homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates)
-that map the framebuffer to a [-1, 1] by [-1, 1] coordinate system that looks
-like the following:
+기존 API와는 다르게, Vulkan의 셰이더 코드는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)이나 [HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language)과 같은 사람이 읽을 수 있는(human-readable) 문법이 아닌 바이트코드(bytecode) 포맷으로 명시되어야 합니다. 이 바이트코드 포맷은 [SPIR-V](https://www.khronos.org/spir)라 불리며 Vulkan과 OpenCL에서의 사용을 위해 설계되었습니다(둘 다 크로노스(Khronos)의 API). 이는 사용해 그래픽스 및 계산 셰이더 작성이 가능하지만 이 튜토리얼에서는 Vulkan의 그래픽스 파이프라인에 사용되는 셰이더에 포커스를 맞추도록 하겠습니다.
 
-![](/images/normalized_device_coordinates.svg)
+바이트코드를 사용함으로써 얻을 수 있는 장점은 GPU 벤더가 작성하는, 셰이더 코드를 네이티브 코드로 변환하는 컴파일러가 훨씬 간단해진다는 것입니다. 과거의 사례를 봤을 때 사람이 읽을 수 있는 GLSL과 같은 문법에서, 어떤 GPU 벤더들은 표준을 유연하게 해석하는 경우가 있었습니다. 이러한 벤더의 GPU에서 여러분이 일반적이지 않은(non-trivial) 셰이더를 작성하는 경우에, 다른 벤더의 드라이버에서는 여러분의 코드가 문법 오류로 판단된다던지, 더 안좋은 상황에서는 다른 방식으로 동작한다던지 하는 문제가 있을 수 있습니다. SPIR-V와 같은 직관적인 바이트코드 포맷을 사용하면 이러한 문제가 해결될 것으로 바라고 있습니다.
+
+그렇다고 우리가 손으로 바이트코드를 작성해야 한다는 뜻은 아닙니다. 크로노스 자체적으로 GLSL을 SPIR-V로 변환하는 벤더 독립적인 컴파일러를 릴리즈하였습니다. 이 컴파일러는 려어분의 셰이더 코드가 표준에 맞는지를 검증하고 프로그램에 사용할 수 있는 SPIR-V 바이너리를 생성합니다. 또한 이 컴파일러를 라이브러리의 형태로 추가하여 런타임에 SPIR-V를 생성하도록 할 수도 있지만, 이 튜토리얼에서 이 기능을 사용하지는 않을 것입니다. 컴파일러는 `glslangValidator.exe`를 통해 직접 사용할수도 있지만 우리는 구글에서 만든 `glslc.exe`를 사용할 것입니다. `glslc`의 장점은 GCC와 Clang과 같은 유명한 컴파일러와 같은 매개변수 포맷을 사용한다는 점, 그리고 *include*와 같은 부가 기능을 제공하는 점입니다. 둘 다 Vulkan SDK에 포함되어 있으므로 추가적으로 다운로드 할 필요는 없습니다.
+
+GLSL은 C 스타일 문법을 가진 셰이더 언어입니다. GLSL로 작성된 프로그램은 `main`함수가 있어서 모든 객체에 대해 실행됩니다. 입력에 매개변수를 사용하고 출력에 반환값을 사용하는 대신, GLSL은 입력과 출력을 관리하는 전역 변수를 가지고 있습니다. 이 언어는 그래픽스 프로그램을 위한 다양한 기능을 포함하고 있는데 내장 벡터(vector)와 행렬(matrix) 타입이 그 예시입니다. 외적(cross product)이나 행렬-벡터 곱, 벡터를 기준으로 한 반사(reflection) 연산을 위한 함수 또한 포함되어 있습니다. 벡터 타입은 `vec`이라고 물리며 요소의 개수를 명시하는 숫자가 뒤에 붙습니다. 예를 들어 3차원 위치는 `vec3`에 저장됩니다. 개별 요소에 대한 접근은 멤버 접근 연산자처러 `.x`로 접근 가능하지만 여러 요소를 갖는 벡터를 새로 만들수도 있습니다. 예를 들어 `vec3(1.0, 2.0, 3.0).xy`는 결과적으로 `vec2` 입니다. 벡터의 생성자(constructor)는 벡터 객체와 스칼라(scalar)값의 조합을 받을 수 있습니다. 예를 들어 `vec3`가 `vec3(vec2(1.0, 2.0), 3.0)`를 통해 만들어질 수 있습니다.
+
+이전 챕터에서 이야기한 것처럼 삼각형을 화면에 그리기 위해 우리는 정점 셰이더와 프래그먼트 셰이더를 작성해야 합니다. 다음 두 섹션에서 각각의 GLSL 코드를 설명할 것이고 그 이후에는 SPIR-V 바이너리를 만드는 방법과 이를 프로그램에 로드(load)하는 법을 보여드리겠습니다.
 
-You should already be familiar with these if you have dabbled in computer
-graphics before. If you have used OpenGL before, then you'll notice that the
-sign of the Y coordinates is now flipped. The Z coordinate now uses the same
-range as it does in Direct3D, from 0 to 1.
+## 정점 셰이더
+
+정점 셰이더는 각 입력 정점을 처리합니다. 정점 셰이더는 월드 공간 좌표, 색상, 법선과 텍스처 좌표같은 값을 입력 데이터를 어트리뷰트로 받습니다. 출력은 클립 좌표(clip coordinate) 위치와 프래그먼트 셰이더로 전달할 색상과 텍스처 좌표와 같은 어트리뷰트 들입니다. 이 값들은 래스터화 단계에서 프래그먼트에 걸쳐 연속적인 값을 갖도록(smooth gradient) 보간됩니다.
+
+*클립 좌표*는 정점 셰이더에서 도출된 4차원 벡터로 벡터를 마지막 구성요소의 값으로 나눔으로써 *정규화된 장치 좌표(normalized device coordinate)*로 변환됩니다. 정규화된 장치 좌표계는 [동차 좌표(homogeneous coordinates)](https://en.wikipedia.org/wiki/Homogeneous_coordinates)로, 아래 그림과 같이 프레임버퍼와 맵핑되는 [-1, 1]x[-1, 1] 좌표계입니다:
+
+![](/images/normalized_device_coordinates.svg)
 
-For our first triangle we won't be applying any transformations, we'll just
-specify the positions of the three vertices directly as normalized device
-coordinates to create the following shape:
+컴퓨터 그래픽스를 좀 보셨다면 이런 것들이 익숙하실 겁니다. OpenGL을 사용해보셨다면 Y좌표가 뒤집혀 있는 것을 눈치채실 겁니다. Z좌표도 이제 Direct3D와 동일하게 0에서 1 사이의 값을 사용합니다.
 
+첫 번째 삼각형 그릴 때, 우리는 아무 변환도 적용하지 않을 것입니다. 그냥 3개 정점의 위치를 정규화된 장치 좌표에서 직접 명시하여 아래와 같은 모양을 만들 것입니다:
 ![](/images/triangle_coordinates.svg)
 
-We can directly output normalized device coordinates by outputting them as clip
-coordinates from the vertex shader with the last component set to `1`. That way
-the division to transform clip coordinates to normalized device coordinates will
-not change anything.
+정점 셰이더의 클립 좌표에서 마지막 요소를 `1`로 설정하여 정규화된 장치 좌표를 바로 출력되도록 할 수 있습니다. 이렇게 하면 클립 좌표를 정규화된 장치 좌표로 변환해도 아무런 값의 변화가 없을것입니다.
 
-Normally these coordinates would be stored in a vertex buffer, but creating a
-vertex buffer in Vulkan and filling it with data is not trivial. Therefore I've
-decided to postpone that until after we've had the satisfaction of seeing a
-triangle pop up on the screen. We're going to do something a little unorthodox
-in the meanwhile: include the coordinates directly inside the vertex shader. The
-code looks like this:
+일반적으로 이러한 좌표는 정점 버퍼에서 저장되겠지만 Vulkan에서 정점 버퍼를 생성하고 값을 집어넣는 것은 쉽지 않습니다. 그래서 이러한 작업은 화면에 삼각형을 띄우는 만족스러운 결과 이후로 미루도록 하겠습니다. 정석적인 방법은 아니지만 정점 셰이더에 좌표값을 직접 추가하겠습니다. 코드는 아래와 같습니다:
 
 ```glsl
 #version 450
@@ -97,21 +39,11 @@ void main() {
 }
 ```
 
-The `main` function is invoked for every vertex. The built-in `gl_VertexIndex`
-variable contains the index of the current vertex. This is usually an index into
-the vertex buffer, but in our case it will be an index into a hardcoded array
-of vertex data. The position of each vertex is accessed from the constant array
-in the shader and combined with dummy `z` and `w` components to produce a
-position in clip coordinates. The built-in variable `gl_Position` functions as
-the output.
+`main` 함수는 모든 정점에 대해 호출됩니다. `gl_VertexIndex` 내장 변수가 현재 정점의 인덱스를 가지고 있습니다. 이는 보통 정점 버퍼의 인덱스이지만 우리의 경우 하드코딩된 정점 데이터의 인덱스를 의미합니다. 각 정점의 위치는 셰이더에 있는 상수 배열로부터 얻어지고, `z`와 `w`값이 클립 좌표값을 위해 합쳐집니다. `gl_Position` 내장 변수가 출력처럼 활용됩니다.
 
-## Fragment shader
+## 프래그먼트 셰이더
 
-The triangle that is formed by the positions from the vertex shader fills an
-area on the screen with fragments. The fragment shader is invoked on these
-fragments to produce a color and depth for the framebuffer (or framebuffers). A
-simple fragment shader that outputs the color red for the entire triangle looks
-like this:
+정점 셰이더의 위치들로 구성된 삼각형은 화면상의 일정 영역을 프래그먼트로 채우게 됩니다. 프래그먼트 셰이더는 이 프래그먼트들에 대해 실행되어 프레임버퍼(들)의 색상과 깊이 값을 생성합니다. 전체 삼각형에 대해 빨간색을 출력하는 간단한 프래그먼트 셰이더는 아래와 같습니다:
 
 ```glsl
 #version 450
@@ -123,26 +55,15 @@ void main() {
 }
 ```
 
-The `main` function is called for every fragment just like the vertex shader
-`main` function is called for every vertex. Colors in GLSL are 4-component
-vectors with the R, G, B and alpha channels within the [0, 1] range. Unlike
-`gl_Position` in the vertex shader, there is no built-in variable to output a
-color for the current fragment. You have to specify your own output variable for
-each framebuffer where the `layout(location = 0)` modifier specifies the index
-of the framebuffer. The color red is written to this `outColor` variable that is
-linked to the first (and only) framebuffer at index `0`.
+정점 셰이더가 모든 정점에 대해 `main` 함수를 호출하는 것처럼, 모든 프래그먼트에 대해 `main`함수가 호출됩니다. GLSL에서 색상은 [0, 1] 범위의 R,G,B와 알파 채널의 4차원 벡터로 표현됩니다. 정점 셰이더의 `gl_Position`과는 다르게, 현재 프래그먼트 출력을 위한 내장 변수는 없습니다. 각 프레임버퍼를 위한 출력 변수는 스스로 명시해야 하며 `layout(location = 0)` 수식어가 프레임버퍼의 인덱스를 명시합니다. 빨간색이 이러한 `outColor` 변수에 쓰여졌고, 이는 첫 번째(그리고 유일한) `0`번 인덱스 프레임버퍼와 연결되어 있습니다.
 
-## Per-vertex colors
+## 정점별 색상
 
-Making the entire triangle red is not very interesting, wouldn't something like
-the following look a lot nicer?
+삼각형 전체를 빨간색으로 만드는것 재미가 없네요. 아래와 같이 그린다면 훨씬 재미있지 않을까요?
 
 ![](/images/triangle_coordinates_colors.png)
 
-We have to make a couple of changes to both shaders to accomplish this. First
-off, we need to specify a distinct color for each of the three vertices. The
-vertex shader should now include an array with colors just like it does for
-positions:
+이를 위해 두 셰이더 모두에 약간의 수정을 하겠습니다. 먼저 세 개의 정점에 각각 다른 색상을 명시해 주어야 합니다. 이제 정점 셰이더는 위치와 함께 색상을 위한 배열을 가집니다:
 
 ```glsl
 vec3 colors[3] = vec3[](
@@ -152,9 +73,7 @@ vec3 colors[3] = vec3[](
 );
 ```
 
-Now we just need to pass these per-vertex colors to the fragment shader so it
-can output their interpolated values to the framebuffer. Add an output for color
-to the vertex shader and write to it in the `main` function:
+이제 프래그먼트 셰이더에 정점별 색상을 전달해줘서 프레임버퍼에 보간된 색상을 출력하게 하면 됩니다. 정점 셰이더에 색상 출력을 추가하고 `main`함수에서 여기에 값을 쓰면 됩니다:
 
 ```glsl
 layout(location = 0) out vec3 fragColor;
@@ -165,7 +84,7 @@ void main() {
 }
 ```
 
-Next, we need to add a matching input in the fragment shader:
+다음으로, 프래그먼트 셰이더에는 매칭되는 입력을 추가해야 합니다:
 
 ```glsl
 layout(location = 0) in vec3 fragColor;
@@ -175,21 +94,13 @@ void main() {
 }
 ```
 
-The input variable does not necessarily have to use the same name, they will be
-linked together using the indexes specified by the `location` directives. The
-`main` function has been modified to output the color along with an alpha value.
-As shown in the image above, the values for `fragColor` will be automatically
-interpolated for the fragments between the three vertices, resulting in a smooth
-gradient.
+입력 변수의 이름이 (정점 셰이더의 출력과) 같은 이름일 필요는 없습니다. 이들은 `location` 지시어에 의해 명시된 인덱스를 기반으로 연결됩니다. `main`함수는 알파값과 함께 색상을 출력하도록 수정되었습니다. 위쪽 이미지에서 본 것처럼, `fragColor`의 값은 자동으로 세 정점 사이에서 보간되어 연속적인 값을 보여줍니다.
 
-## Compiling the shaders
+## 셰이더 컴파일하기
 
-Create a directory called `shaders` in the root directory of your project and
-store the vertex shader in a file called `shader.vert` and the fragment shader
-in a file called `shader.frag` in that directory. GLSL shaders don't have an
-official extension, but these two are commonly used to distinguish them.
+프로젝트의 루트(root) 디렉토리에 `shaders`라는 이름의 디렉토리를 만들고 정점 셰이더는 `shader.vert` 파일에, 프래그먼트 셰이더는 `shader.frag`파일에 작성하고 해당 디렉토리에 넣으세요. GLSL 셰이더를 위한 공식적인 확장자는 없지만, 이러한 방식이 그 둘을 구분하기 위해 일반적으로 사용하는 방법입니다.
 
-The contents of `shader.vert` should be:
+`shader.vert`의 내용은 아래와 같습니다:
 
 ```glsl
 #version 450
@@ -214,7 +125,7 @@ void main() {
 }
 ```
 
-And the contents of `shader.frag` should be:
+`shader.frag`의 내용은 아래와 같습니다:
 
 ```glsl
 #version 450
@@ -228,12 +139,11 @@ void main() {
 }
 ```
 
-We're now going to compile these into SPIR-V bytecode using the
-`glslc` program.
+이제 `glslc` 프로그램을 사용해 이들을 SPIR-V 바이트코드로 만들겁니다.
 
-**Windows**
+**윈도우즈**
 
-Create a `compile.bat` file with the following contents:
+아래와 같은 내용을 담은 `compile.bat` 파일을 만듭니다:
 
 ```bash
 C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.vert -o vert.spv
@@ -241,39 +151,30 @@ C:/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.frag -o frag.spv
 pause
 ```
 
-Replace the path to `glslc.exe` with the path to where you installed
-the Vulkan SDK. Double click the file to run it.
+`glslc.exe`의 경로는 여러분이 Vulkan SDK를 설치한 경로로 설정해 주세요. 그리고 더블클릭하여 실행합니다.
 
-**Linux**
+**리눅스**
 
-Create a `compile.sh` file with the following contents:
+아래와 같은 내용을 담은 `compile.sh` 파일을 만듭니다:
 
 ```bash
 /home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv
 /home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv
 ```
 
-Replace the path to `glslc` with the path to where you installed the
-Vulkan SDK. Make the script executable with `chmod +x compile.sh` and run it.
+`glslc.exe`의 경로는 여러분이 Vulkan SDK를 설치한 경로로 설정해 주세요. 그리고 `chmod +x compile.sh`로 실행 파일을 만든 뒤 실행합니다.
 
-**End of platform-specific instructions**
+**플랫폼별 안내는 여기까지**
 
-These two commands tell the compiler to read the GLSL source file and output a SPIR-V bytecode file using the `-o` (output) flag.
+위 두 명령어는 `-o` (output) 플래그로 컴파일러에게 GLSL 소스 파일을 읽어서 SPIR-V 바이트코드 파일을 출력하도록 합니다.
 
-If your shader contains a syntax error then the compiler will tell you the line
-number and problem, as you would expect. Try leaving out a semicolon for example
-and run the compile script again. Also try running the compiler without any
-arguments to see what kinds of flags it supports. It can, for example, also
-output the bytecode into a human-readable format so you can see exactly what
-your shader is doing and any optimizations that have been applied at this stage.
+여러분의 셰이더에 문법적 오류가 있다면 컴파일러가 해당하는 라인과 문제가 뭔지를 알려줍니다. 예를 들어 세미콜론을 지우고 다시 컴파일 해 보세요. 또한 아무런 인자 없이 컴파일러를 실행해 어떤 플래그들이 지원되는지 살펴 보세요. 예를 들어 바이트코드를 사람이 읽을 수 있는 포맷으로 출력하여 셰이더가 정확히 어떤 일을 하는지도 볼 수 있고, 이 단계에서 어떤 최적화가 적용되는지도 볼 수 있습니다.
 
-Compiling shaders on the commandline is one of the most straightforward options and it's the one that we'll use in this tutorial, but it's also possible to compile shaders directly from your own code. The Vulkan SDK includes [libshaderc](https://github.com/google/shaderc), which is a library to compile GLSL code to SPIR-V from within your program.
+셰이더를 명령줄(commandline)로 컴파일하는 것은 가장 직관적인 방법이고 이 튜토리얼에서는 이 방식을 사용할 것이지만, 코드 내에서 셰이더를 컴파일하도록 할 수도 있습니다. Vulkan SDK에는 [libshaderc](https://github.com/google/shaderc)가 포함되어 있는데, 프로그램 내에서 GLSL 코드를 SPIR-V로 컴파일하기 위한 라이브러리입니다.
 
-## Loading a shader
+## 셰이더 로딩
 
-Now that we have a way of producing SPIR-V shaders, it's time to load them into
-our program to plug them into the graphics pipeline at some point. We'll first
-write a simple helper function to load the binary data from the files.
+SPIR-V 셰이더를 생성할 방법을 알아봤으니 이제는 그 결과물을 프로그램에 로드하고 이를 그래픽스 파이프라인 어딘가에 꽂아넣을 시간입니다. 먼저 간단한 헬퍼 함수를 만들어 바이너리 데이터를 파일로부터 로드할 수 있도록 합니다.
 
 ```c++
 #include <fstream>
@@ -289,30 +190,26 @@ static std::vector<char> readFile(const std::string& filename) {
 }
 ```
 
-The `readFile` function will read all of the bytes from the specified file and
-return them in a byte array managed by `std::vector`. We start by opening the
-file with two flags:
+`readFile` 함수는 명시한 파일에서 모든 바이트를 읽어와서 `std::vector`로 저장된 바이트 배열을 반환하도록 할 것입니다. 먼저 두 개의 플래그로 파일을 엽니다.
 
-* `ate`: Start reading at the end of the file
-* `binary`: Read the file as binary file (avoid text transformations)
+- `ate`: 파일의 끝에서부터 읽습니다.
+- `binary`: 파일을 바이너리로 읽습니다 (텍스트로 변환 방지)
 
-The advantage of starting to read at the end of the file is that we can use the
-read position to determine the size of the file and allocate a buffer:
+끝에서부터 읽는 경우의 장점은 읽기 위치를 사용해 파일의 크기를 파악하여 버퍼를 할당할 수 있다는 점입니다.
 
 ```c++
 size_t fileSize = (size_t) file.tellg();
 std::vector<char> buffer(fileSize);
 ```
 
-After that, we can seek back to the beginning of the file and read all of the
-bytes at once:
+그러고 나서 파일의 맨 앞까지 탐색하여 모든 바이트를 한 번에 읽어옵니다:
 
 ```c++
 file.seekg(0);
 file.read(buffer.data(), fileSize);
 ```
 
-And finally close the file and return the bytes:
+마지막으로 파일을 닫고 바이트를 반환합니다:
 
 ```c++
 file.close();
@@ -320,8 +217,7 @@ file.close();
 return buffer;
 ```
 
-We'll now call this function from `createGraphicsPipeline` to load the bytecode
-of the two shaders:
+이제 이 함수를 `createGraphicsPipeline`에서 호출하여 두 셰이더의 바이트코드를 로드합니다:
 
 ```c++
 void createGraphicsPipeline() {
@@ -330,14 +226,11 @@ void createGraphicsPipeline() {
 }
 ```
 
-Make sure that the shaders are loaded correctly by printing the size of the
-buffers and checking if they match the actual file size in bytes. Note that the code doesn't need to be null terminated since it's binary code and we will later be explicit about its size.
+셰이더가 제대로 로드 되었는지를 버퍼의 크기를 출력하여 파일의 실제 바이트 사이와 일치하는를 통해 확인하세요. 바이너리 코드이기 때문에 널 종료(null terminate)여야 할 필요가 없고 나중에는 이러한 크기를 명시적으로 확인할 것입니다.
 
-## Creating shader modules
+## 셰이더 모듈 생성하기
 
-Before we can pass the code to the pipeline, we have to wrap it in a
-`VkShaderModule` object. Let's create a helper function `createShaderModule` to
-do that.
+코드를 파이프라인에 넘기기 전에, `VkShaderModule` 객체로 이들을 감싸야 합니다. 이를 위한 `createShaderModule` 헬퍼 함수를 만듭시다.
 
 ```c++
 VkShaderModule createShaderModule(const std::vector<char>& code) {
@@ -345,18 +238,9 @@ VkShaderModule createShaderModule(const std::vector<char>& code) {
 }
 ```
 
-The function will take a buffer with the bytecode as parameter and create a
-`VkShaderModule` from it.
+이 함수는 바이트코드 버퍼를 매개변수로 받아서 `VkShaderModule`를 만들 것입니다.
 
-Creating a shader module is simple, we only need to specify a pointer to the
-buffer with the bytecode and the length of it. This information is specified in
-a `VkShaderModuleCreateInfo` structure. The one catch is that the size of the
-bytecode is specified in bytes, but the bytecode pointer is a `uint32_t` pointer
-rather than a `char` pointer. Therefore we will need to cast the pointer with
-`reinterpret_cast` as shown below. When you perform a cast like this, you also
-need to ensure that the data satisfies the alignment requirements of `uint32_t`.
-Lucky for us, the data is stored in an `std::vector` where the default allocator
-already ensures that the data satisfies the worst case alignment requirements.
+셰이더 모듈을 만드는 것은 간단합니다. 바이트코드가 있는 버퍼에 대한 포인터와 그 길이를 명시해 주기만 하면 됩니다. 이 정보들은 `VkShaderModuleCreateInfo` 구조체에 명시할 것입니다. 하나 주의할 점은 바이트코드의 크기는 바이트 단위이지만, 바이트코드의 포인터는 `char` 포인터가 아닌 `uint32_t` 포인터라는 것입니다. 따라서 아래 보이는 것처럼 `reinterpret_cast`를 활용해 캐스팅(cast)을 해 주어야 합니다. 이런 식으로 캐스팅을 하게 되면 데이터가 `uint32_t`의 정렬(alignment) 요구사항에 맞는지 확인해 주어야 합니다. 다행히 데이터는 기본 할당자가 정렬 요구사항을 만족하도록 보장되어 있는 `std::vector`에 저장되어 있으니 문제 없습니다.
 
 ```c++
 VkShaderModuleCreateInfo createInfo{};
@@ -365,7 +249,7 @@ createInfo.codeSize = code.size();
 createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
 ```
 
-The `VkShaderModule` can then be created with a call to `vkCreateShaderModule`:
+`VkShaderModule`은 `vkCreateShaderModule`를 호출하여 생성됩니다:
 
 ```c++
 VkShaderModule shaderModule;
@@ -374,17 +258,13 @@ if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCC
 }
 ```
 
-The parameters are the same as those in previous object creation functions: the
-logical device, pointer to create info structure, optional pointer to custom
-allocators and handle output variable. The buffer with the code can be freed
-immediately after creating the shader module. Don't forget to return the created
-shader module:
+매개변수는 이전 객체 생성 함수와 동일합니다. 논리적 장치, 생성 정보를 담은 구조체에 대한 포인터, 사용자 정의 할당자를 위한 선택적 포인터, 그리고 출력 변수에 대한 핸들입니다. 셰이더 모듈을 생성하고 나면 코드가 담긴 버퍼는 해제되어도 됩니다. 만들어진 셰이더 모듈을 반환하는 것도 잊지 마시고요:
 
 ```c++
 return shaderModule;
 ```
 
-Shader modules are just a thin wrapper around the shader bytecode that we've previously loaded from a file and the functions defined in it. The compilation and linking of the SPIR-V bytecode to machine code for execution by the GPU doesn't happen until the graphics pipeline is created. That means that we're allowed to destroy the shader modules again as soon as pipeline creation is finished, which is why we'll make them local variables in the `createGraphicsPipeline` function instead of class members:
+셰이더 모듈은 단지 파일로부터 로드한 셰이더 바이트코드를 감싸는 작은 래퍼입니다. GPU에서 실행을 위해 수행하는 SPIR-V 바이트코드의 컴파일과 링킹을 통한 기계 코드로의 변환은 그래픽스 파이프라인이 생성되기 전에는 수행되지 않습니다. 즉, 파이프라인 생성이 완료되면 셰이더 모듈은 소멸되어도 문제가 없고, 그러한 이유로 우리는 이들을 클래스 멤버가 아닌 `createGraphicsPipeline`함수의 지역 변수로 선언할 것입니다:
 
 ```c++
 void createGraphicsPipeline() {
@@ -395,7 +275,7 @@ void createGraphicsPipeline() {
     VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
 ```
 
-The cleanup should then happen at the end of the function by adding two calls to `vkDestroyShaderModule`. All of the remaining code in this chapter will be inserted before these lines.
+정리 과정은 함수의 마지막 부분에 `vkDestroyShaderModule` 함수를 두 번 호출함으로써 이루어집니다. 이 챕터의 나머지 모든 코드는 이 둘 사이에 작성될 것입니다.
 
 ```c++
     ...
@@ -404,12 +284,11 @@ The cleanup should then happen at the end of the function by adding two calls to
 }
 ```
 
-## Shader stage creation
+## 셰이더 단계(stage) 생성하기
 
-To actually use the shaders we'll need to assign them to a specific pipeline stage through `VkPipelineShaderStageCreateInfo` structures as part of the actual pipeline creation process.
+셰이더를 실제로 사용하기 위해서는 이들을 `VkPipelineShaderStageCreateInfo` 구조체를 활용하여 파이프라인의 특정 단계에 할당해야 하고, 이 역시 파이프라인 생성 과정의 한 부분입니다.
 
-We'll start by filling in the structure for the vertex shader, again in the
-`createGraphicsPipeline` function.
+먼저 정점 셰이더를 위한 구조체를 채우는 것부터 시작할 것인데, 마찬가지로 `createGraphicsPipeline` 함수 안에서 이루어집니다.
 
 ```c++
 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
@@ -417,32 +296,18 @@ vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
 ```
 
-The first step, besides the obligatory `sType` member, is telling Vulkan in
-which pipeline stage the shader is going to be used. There is an enum value for
-each of the programmable stages described in the previous chapter.
+첫 단계로 당연히 필요한 `sType` 외에, Vulkan에게 셰이더가 사용될 파이프라인 단계를 알려줍니다. 이전 챕터에서 설명한 각 프로그램가능한 단계를 위한 열거형 값들이 있습니다.
 
 ```c++
 vertShaderStageInfo.module = vertShaderModule;
 vertShaderStageInfo.pName = "main";
 ```
 
-The next two members specify the shader module containing the code, and the
-function to invoke, known as the *entrypoint*. That means that it's possible to combine multiple fragment
-shaders into a single shader module and use different entry points to
-differentiate between their behaviors. In this case we'll stick to the standard
-`main`, however.
+다음 두 멤버는 코드를 담은 셰이더 모듈과 *진입점(entrypoint)*인 호출할 함수를 명시합니다. 즉 여러 프래그먼트 셰이더들을 하나의 셰이더 모듈로 만들고 서로 다른 진입점을 사용해 다른 동작을 하도록 만들 수도 있습니다. 지금은 그냥 표준적인 `main`을 사용할 것입니다.
 
-There is one more (optional) member, `pSpecializationInfo`, which we won't be
-using here, but is worth discussing. It allows you to specify values for shader
-constants. You can use a single shader module where its behavior can be
-configured at pipeline creation by specifying different values for the constants
-used in it. This is more efficient than configuring the shader using variables
-at render time, because the compiler can do optimizations like eliminating `if`
-statements that depend on these values. If you don't have any constants like
-that, then you can set the member to `nullptr`, which our struct initialization
-does automatically.
+마지막 하나의 (선택적인) 멤버는 `pSpecializationInfo`이고, 여기서 사용할 것은 아니지만 언급할 필요는 있습니다. 이 멤버는 셰이더 상수(constant)의 값을 명시할 수 있도록 합니다. 하나의 셰이더 모듈을 만들고 파이프라인 생성 단계에서 사용되는 상수의 값을 다르게 명시하여 다르게 동작하도록 할 수 있습니다. 이렇게 하는 것이 변수를 사용하여 렌더링 시점에 셰이더의 동작을 바꾸는 것보다 효율적인데, 이렇게 하면 컴파일러가 이 값에 의존하는 `if` 분기를 제거하는 등의 최적화를 할 수 있습니다. 이에 해당하는 상수가 없다면 이 값은 `nullptr`로 두면 되고, 지금 우리 코드에서는 자동으로 이렇게 됩니다.
 
-Modifying the structure to suit the fragment shader is easy:
+프래그먼트 셰이더 경우를 위해 구조체를 수정하는 것은 쉽습니다:
 
 ```c++
 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
@@ -452,15 +317,13 @@ fragShaderStageInfo.module = fragShaderModule;
 fragShaderStageInfo.pName = "main";
 ```
 
-Finish by defining an array that contains these two structs, which we'll later
-use to reference them in the actual pipeline creation step.
+이 두 구조체를 포함하는 배열을 정의하는 것이 마지막 단계이고, 실제 파이프라인 생성 단계에서는 이 배열을 사용해 셰이더 모듈들을 참조하도록 할 것입니다.
 
 ```c++
 VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
 ```
 
-That's all there is to describing the programmable stages of the pipeline. In
-the next chapter we'll look at the fixed-function stages.
+파이프라인의 프로그램 가능한 단계에 대한 설정은 여기까지입니다. 다음 챕터에서는 고정 함수 단계를 살펴볼 것입니다.
 
 [C++ code](/code/09_shader_modules.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From a5d2faf4959f885710fbb5a9dfdf67c3418ccb49 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 17 Oct 2023 20:35:53 +0900
Subject: [PATCH 16/47] kr translate fixed mardown errors and links

---
 kr/01_Overview.md                                |  2 +-
 kr/02_Development_environment.md                 | 16 ++++++++--------
 .../01_Presentation/01_Swap_chain.md             |  2 +-
 .../00_Introduction.md                           |  6 +++---
 4 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/kr/01_Overview.md b/kr/01_Overview.md
index 325e5124..eb62c050 100644
--- a/kr/01_Overview.md
+++ b/kr/01_Overview.md
@@ -114,7 +114,7 @@ Vulkan의 많은 구조체들은 해당 구조체의 타입을 `sType` 멤버를
 
 앞서 이야기한 것처럼, Vulkan은 고성능과 적은 드라이버 오버헤드를 위해 설계되었습니다. 따라서 기본적으로는 아주 제한적인 오류 체킹과 디버깅 기능만을 포함하고 있습니다. 코드를 잘못 작성하는 드라이버는 오류 코드를 반환하는 대신 그냥 크래쉬(crash)가 발생하거나, 더 나쁜 경우에는 여러분의 그래픽 카드에서는 제대로 동작하는 것처럼 보이지만 다른 그래픽 카드에서는 전혀 동작하지 않을겁니다.
 
-Vulkan은 꼼꼼한 오류 체크를 _validation layers_ 기능을 통해 제공합니다. 검증 레이어는 API와 그래픽 드라이버 사이에 삽입되는 코드로 함수 매개변수에 대한 추가적인 검증이나 메모리 관리 문제를 추적하는 데 사용됩니다. 좋은 점은 이러한 기능을 개발 과정에서 사용하고 릴리즈 할 때에는 완전히 사용하지 않도록 하여 오버헤드를 없앨 수 있다는 것입니다. 스스로 검증 레이어를 작성할 수도 있지만, LunarG가 만든 Vulkan SDK는 표준적인 검증 레이어를 제공하고, 이 튜토리얼에서는 그것을 사용할 것입니다. 여러분은 레이어에서 날아온 디버깅 메시지를 처기하기 위한 콜백 함수를 등록해야 합니다.
+Vulkan은 꼼꼼한 오류 체크를 *validation layers* 기능을 통해 제공합니다. 검증 레이어는 API와 그래픽 드라이버 사이에 삽입되는 코드로 함수 매개변수에 대한 추가적인 검증이나 메모리 관리 문제를 추적하는 데 사용됩니다. 좋은 점은 이러한 기능을 개발 과정에서 사용하고 릴리즈 할 때에는 완전히 사용하지 않도록 하여 오버헤드를 없앨 수 있다는 것입니다. 스스로 검증 레이어를 작성할 수도 있지만, LunarG가 만든 Vulkan SDK는 표준적인 검증 레이어를 제공하고, 이 튜토리얼에서는 그것을 사용할 것입니다. 여러분은 레이어에서 날아온 디버깅 메시지를 처기하기 위한 콜백 함수를 등록해야 합니다.
 
 Vulkan의 각 연산은 아주 명시적이고 검증 레이어는 꼼꼼하기 떄문에 화면이 검은 색 밖에 안나오는 경우에 OpenGL이나 Direct3D보다 그 원인을 찾기가 훨씬 쉽습니다!
 
diff --git a/kr/02_Development_environment.md b/kr/02_Development_environment.md
index 79b30a62..2839940c 100644
--- a/kr/02_Development_environment.md
+++ b/kr/02_Development_environment.md
@@ -16,9 +16,9 @@ SDK는 [LunarG 웹사이트](https://vulkan.lunarg.com/) 페이지 하단의 버
 
 ![](/images/cube_demo.png)
 
-오류 메시지가 나타난다면 드라이버가 최신 버전인지 확인하고, 그래픽 카드가 Vulkan을 지원하고 Vulkan 런타임이 드라이브에 포함되어 있는지 확인하세요. 주요 벤더들의 드라이버 링크는 [introduction](!en/Introduction) 챕터를 확인하세요.
+오류 메시지가 나타난다면 드라이버가 최신 버전인지 확인하고, 그래픽 카드가 Vulkan을 지원하고 Vulkan 런타임이 드라이브에 포함되어 있는지 확인하세요. 주요 벤더들의 드라이버 링크는 [introduction](!kr/Introduction) 챕터를 확인하세요.
 
-이 폴더에는 개발에 유용한 다른 프로그램들도 있습니다. `glslangValidator.exe`와 `glslc.exe`는 사람이 읽을 수 있는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) 코드를 바이트 코드로 변환하기 위해 사용됩니다. [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) 챕터에서 이 내용을 자세히 살펴볼 것입니다. `Bin` 디렉터리에는 또한 Vulkan 로더와 검증 레이어의 바이너리들을 포함하고 있으며, `Lib` 디렉터리에는 라이브러리들이 들어 있습니다.
+이 폴더에는 개발에 유용한 다른 프로그램들도 있습니다. `glslangValidator.exe`와 `glslc.exe`는 사람이 읽을 수 있는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) 코드를 바이트 코드로 변환하기 위해 사용됩니다. [shader modules](!kr/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) 챕터에서 이 내용을 자세히 살펴볼 것입니다. `Bin` 디렉터리에는 또한 Vulkan 로더와 검증 레이어의 바이너리들을 포함하고 있으며, `Lib` 디렉터리에는 라이브러리들이 들어 있습니다.
 
 마지막으로 `Include` 디렉터리에는 Vulkan 헤더들이 있습니다. 다른 파일들도 자유롭게 살펴보시길 바라지만 이 튜토리얼에서는 필요하지 않습니다.
 
@@ -140,7 +140,7 @@ Vulkan과 GLFW의 오브젝트 파일 이름을 추가하세요:
 
 ![](/images/vs_test_window.png)
 
-extention의 숫자는 0이 아니어야 합니다. 축하합니다. [Vulkan을 즐기기 위한](!en/Drawing_a_triangle/Setup/Base_code)! 모든 준비가 완료되었습니다.
+extention의 숫자는 0이 아니어야 합니다. 축하합니다. [Vulkan을 즐기기 위한](!kr/Drawing_a_triangle/Setup/Base_code)! 모든 준비가 완료되었습니다.
 
 ## 리눅스
 
@@ -160,7 +160,7 @@ Arch 리눅스에서는 위 도구들을 설치하기 위해서는 `sudo pacman
 
 ![](/images/cube_demo_nowindow.png)
 
-오류 메시지가 나타난다면 드라이버가 최신 버전인지 확인하고, 그래픽 카드가 Vulkan을 지원하고 Vulkan 런타임이 드라이브에 포함되어 있는지 확인하세요. 주요 벤더들의 드라이버 링크는 [introduction](!en/Introduction) 챕터를 확인하세요.
+오류 메시지가 나타난다면 드라이버가 최신 버전인지 확인하고, 그래픽 카드가 Vulkan을 지원하고 Vulkan 런타임이 드라이브에 포함되어 있는지 확인하세요. 주요 벤더들의 드라이버 링크는 [introduction](!kr/Introduction) 챕터를 확인하세요.
 
 ### X Window System and XFree86-VidModeExtension
 
@@ -221,7 +221,7 @@ sudo pacman -S glm
 
 `glslc: error: no input files`
 
-[shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) 챕터에서 `glslc`를 자세히 살펴볼 것입니다.
+[shader modules](!kr/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) 챕터에서 `glslc`를 자세히 살펴볼 것입니다.
 
 ### makefile 프로젝트 구성
 
@@ -344,7 +344,7 @@ clean:
 
 이제 이 디렉터리를 여러분의 Vulkan 프로젝트를 위한 템플릿으로 사용하시면 됩니다. 복사하고 이름을 `HelloTriangle`과 같은 것으로 바꾸고, `main.cpp`의 모든 내용을 지우면 됩니다.
 
-이제 [진정한 탐험](!en/Drawing_a_triangle/Setup/Base_code)을 위한 준비가 끝났습니다.
+이제 [진정한 탐험](!kr/Drawing_a_triangle/Setup/Base_code)을 위한 준비가 끝났습니다.
 
 ## MacOS
 
@@ -438,7 +438,7 @@ int main() {
 
 Keep in mind you are not required to understand all this code is doing yet, we are just setting up some API calls to make sure everything is working.
 
-Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the _Project Navigator_ panel select your project. Open the _Build Settings_ tab and then:
+Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the *Project Navigator* panel select your project. Open the *Build Settings* tab and then:
 
 - Find the **Header Search Paths** field and add a link to `/usr/local/include` (this is where Homebrew installs headers, so the glm and glfw3 header files should be there) and a link to `vulkansdk/macOS/include` for the Vulkan headers.
 - Find the **Library Search Paths** field and add a link to `/usr/local/lib` (again, this is where Homebrew installs libraries, so the glm and glfw3 lib files should be there) and a link to `vulkansdk/macOS/lib`.
@@ -447,7 +447,7 @@ It should look like so (obviously, paths will be different depending on where yo
 
 ![](/images/xcode_paths.png)
 
-Now, in the _Build Phases_ tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding the dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks).
+Now, in the *Build Phases* tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding the dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks).
 
 - For glfw open the folder `/usr/local/lib` and there you will find a file name like `libglfw.3.x.dylib` ("x" is the library's version number, it might be different depending on when you downloaded the package from Homebrew). Simply drag that file to the Linked Frameworks and Libraries tab on Xcode.
 - For vulkan, go to `vulkansdk/macOS/lib`. Do the same for the both files `libvulkan.1.dylib` and `libvulkan.1.x.xx.dylib` (where "x" will be the version number of the the SDK you downloaded).
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
index 26808457..c782e189 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
@@ -232,7 +232,7 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 }
 ```
 
-스왑 크기는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 _픽셀 단위에서_ 우리가 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)으로 해상도를 명시해야 합니다.
+스왑 크기는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 *픽셀 단위에서* 우리가 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)으로 해상도를 명시해야 합니다.
 
 GLFW는 크기를 측정하는 두 단위가 있고 이는 픽셀과 [스크린 좌표계](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems) 입니다. 예를 들어 우리가 이전에 윈도우를 생성할 때 명시한 `{WIDTH, HEIGHT}` 해상도는 스크린 좌표계 기준으로 측정한 값입니다. 하지만 Vulkan은 픽셀 단위로 동작하기 때문에, 스왑 체인의 크기도 픽셀 단위로 명시해 주어야만 합니다. 안타깝게도 여러분이 (애플릐 레티나 디스플레이와 같은) 고DPI 디스플레이를 사용하는 경우, 스크린 좌표계가 픽셀 단위와 달라집니다. 높은 픽셀 밀도로 인해 픽셀 단위의 윈도우 해상도는 스크린 좌표계 단위의 윈도우 해상도보다 커집니다. Vulkan이 스왑 크기에 관한 것을 수정해 주지 않는 한, 그냥 `{WIDTH, HEIGHT}`를 사용할 수는 없습니다. 대신에 `glfwGetFramebufferSize`를 사용해서 윈도우의 해상도를 최대 및 최소 이미지 크기와 맞추기 전에 픽셀 단위로 받아와야만 합니다.
 
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
index 4bff8d68..72115d68 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
@@ -10,13 +10,13 @@
 
 *기하 셰이더(geometry shader)*는 모든 프리미티브(삼각형, 선, 점)에 대해 실행되며 그 정보를 탈락(discard)시키거나 입력된 것보다 더 많은 프리미티브를 생성할 수 있습니다. 테셀레이션 셰이더와 비슷하지만 훨씬 유연합니다. 하지만 요즘 응용 프로그램에서는 자주 사용되지 않는데 인텔의 내장 그래픽 카드를 제외하고 대부분 그래픽 카드에서는 성능이 그리 좋지 않기 때문입니다.
 
-_래스터화(rasterization)_ 단계는 프리미티브를 *프래그먼트*로 이산화하는 단계입니다. 프래그먼트는 픽셀 요로소 프레임버퍼에 채워집니다. 화면 밖에 놓여 있는 프래그먼트는 버려지고 정점 셰이더의 출력 어트리뷰트(attribute)는 프래그먼트들에 걸쳐 그림에 보이는 것과 같이 보간됩니다. 다른 프리미티브 프래그먼트 뒤에 놓여있는 프래그먼트도 깊이 테스트(depth test)에 의해 버려집니다.
+*래스터화(rasterization)* 단계는 프리미티브를 *프래그먼트*로 이산화하는 단계입니다. 프래그먼트는 픽셀 요로소 프레임버퍼에 채워집니다. 화면 밖에 놓여 있는 프래그먼트는 버려지고 정점 셰이더의 출력 어트리뷰트(attribute)는 프래그먼트들에 걸쳐 그림에 보이는 것과 같이 보간됩니다. 다른 프리미티브 프래그먼트 뒤에 놓여있는 프래그먼트도 깊이 테스트(depth test)에 의해 버려집니다.
 
 *프래그먼트 셰이더(fragment shader)*는 모든 살아남은 프래그먼트에 대해 실행되며 어떤 프레임버퍼에 프래그먼트가 쓰여질지, 어떤 색상과 깊이값이 쓰여질지를 결정합니다. 이는 정점 셰이더에서 보간된 데이터를 바탕으로 이루어지며 데이터는 텍스처 좌표계와 라이팅(lighting)을 위한 법선(normal) 정보 같은 것들이 포함됩니다.
 
-_컬러 블렌딩(color blending)_ 단계는 프레임버퍼의 같은 픽셀에 맵핑되는 다른 프래드먼트들을 섞는 연산을 적용합니다. 프래그먼트 값들이 다른 값들을 대체할 수도 있고, 투명도에 따라 더해지거나 섞일 수 있습니다.
+*컬러 블렌딩(color blending)* 단계는 프레임버퍼의 같은 픽셀에 맵핑되는 다른 프래드먼트들을 섞는 연산을 적용합니다. 프래그먼트 값들이 다른 값들을 대체할 수도 있고, 투명도에 따라 더해지거나 섞일 수 있습니다.
 
-녹색으로 표현된 단계는 _고정 함수(fixed-function)_ 단계로 알려져 있습니다. 이 단계들은 매개변수를 사용해 연산을 약간 변경할 수 있지만, 동작 방식 자체는 미리 정의되어 있습니다.
+녹색으로 표현된 단계는 *고정 함수(fixed-function)* 단계로 알려져 있습니다. 이 단계들은 매개변수를 사용해 연산을 약간 변경할 수 있지만, 동작 방식 자체는 미리 정의되어 있습니다.
 
 주황색으로 표시된 단계는 `programmable`한 단계인데, 여러분이 작성한 코드를 그래픽 카드에 업로드할 수 있어서 원하는 대로 연산을 할 수 있다는 뜻입니다. 이렇게 되면 예를 들어 프래그먼트 셰이더에서 텍스처링이라던지 레이 트레이싱(ray tracing)을 위한 라이팅 등을 구현할 수 있게 됩니다. 이러한 프로그램은 여러 객체(예를들어 정점 또는 프래그먼트)들을 처리하기 위해 여러 GPU 코어에서 동시에 병렬적으로 실행됩니다.
 

From 28d4c5cd126de1d8a67e89c49f2366d015b8f613 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 18 Oct 2023 22:50:31 +0900
Subject: [PATCH 17/47] kr translate 03-02-02 fixed functions

---
 .../02_Fixed_functions.md                     | 263 +++++-------------
 1 file changed, 76 insertions(+), 187 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
index a80f72ae..f5946a7c 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
@@ -1,16 +1,8 @@
+예전 그래픽스 API는 그래픽스 파이프라인 대부분의 단계에서 기본 상태(default state)를 제공했습니다. Vulkan에서는 파이프라인 상태 대부분을 명시적으로 설정해야 하고 이는 불변하는 파이프라인 상태 객체로 만들어집니다(baked). 이 챕터에서는 이러한 고정 함수 연산들에 대한 모든 구조체를 만들 것입니다.
 
-The older graphics APIs provided default state for most of the stages of the
-graphics pipeline. In Vulkan you have to be explicit about most pipeline states as
-it'll be baked into an immutable pipeline state object. In this chapter we'll fill 
-in all of the structures to configure these fixed-function operations.
+## 동적 상태(Dynamic state)
 
-## Dynamic state
-
-While *most* of the pipeline state needs to be baked into the pipeline state, 
-a limited amount of the state *can* actually be changed without recreating the 
-pipeline at draw time. Examples are the size of the viewport, line width 
-and blend constants. If you want to use dynamic state and keep these properties out, 
-then you'll have to fill in a `VkPipelineDynamicStateCreateInfo` structure like this:
+*대부분*의 파이프라인 상태가 파이프라인 상태 객체로 만들어져야만 하지만 몇몇 상태들은 그리기 시점에 파이프라인을 재생성하지 않고도 변경*될 수 있습니다*. 예시로는 뷰포트의 크기라던지, 선의 두께라던지 블렌딩 상수 등이 있습니다. 동적 상태를 사용하고 싶고, 이런 상태들을 계속 제외된 상태로 두고 싶다면, `VkPipelineDynamicStateCreateInfo` 구조체를 아래와 같이 만들어야 합니다.
 
 ```c++
 std::vector<VkDynamicState> dynamicStates = {
@@ -24,25 +16,17 @@ dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
 dynamicState.pDynamicStates = dynamicStates.data();
 ```
 
-This will cause the configuration of these values to be ignored and you will be
-able (and required) to specify the data at drawing time. This results in a more flexible
-setup and is very common for things like viewport and scissor state, which would
-result in a more complex setup when being baked into the pipeline state.
+이렇게 하면 해당하는 값들의 설정은 무시되고 그리기 시점에 이들을 변경 가능(그리고 변경해야만) 합니다. 이렇게 하면 보다 유연한 설정이 가능하고 뷰포트나 시저(scissor) 상태에 대해서는 그렇게 하는 것이 일반적이지만, 파이프라인 상태 객체를 만드는 것이 보다 복잡해집니다.
 
-## Vertex input
+## 정점 입력
 
-The `VkPipelineVertexInputStateCreateInfo` structure describes the format of the
-vertex data that will be passed to the vertex shader. It describes this in
-roughly two ways:
+`VkPipelineVertexInputStateCreateInfo` 구조체는 정점 데이터의 포맷을 기술하고, 이는 정점 셰이더로 넘겨집니다. 크게 두 가지 방법으로 기술됩니다:
 
-* Bindings: spacing between data and whether the data is per-vertex or
-per-instance (see [instancing](https://en.wikipedia.org/wiki/Geometry_instancing))
-* Attribute descriptions: type of the attributes passed to the vertex shader,
+* 바인딩: 데이터 사이의 간격과 데이터가 정점별 데이터인지 인스턴스별(per-instance) 데이터인지 여부 ([인스턴싱](https://en.wikipedia.org/wiki/Geometry_instancing) 참고)
+* 어트리뷰트 기술: type of the attributes passed to the vertex shader,
 which binding to load them from and at which offset
 
-Because we're hard coding the vertex data directly in the vertex shader, we'll
-fill in this structure to specify that there is no vertex data to load for now.
-We'll get back to it in the vertex buffer chapter.
+우리는 정점 셰이더에 정점 데이터를 하드 코딩하고 있기 때문에 지금은 이 구조체에 로드할 정점 데이터가 없다고 명시할 것입니다. 정점 버퍼 챕터에서 다시 살펴볼 것입니다.
 
 ```c++
 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
@@ -53,36 +37,21 @@ vertexInputInfo.vertexAttributeDescriptionCount = 0;
 vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional
 ```
 
-The `pVertexBindingDescriptions` and `pVertexAttributeDescriptions` members
-point to an array of structs that describe the aforementioned details for
-loading vertex data. Add this structure to the `createGraphicsPipeline` function
-right after the `shaderStages` array.
-
-## Input assembly
-
-The `VkPipelineInputAssemblyStateCreateInfo` struct describes two things: what
-kind of geometry will be drawn from the vertices and if primitive restart should
-be enabled. The former is specified in the `topology` member and can have values
-like:
-
-* `VK_PRIMITIVE_TOPOLOGY_POINT_LIST`: points from vertices
-* `VK_PRIMITIVE_TOPOLOGY_LINE_LIST`: line from every 2 vertices without reuse
-* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP`: the end vertex of every line is used as
-start vertex for the next line
-* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST`: triangle from every 3 vertices without
-reuse
-* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP `: the second and third vertex of every
-triangle are used as first two vertices of the next triangle
-
-Normally, the vertices are loaded from the vertex buffer by index in sequential
-order, but with an *element buffer* you can specify the indices to use yourself.
-This allows you to perform optimizations like reusing vertices. If you set the
-`primitiveRestartEnable`  member to `VK_TRUE`, then it's possible to break up
-lines and triangles in the `_STRIP` topology modes by using a special index of
-`0xFFFF` or `0xFFFFFFFF`.
-
-We intend to draw triangles throughout this tutorial, so we'll stick to the
-following data for the structure:
+`pVertexBindingDescriptions`와 `pVertexAttributeDescriptions` 멤버는 앞서 언급한 정점 데이터를 로드하기 위한 세부 사항들을 기술하는 구조체의 배열에 대한 포인터입니다. 이 구조체를 `createGraphicsPipeline`함수의 `shaderStages` 배열 뒤에 추가합니다.
+
+## 입력 조립
+
+`VkPipelineInputAssemblyStateCreateInfo`구조체는 두 가지를 기술합니다: 정점으로부터 어떤 기하 형상이 그려질지와 프리미티브 재시작(restart)를 활성화할지 여부입니다. 앞의 내용은 `topology` 멤버에 명시되고 가능한 값들은 아래와 같습니다:
+
+* `VK_PRIMITIVE_TOPOLOGY_POINT_LIST`: 정점으로부터 점을 그림
+* `VK_PRIMITIVE_TOPOLOGY_LINE_LIST`: 재사용 없이 두 정점마다 선을 그림
+* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP`: 선의 마지막 정점이 다음 선의 시작점으로 사용됨
+* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST`: 재사용 없이 3개의 정점마다 삼각형을 그림
+* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP `: 삼각형의 두 번쨰와 세 번째 정점이 다음 삼각형의 첫 두 개의 정점으로 사용됨
+
+일반적으로 정점은 정점 버퍼로부터 인덱스 순서대로 로드되지만, *요소 버퍼(element buffer)*를 사용해 인덱스를 직접 명시할 수 있습니다. 이렇게 하면 정점의 재사용을 통해 성능을 최적화 할 수 있습니다. `primitiveRestartEnable` 멤버를 `VK_TRUE`로 설정했다면, `_STRIP` 토폴로지 모드의 선과 삼각형을 `0xFFFF` 또는 `0xFFFFFFFF`와 같은 특별한 인덱스를 사용해 분할할 수 있습니다.
+
+이 예제에서는 삼각형들을 그릴 것이므로 구조체는 아래와 같은 값들로 설정할 것입니다:
 
 ```c++
 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
@@ -91,11 +60,9 @@ inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
 inputAssembly.primitiveRestartEnable = VK_FALSE;
 ```
 
-## Viewports and scissors
+## 뷰포트와 시저
 
-A viewport basically describes the region of the framebuffer that the output
-will be rendered to. This will almost always be `(0, 0)` to `(width, height)`
-and in this tutorial that will also be the case.
+뷰포트는 출력으로 그려질 프레임버퍼의 영역을 설정합니다. 튜토리얼에서는 거의 항상 `(0, 0)`에서 `(width, height)`까지고, 지금도 그렇게 설정합니다.
 
 ```c++
 VkViewport viewport{};
@@ -107,26 +74,15 @@ viewport.minDepth = 0.0f;
 viewport.maxDepth = 1.0f;
 ```
 
-Remember that the size of the swap chain and its images may differ from the
-`WIDTH` and `HEIGHT` of the window. The swap chain images will be used as
-framebuffers later on, so we should stick to their size.
+스왑 체인의 크기와 그 이미지가 윈도우의 `WIDTH`와 `HEIGHT`와는 다르다는 것을 기억하십시오. 스왑 체인 이미지는 나중에 프레임버퍼로 사용될 것이므로 그 크기를 사용해야 합니다.
 
-The `minDepth` and `maxDepth` values specify the range of depth values to use
-for the framebuffer. These values must be within the `[0.0f, 1.0f]` range, but
-`minDepth` may be higher than `maxDepth`. If you aren't doing anything special,
-then you should stick to the standard values of `0.0f` and `1.0f`.
+`minDepth`와 `maxDepth` 값은 프레임버퍼에서 사용할 깊이 값의 범위입니다. 이 값들은 `[0.0f, 1.0f]` 범위여야 하지만 `minDepth`가 `maxDepth`보다 클 수 있습니다. 특수한 작업을 하는 것이 아니라면 `0.0f`와 `1.0f`의 일반적인 값을 사용하면 됩니다.
 
-While viewports define the transformation from the image to the framebuffer,
-scissor rectangles define in which regions pixels will actually be stored. Any
-pixels outside the scissor rectangles will be discarded by the rasterizer. They
-function like a filter rather than a transformation. The difference is
-illustrated below. Note that the left scissor rectangle is just one of the many
-possibilities that would result in that image, as long as it's larger than the
-viewport.
+뷰포트가 이미지로부터 프레임버퍼로의 변환을 정의하는 반면, 시저 사각형은 픽셀이 저장될 실제 영역을 정의합니다. 시저 사각형 밖의 픽셀은 래스터화 단계에서 버려집니다. 이는 변환이 아닌 필터라고 보면 됩니다. 차이점이 아래에 나타나 있습니다. 왼쪽 시저 사각형은 위와 같은 결과가 도출되는 수 많은 가능성 중 하나일 뿐임에 주의하십시오. 뷰포트보다 큰 시저 사각형이면 모두 결과가 왼쪽 위와 같이 나타나게 됩니다.
 
 ![](/images/viewports_scissors.png)
 
-So if we wanted to draw to the entire framebuffer, we would specify a scissor rectangle that covers it entirely:
+따라서 전체 프레임버퍼에 그리고 싶다면 시저 사각형은 전체 범위를 커버하도록 명시하면 됩니다:
 
 ```c++
 VkRect2D scissor{};
@@ -134,9 +90,9 @@ scissor.offset = {0, 0};
 scissor.extent = swapChainExtent;
 ```
 
-Viewport(s) and scissor rectangle(s) can either be specified as a static part of the pipeline or as a [dynamic state](#dynamic-state) set in the command buffer. While the former is more in line with the other states it's often convenient to make viewport and scissor state dynamic as it gives you a lot more flexibility. This is very common and all implementations can handle this dynamic state without a performance penalty.
+뷰포트와 시저 사각형은 파이프라인의 정적인 부분으로 명시할 수도 있고 [동적 상태](#dynamic-state)로 명시할 수도 있습니다. 정적으로 하는 경우 다른 상태들과 비슷하게 유지되지만 이들은 동적 상태로 명시하는 것이 유연성을 위해 더 편리합니다. 이렇게 하는 것이 더 일반적이고 이러한 동적 상태는 성능 저하 없이 구현이 가능합니다.
 
-When opting for dynamic viewport(s) and scissor rectangle(s) you need to enable the respective dynamic states for the pipeline:
+동적 뷰포트와 시저 사각형을 위해서는 해당하는 동적 상태를 파이프라인에서 활성화해야 합니다:
 
 ```c++
 std::vector<VkDynamicState> dynamicStates = {
@@ -150,7 +106,7 @@ dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
 dynamicState.pDynamicStates = dynamicStates.data();
 ```
 
-And then you only need to specify their count at pipeline creation time:
+그리고 그 개수를 파이프라인 생성 시에 명시해주면 됩니다.
 
 ```c++
 VkPipelineViewportStateCreateInfo viewportState{};
@@ -159,12 +115,11 @@ viewportState.viewportCount = 1;
 viewportState.scissorCount = 1;
 ```
 
-The actual viewport(s) and scissor rectangle(s) will then later be set up at drawing time.
+실제 뷰포트와 시저 사각형은 그리기 시점에 설정하면 됩니다.
 
-With dynamic state it's even possible to specify different viewports and or scissor rectangles within a single command buffer.
+동적 상태를 사용하면 하나의 명령 버퍼로부터 여러 뷰포트와 시저 사각형을 명시하는 것도 가능합니다.
 
-Without dynamic state, the viewport and scissor rectangle need to be set in the pipeline using the `VkPipelineViewportStateCreateInfo` struct. This makes the viewport and scissor rectangle for this pipeline immutable.
-Any changes required to these values would require a new pipeline to be created with the new values.
+동적 상태를 사용하지 않으면, 뷰포트와 시저 사각형은 `VkPipelineViewportStateCreateInfo` 구조체를 사용해 파이프라인에 설정되어야 합니다. 이렇게 생성된 뷰포트와 시저 사각형은 해당 파이프라인에서 불변성을 가집니다. 이 값들을 변경하고자 하면 새로운 값으로 새로운 파이프라인을 생성해야 합니다.
 
 ```c++
 VkPipelineViewportStateCreateInfo viewportState{};
@@ -175,17 +130,13 @@ viewportState.scissorCount = 1;
 viewportState.pScissors = &scissor;
 ```
 
+어떻게 설정하셨건, 어떤 그래픽 카드에서는 다중 뷰포트와 시저 사각형이 사용 가능하므로 구조체의 멤버는 이들의 배열을 참조하도록 되어 있습니다. 다중 뷰포트 또는 시저 사각형을 사용하려면 GPU 기능을 활성화 하는 것도 필요합니다 (논리적 장치 생성 부분을 참고하세요).
 Independent of how you set them, it's is possible to use multiple viewports and scissor rectangles on some graphics cards, so the structure members reference an array of them. Using multiple requires enabling a GPU feature (see logical device creation).
 
-## Rasterizer
+## 래스터화
 
-The rasterizer takes the geometry that is shaped by the vertices from the vertex
-shader and turns it into fragments to be colored by the fragment shader. It also
-performs [depth testing](https://en.wikipedia.org/wiki/Z-buffering),
-[face culling](https://en.wikipedia.org/wiki/Back-face_culling) and the scissor
-test, and it can be configured to output fragments that fill entire polygons or
-just the edges (wireframe rendering). All this is configured using the
-`VkPipelineRasterizationStateCreateInfo` structure.
+래스터화는 정점 셰이더로부터 만들어진 정점을 받아서 프래그먼트 셰이더에서 색상을 결정할 프래그먼트로 변환합니다. 또한 [깊이 테스트](https://en.wikipedia.org/wiki/Z-buffering),
+[face culling](https://en.wikipedia.org/wiki/Back-face_culling)과 시저 테스트를 수행하고 출력 프래그먼트가 다각형 내부를 채우는지, 모서리만 그리는지(와이어프레임 렌더링)도 설정할 수 있습니다. 이 모든 것들은 `VkPipelineRasterizationStateCreateInfo` 구조체를 통해 설정합니다.
 
 ```c++
 VkPipelineRasterizationStateCreateInfo rasterizer{};
@@ -193,50 +144,38 @@ rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
 rasterizer.depthClampEnable = VK_FALSE;
 ```
 
-If `depthClampEnable` is set to `VK_TRUE`, then fragments that are beyond the
-near and far planes are clamped to them as opposed to discarding them. This is
-useful in some special cases like shadow maps. Using this requires enabling a
-GPU feature.
+`depthClampEnable`가 `VK_TRUE`면, near plane과 far plane 밖의 프래그먼트는 값이 버려지는 대신 clamp됩니다. 이는 그림자 맵과 같은 특수한 경우에 유용한 기능입니다. 이는 GPU 기능을 활성화함으로써 사용 가능합니다. 
 
 ```c++
 rasterizer.rasterizerDiscardEnable = VK_FALSE;
 ```
 
-If `rasterizerDiscardEnable` is set to `VK_TRUE`, then geometry never passes
-through the rasterizer stage. This basically disables any output to the
-framebuffer.
+`rasterizerDiscardEnable`가 `VK_TRUE`면 기하 요소는 래스터화 다음 단계로 넘어가지 않습니다. 이렇게 되면 기본적으로 프레임버퍼에 아무것도 출력되지 않습니다.
 
 ```c++
 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
 ```
 
-The `polygonMode` determines how fragments are generated for geometry. The
-following modes are available:
+`polygonMode`는 프래그먼트가 기하 요소를 생성하는 방식을 결정합니다. 아래와 같은 모드들이 사용 가능합니다:
 
-* `VK_POLYGON_MODE_FILL`: fill the area of the polygon with fragments
-* `VK_POLYGON_MODE_LINE`: polygon edges are drawn as lines
-* `VK_POLYGON_MODE_POINT`: polygon vertices are drawn as points
+* `VK_POLYGON_MODE_FILL`: 폴리곤 영역을 프래그먼트로 채움
+* `VK_POLYGON_MODE_LINE`: 폴리곤의 모서리가 선으로 그려짐
+* `VK_POLYGON_MODE_POINT`: 폴리곤의 정점이 점으로 그려짐
 
-Using any mode other than fill requires enabling a GPU feature.
+색상을 채우는 모드 이외에는 GPU 기능을 활성화해야 사용 가능합니다.
 
 ```c++
 rasterizer.lineWidth = 1.0f;
 ```
 
-The `lineWidth` member is straightforward, it describes the thickness of lines
-in terms of number of fragments. The maximum line width that is supported
-depends on the hardware and any line thicker than `1.0f` requires you to enable
-the `wideLines` GPU feature.
+`lineWidth` 멤버는 이해하기 쉽습니다. 프래그먼트 개수 단위로 선의 두께를 명시합니다. 지원하는 선의 최대 두께는 하드웨어에 따라 다르며, `1.0f` 이상의 값은 `wideLines` GPU 기능을 활성화해야만 사용 가능합니다.
 
 ```c++
 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
 ```
 
-The `cullMode` variable determines the type of face culling to use. You can
-disable culling, cull the front faces, cull the back faces or both. The
-`frontFace` variable specifies the vertex order for faces to be considered
-front-facing and can be clockwise or counterclockwise.
+`cullMode` 변수는 사용할 face culling 타입을 결정합니다. culling을 하지 않거나, 앞면(front face)를 culling하거나, 뒷면(back fack)를 culling하거나, 양면 모두를 culling할 수 있습니다. `frontFace` 변수는 앞면으로 간주할 면의 정점 순서를 명시하며 시계방향 또는 반시계방향일 수 있습니다.
 
 ```c++
 rasterizer.depthBiasEnable = VK_FALSE;
@@ -245,20 +184,11 @@ rasterizer.depthBiasClamp = 0.0f; // Optional
 rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
 ```
 
-The rasterizer can alter the depth values by adding a constant value or biasing
-them based on a fragment's slope. This is sometimes used for shadow mapping, but
-we won't be using it. Just set `depthBiasEnable` to `VK_FALSE`.
+래스터화 단계에서 상수를 더하거나 프래그먼트의 기울기를 기반으로 깊이값을 편향(bias)시킬 수 있습니다. 이 기능은 그림자 맵에서 종종 사용되지만, 우리는 사용하지 않을 것입니다. `depthBiasEnable`를 `VK_FALSE`로 설정합니다.
 
-## Multisampling
+## 멀티샘플링(Multisampling)
 
-The `VkPipelineMultisampleStateCreateInfo` struct configures multisampling,
-which is one of the ways to perform [anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing).
-It works by combining the fragment shader results of multiple polygons that
-rasterize to the same pixel. This mainly occurs along edges, which is also where
-the most noticeable aliasing artifacts occur. Because it doesn't need to run the
-fragment shader multiple times if only one polygon maps to a pixel, it is
-significantly less expensive than simply rendering to a higher resolution and
-then downscaling. Enabling it requires enabling a GPU feature.
+`VkPipelineMultisampleStateCreateInfo`구조체는 멀티샘플링을 설정하는데, 이는 [안티앨리어싱(anti-aliasing)](https://en.wikipedia.org/wiki/Multisample_anti-aliasing)을 하는 방법 중 하나입니다. 이는 동일한 픽셀로 래스터화되는 여러 다각형의 프래그먼트 셰이더 결과를 결합하여 수행됩니다. 주로 모서리에서 수행되며, 모서리가 앨리어싱에 따른 문제가 가장 눈에 띄게 발생하는 부분입니다. 단일 다각형이 픽셀에 맵핑되는 경우에는 프래그먼트 셰이더를 여러 번 실행할 필요가 없기 때문에, 단순히 고해상도로 렌더링한 후에 다운샘플링(downsampling)하는 것 보다 훨씬 계산 비용이 낮습니다. 이를 사용하려면 GPU 기능을 활성화해야 합니다.
 
 ```c++
 VkPipelineMultisampleStateCreateInfo multisampling{};
@@ -271,29 +201,20 @@ multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
 multisampling.alphaToOneEnable = VK_FALSE; // Optional
 ```
 
-We'll revisit multisampling in later chapter, for now let's keep it disabled.
+멀티샘플링은 나중 챕터에서 다시 살펴볼 것이고, 지금은 활성화 하지 않은 상태로 두겠습니다.
 
-## Depth and stencil testing
+## 깊이와 스텐실(stencil) 테스트
 
-If you are using a depth and/or stencil buffer, then you also need to configure
-the depth and stencil tests using `VkPipelineDepthStencilStateCreateInfo`. We
-don't have one right now, so we can simply pass a `nullptr` instead of a pointer
-to such a struct. We'll get back to it in the depth buffering chapter.
+깊이 또는 스텐실 버퍼를 사용하는 경우 `VkPipelineDepthStencilStateCreateInfo`를 사용해 깊이와 스텐실 테스트를 설정해야 합니다. 지금은 그렇지 않으니 구조체에 대한 포인터 대신 `nullptr`를 전달합니다. 깊이 버퍼링 챕터에서 다시 사용할 것입니다.
 
-## Color blending
+## 컬러 블렌딩
 
-After a fragment shader has returned a color, it needs to be combined with the
-color that is already in the framebuffer. This transformation is known as color
-blending and there are two ways to do it:
+프래그먼트 셰이더가 값을 반환한 후에는 이미 프레임버퍼에 쓰여진 색상값과 결합되어야 합니다. 이러한 변환 과정은 컬러 블렌딩이라 하며 두 가지 방법이 있습니다:
 
-* Mix the old and new value to produce a final color
-* Combine the old and new value using a bitwise operation
+* 쓰여진 값과 새 값을 섞어 새로운 색상을 만듬
+* 쓰여진 값과 새 값을 비트 연산(bitwise operation)하여 결합
 
-There are two types of structs to configure color blending. The first struct,
-`VkPipelineColorBlendAttachmentState` contains the configuration per attached
-framebuffer and the second struct, `VkPipelineColorBlendStateCreateInfo`
-contains the *global* color blending settings. In our case we only have one
-framebuffer:
+컬러 블렌딩을 구성하는 두 종류의 구조체가 있습니다. 먼저 `VkPipelineColorBlendAttachmentState`는 부착(attach)된 프레임버퍼별 설정을 담은 구조체이며 `VkPipelineColorBlendStateCreateInfo`는 *전역(global)* 컬러 블렌딩 설정을 담고 있습니다. 우리는 하나의 프레임버퍼만 사용합니다:
 
 ```c++
 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
@@ -307,9 +228,7 @@ colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
 ```
 
-This per-framebuffer struct allows you to configure the first way of color
-blending. The operations that will be performed are best demonstrated using the
-following pseudocode:
+이 프레임버퍼별 구조체는 컬러 블렌딩의 첫 번째 방법을 설정할 수 있도록 합니다. 수행되는 연산은 다음과 같은 의사 코드(pseudocode)로 잘 설명됩니다:
 
 ```c++
 if (blendEnable) {
@@ -322,21 +241,16 @@ if (blendEnable) {
 finalColor = finalColor & colorWriteMask;
 ```
 
-If `blendEnable` is set to `VK_FALSE`, then the new color from the fragment
-shader is passed through unmodified. Otherwise, the two mixing operations are
-performed to compute a new color. The resulting color is AND'd with the
-`colorWriteMask` to determine which channels are actually passed through.
+`blendEnable`가 `VK_FALSE`면, 프래그먼트 셰이더에서 계산한 새로운 색상이 수정되지 않고 전달됩니다. 그렇지 않으면 새로운 색상을 위해 두 개의 결합(mix) 연산이 수행됩니다. 결과 색상은 `colorWriteMask`를 통해 명시된 채널들과 AND연산이 수행되어 전달될 채널이 결정됩니다.
 
-The most common way to use color blending is to implement alpha blending, where
-we want the new color to be blended with the old color based on its opacity. The
-`finalColor` should then be computed as follows:
+컬러 블렌딩을 하는 가장 보편적인 방법은 알파 블렌딩(alpha blending)입니다. 이는 새로운 색상이 이미 쓰여진 색상과 불투명도(opacity)를 기반으로 섞이는 것입니다. `finalColor`는 다음과 같이 계산됩니다:
 
 ```c++
 finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;
 finalColor.a = newAlpha.a;
 ```
 
-This can be accomplished with the following parameters:
+이는 아래와 같은 매개변수를 사용하면 수행됩니다:
 
 ```c++
 colorBlendAttachment.blendEnable = VK_TRUE;
@@ -348,12 +262,9 @@ colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
 ```
 
-You can find all of the possible operations in the `VkBlendFactor` and
-`VkBlendOp` enumerations in the specification.
+명세에 있는 `VkBlendFactor`와 `VkBlendOp` 열거형을 통해 모든 가능한 연산을 찾아볼 수 있습니다.
 
-The second structure references the array of structures for all of the
-framebuffers and allows you to set blend constants that you can use as blend
-factors in the aforementioned calculations.
+두 번째 구조체는 모든 프레임버퍼를 위한 구조체 배열의 참조이고, 앞서 언급한 계산들에 사용할 블렌드 팩터(blend factor)들로 사용될 블렌드 상수들을 설정할 수 있습니다.
 
 ```c++
 VkPipelineColorBlendStateCreateInfo colorBlending{};
@@ -368,35 +279,21 @@ colorBlending.blendConstants[2] = 0.0f; // Optional
 colorBlending.blendConstants[3] = 0.0f; // Optional
 ```
 
-If you want to use the second method of blending (bitwise combination), then you
-should set `logicOpEnable` to `VK_TRUE`. The bitwise operation can then be
-specified in the `logicOp` field. Note that this will automatically disable the
-first method, as if you had set `blendEnable` to `VK_FALSE` for every
-attached framebuffer! The `colorWriteMask` will also be used in this mode to
-determine which channels in the framebuffer will actually be affected. It is
-also possible to disable both modes, as we've done here, in which case the
-fragment colors will be written to the framebuffer unmodified.
+블렌딩의 두 번째 방법(비트 연산)을 하려면 `logicOpEnable`를 `VK_TRUE`로 설정해야 합니다. 그러면 비트 연산은 `logicOp` 필드에 명시됩니다. 주의하실 점은 이러한 경우 첫 번째 방법은 자동으로 비활성화 됩니다. 마치 여러분이 모든 프레임버퍼에 대해 `blendEnable`를 `VK_FALSE`로 설정한 것과 같이 말이죠! `colorWriteMask`또한 이 모드에서 프레임버퍼의 어떤 채널이 영향을 받을지를 결정하기 위해 사용됩니다. 지금 우리가 한 것처럼 두 모드를 모두 비활성화 하는 것도 가능합니다. 이러한 경우 프래그먼트 색상은 변경되지 않고 그대로 프레임버퍼에 쓰여집니다.
 
-## Pipeline layout
+## 파이프라인 레이아웃(layout)
 
-You can use `uniform` values in shaders, which are globals similar to dynamic
-state variables that can be changed at drawing time to alter the behavior of
-your shaders without having to recreate them. They are commonly used to pass the
-transformation matrix to the vertex shader, or to create texture samplers in the
-fragment shader.
+셰이더에서 사용하는 `uniform`값은 동적 상태 변수처럼 전역적인 값으로 셰이더를 재생성하지 않고 그리기 시점에 값을 변경하여 다른 동작을 하도록 할 수 있습니다. 이는 주로 변환 행렬을 정점 셰이더에 전달하거나, 프래그먼트 셰이더에 텍스처 샘플러(sampler)를 생성하기 위해 사용됩니다.
 
-These uniform values need to be specified during pipeline creation by creating a
-`VkPipelineLayout` object. Even though we won't be using them until a future
-chapter, we are still required to create an empty pipeline layout.
+이러한 uniform 값은 `VkPipelineLayout` 객체를 생성하여 파이프라인 생성 단계에서 명시되어야 합니다. 나중 챕터까지는 사용하지 않을 것이지만 빈 파이프라인 레이아웃이라도 생성해 두어야만 합니다.
 
-Create a class member to hold this object, because we'll refer to it from other
-functions at a later point in time:
+이 객체를 저장할 클래스 멤버를 만들 것인데, 나중에 다른 함수에서 참조할 것이기 때문입니다.
 
 ```c++
 VkPipelineLayout pipelineLayout;
 ```
 
-And then create the object in the `createGraphicsPipeline` function:
+그리고 `createGraphicsPipeline` 함수에서 객체를 만듭니다.
 
 ```c++
 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
@@ -411,10 +308,7 @@ if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout
 }
 ```
 
-The structure also specifies *push constants*, which are another way of passing
-dynamic values to shaders that we may get into in a future chapter. The pipeline
-layout will be referenced throughout the program's lifetime, so it should be
-destroyed at the end:
+구조체는 또한 *push 상수*를 명시하는데, 나중에 알아보겠지만 동적인 값을 셰이더에 전달하는 또 다른 방법입니다. 파이프라인 레이아웃은 프로그램의 실행 주기동안 참조되므로 마지막에는 소멸되어야 합니다:
 
 ```c++
 void cleanup() {
@@ -423,16 +317,11 @@ void cleanup() {
 }
 ```
 
-## Conclusion
+## 결론
 
-That's it for all of the fixed-function state! It's a lot of work to set all of
-this up from scratch, but the advantage is that we're now nearly fully aware of
-everything that is going on in the graphics pipeline! This reduces the chance of
-running into unexpected behavior because the default state of certain components
-is not what you expect.
+이로써 고정 함수 상태는 끝입니다! 이 모든 것들을 처음부터 만들어가는 과정은 힘들었지만, 그로 인해 그래픽스 파이프라인에서 일어나는 거의 모든 일들을 알게 되었습니다! 이러한 과정으로 인해 뜻밖의 오류가 발생할 가능성이 줄어들 것인데 특성 구성요소의 기본 상태를 제공한다면 그렇지 않았을 것입니다.
 
-There is however one more object to create before we can finally create the
-graphics pipeline and that is a [render pass](!en/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes).
+그래픽스 파이프라인 생성을 위해서는 아직도 하나 더 객체를 만들어야 하고, 이는 [렌더 패스(render pass)](!en/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes)입니다.
 
 [C++ code](/code/10_fixed_functions.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From fc353934c208005325da23d35ef4a9a13231c277 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 19 Oct 2023 21:56:55 +0900
Subject: [PATCH 18/47] kr translate 03-02-03 render passes

---
 .../03_Render_passes.md                       | 150 ++++++------------
 1 file changed, 46 insertions(+), 104 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
index a635d32f..b8ea6eb4 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
@@ -1,12 +1,6 @@
-## Setup
+## 설정
 
-Before we can finish creating the pipeline, we need to tell Vulkan about the
-framebuffer attachments that will be used while rendering. We need to specify
-how many color and depth buffers there will be, how many samples to use for each
-of them and how their contents should be handled throughout the rendering
-operations. All of this information is wrapped in a *render pass* object, for
-which we'll create a new `createRenderPass` function. Call this function from
-`initVulkan` before `createGraphicsPipeline`.
+파이프라인을 마무리 하기 전에, Vulkan에 렌더링을 할 때 사용할 프레임버퍼에 대해 알려줄 필요가 있습니다. 얼마나 많은 색상과 깊이 버퍼가 있을 것인지, 각각에 대해 얼마나 많은 샘플을 사용할 것인지, 렌더링 연산 과정에서 각각의 내용들이 어떻게 처리될 것인지 등을 명시해야 합니다. 이런 모든 정보가 *렌더 패스(render pass)* 객체에 포함되는데, 이를 위해 새롭게 `createRenderPass` 함수를 만들 겁니다. 이 함수를 `initVulkan` 함수에서 `createGraphicsPipeline` 전에 호출합니다.
 
 ```c++
 void initVulkan() {
@@ -28,10 +22,9 @@ void createRenderPass() {
 }
 ```
 
-## Attachment description
+## 어태치먼트(attachment) 기술
 
-In our case we'll have just a single color buffer attachment represented by one
-of the images from the swap chain.
+우리의 경우 스왑 체인 안에 있는 이미지들 중 하나로 하나의 색상 버퍼 어태치먼트만 있을 것입니다.
 
 ```c++
 void createRenderPass() {
@@ -41,89 +34,56 @@ void createRenderPass() {
 }
 ```
 
-The `format` of the color attachment should match the format of the swap chain
-images, and we're not doing anything with multisampling yet, so we'll stick to 1
-sample.
+색상 어태치먼트의 `format`과 스왑 체인의 이미지 포맷은 동일해야 하며, 지금은 멀티샘플링을 하지 않으니 1개의 샘플을 사용합니다.
 
 ```c++
 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
 ```
 
-The `loadOp` and `storeOp` determine what to do with the data in the attachment
-before rendering and after rendering. We have the following choices for
-`loadOp`:
+`loadOp` 과 `storeOp`은 렌더링 전과 후에 데이터로 어떤 작업을 할 것인지를 결정합니다. `loadOp`과 관련해서는 다음과 같은 선택지가 있습니다:
 
-* `VK_ATTACHMENT_LOAD_OP_LOAD`: Preserve the existing contents of the attachment
-* `VK_ATTACHMENT_LOAD_OP_CLEAR`: Clear the values to a constant at the start
-* `VK_ATTACHMENT_LOAD_OP_DONT_CARE`: Existing contents are undefined; we don't
-care about them
+* `VK_ATTACHMENT_LOAD_OP_LOAD`: 어태치먼트에 존재하는 내용을 유지
+* `VK_ATTACHMENT_LOAD_OP_CLEAR`: 시작 시 상수값으로 내용을 채움
+* `VK_ATTACHMENT_LOAD_OP_DONT_CARE`: 어떤 내용이 존재하는지 정의할 수 없음. 신경쓰지 않음
 
-In our case we're going to use the clear operation to clear the framebuffer to
-black before drawing a new frame. There are only two possibilities for the
-`storeOp`:
+우리의 경우 새로운 프레임을 그리기 전에 clear 연산을 통해 검은 색으로 프레임버퍼를 지울 것입니다. `storeOp`의 선택지는 두 가지입니다:
 
-* `VK_ATTACHMENT_STORE_OP_STORE`: Rendered contents will be stored in memory and
-can be read later
-* `VK_ATTACHMENT_STORE_OP_DONT_CARE`: Contents of the framebuffer will be
-undefined after the rendering operation
+* `VK_ATTACHMENT_STORE_OP_STORE`: 렌더링된 내용이 메모리에 저장되고 나중에 읽을 수 있음
+* `VK_ATTACHMENT_STORE_OP_DONT_CARE`: 프레임버퍼의 내용이 렌더링 이후에도 정의되지 않음으로 남아 있음
 
-We're interested in seeing the rendered triangle on the screen, so we're going
-with the store operation here.
+우리는 화면에 삼각형을 그리고 그걸 확인하는 데 관심이 있으니 store 연산을 사용할 것입니다.
 
 ```c++
 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
 ```
 
-The `loadOp` and `storeOp` apply to color and depth data, and `stencilLoadOp` /
-`stencilStoreOp` apply to stencil data. Our application won't do anything with
-the stencil buffer, so the results of loading and storing are irrelevant.
+`loadOp`과 `storeOp`는 색상과 깊이 데이터에 대해 적용되고 `stencilLoadOp` /
+`stencilStoreOp`은 스텐실 데이터에 적용됩니다. 우리 프로그램은 스텐실 버퍼에 대해 아무런 작업을 하지 않기 때문에 이에 대한 로드와 저장은 신경쓰지 않습니다.
 
 ```c++
 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
 ```
 
-Textures and framebuffers in Vulkan are represented by `VkImage` objects with a
-certain pixel format, however the layout of the pixels in memory can change
-based on what you're trying to do with an image.
-
-Some of the most common layouts are:
-
-* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Images used as color attachment
-* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Images to be presented in the swap chain
-* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Images to be used as destination for a
-memory copy operation
-
-We'll discuss this topic in more depth in the texturing chapter, but what's
-important to know right now is that images need to be transitioned to specific
-layouts that are suitable for the operation that they're going to be involved in
-next.
-
-The `initialLayout` specifies which layout the image will have before the render
-pass begins. The `finalLayout` specifies the layout to automatically transition
-to when the render pass finishes. Using `VK_IMAGE_LAYOUT_UNDEFINED` for
-`initialLayout` means that we don't care what previous layout the image was in.
-The caveat of this special value is that the contents of the image are not
-guaranteed to be preserved, but that doesn't matter since we're going to clear
-it anyway. We want the image to be ready for presentation using the swap chain
-after rendering, which is why we use `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` as
-`finalLayout`.
-
-## Subpasses and attachment references
-
-A single render pass can consist of multiple subpasses. Subpasses are subsequent
-rendering operations that depend on the contents of framebuffers in previous
-passes, for example a sequence of post-processing effects that are applied one
-after another. If you group these rendering operations into one render pass,
-then Vulkan is able to reorder the operations and conserve memory bandwidth for
-possibly better performance. For our very first triangle, however, we'll stick
-to a single subpass.
-
-Every subpass references one or more of the attachments that we've described
-using the structure in the previous sections. These references are themselves
-`VkAttachmentReference` structs that look like this:
+Vulkan의 텍스처와 프레임버퍼는 특정 픽셀 포맷의 `VkImage` 객체로 표현됩니다. 하지만 픽셀의 메모리 레이아웃은 이미지를 어디에 사용하느냐에 따라 달라질 수 있습니다.
+
+흔히 사용되는 레이아웃은 아래와 같습니다:
+
+* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: 이미지가 색상 어태치먼트로 사용됨
+* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: 스왑 체인을 통해 표시될 이미지
+* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: 메모리 복사의 대상으로 사용되는 이미지
+
+텍스처링(texturing) 챕터에서 이 주제에 대해 보다 자세히 논의할 것입니다. 지금 알아두셔야 할 중요한 사항은 이미지는 그 이후에 사용될 작업과 관련한 연산에 어울리는 특정한 레이아웃으로 정의해야 한다는 것입니다.
+
+`initLayout`은 렌더 패스가 시작되기 전 이미지가 어떤 레이아웃일지 명시합니다. `finalLayout`은 렌더 패스가 끝나면 자동적으로 어떤 레이아웃으로 사용될지를 명시합니다. `initLayout`에 `VK_IMAGE_LAYOUT_UNDEFINED`를 사용하면 우리는 그 이미지가 전에 어떤 레이아웃이었는지 신경쓰지 않겠다는 뜻입니다. 이 경우의 단점은 이미지의 내용이 보존된다는 보장이 없다는 것이지만 지금 우리는 어차피 내용을 지우고 시작할 것이니 관계 없습니다. 렌더링 이후에 스왑 체인을 사용해 이미지를 표시할 것이니 `finalLayout`으로는 `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`를 사용합니다.
+
+## 서브패스(subpass)와 어태치먼트 참조
+
+라나의 렌더 패스는 여러 서브패스로 구성될 수 있습니다. 서브패스는 이전 패스에서 저장된 프레임버퍼의 내용을 가지고 렌더링 연산을 수행하는 일련의 패스입니다. 예를 들어 여러 후처리(post-processing) 과정을 연속적으로 적용하는 경우를 들 수 있습니다. 이러한 렌더링 연산들의 집합을 하나의 렌더 패스에 넣으면, Vulkan이 그 순서를 조정해 메모리 대역폭을 아껴서 보다 나은 성능을 얻을 수 있습니다. 하지만 우리의 삼각형 같은 경우 하나의 서브패스만 있으면 됩니다.
+
+모든 서브패스는 위와 같은 내용에 우리가 이전 장에서 만든 하나 이상의 어태치먼트 구조체를 참조합니다. 이 참조는 `VkAttachmentReference` 구조체로 아래와 같습니다.
 
 ```c++
 VkAttachmentReference colorAttachmentRef{};
@@ -131,57 +91,41 @@ colorAttachmentRef.attachment = 0;
 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 ```
 
-The `attachment` parameter specifies which attachment to reference by its index
-in the attachment descriptions array. Our array consists of a single
-`VkAttachmentDescription`, so its index is `0`. The `layout` specifies which
-layout we would like the attachment to have during a subpass that uses this
-reference. Vulkan will automatically transition the attachment to this layout
-when the subpass is started. We intend to use the attachment to function as a
-color buffer and the `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` layout will give
-us the best performance, as its name implies.
+`attachment` 매개변수는 인덱스를 사용해 어태치먼트 배열로부터 참조할 어태치먼트를 명시합니다. 우리 배열은 하나의 `VkAttachmentDescription`만 가지고 있으므로 인덱스는 `0`입니다. `layout`은 서브패스 실행 동안 이 참조를 사용하고자 하는 어태치먼트의 레이아웃을 명시합니다. Vulkan은 서브패스가 시작되면 자동적으로 어태치먼트를 이 레이아웃으로 전환합니다. 우리는 어태치먼트가 색상 버퍼의 기능을 하길 원하고 `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`가 이름 그대로 가장 좋은 성능을 내줄 겁니다.
 
-The subpass is described using a `VkSubpassDescription` structure:
+서브패스는 `VkSubpassDescription` 구조체를 사용해 기술됩니다:
 
 ```c++
 VkSubpassDescription subpass{};
 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
 ```
 
-Vulkan may also support compute subpasses in the future, so we have to be
-explicit about this being a graphics subpass. Next, we specify the reference to
-the color attachment:
+Vulkan이 나중에는 계산을 위한 서브패스도 지원할 지 모르므로, 그래픽스 서브패스를 위한 것이면 이를 명시해야 합니다. 다음으로 색상 어태치먼트의 참조를 명시합니다:
 
 ```c++
 subpass.colorAttachmentCount = 1;
 subpass.pColorAttachments = &colorAttachmentRef;
 ```
 
-The index of the attachment in this array is directly referenced from the
-fragment shader with the `layout(location = 0) out vec4 outColor` directive!
+이 배열의 어태치먼트의 인덱스는 프래그먼트 셰이더에서 직접 `layout(location = 0) out vec4 outColor` 지시자를 통해 참조됩니다!
 
-The following other types of attachments can be referenced by a subpass:
+다른 어태치먼트 타입들이 서브패스로부터 참조될 수 있습니다:
 
-* `pInputAttachments`: Attachments that are read from a shader
-* `pResolveAttachments`: Attachments used for multisampling color attachments
-* `pDepthStencilAttachment`: Attachment for depth and stencil data
-* `pPreserveAttachments`: Attachments that are not used by this subpass, but for
-which the data must be preserved
+* `pInputAttachments`: 셰이서에서 읽어온 어태치먼트
+* `pResolveAttachments`: 색상 어태치먼트의 멀티샘플링을 위한 어태치먼트
+* `pDepthStencilAttachment`: 깊이와 스텐실 데이터를 위한 어태치먼트
+* `pPreserveAttachments`: 이 서브패스에는 사용되지 않지만 데이터가 보존되어야 하는 어태치먼트
 
-## Render pass
+## 렌더 패스
 
-Now that the attachment and a basic subpass referencing it have been described,
-we can create the render pass itself. Create a new class member variable to hold
-the `VkRenderPass` object right above the `pipelineLayout` variable:
+이제 어태치먼트와 기본적인 서브패스 참조가 명시되었으므로 렌더 패스를 만들 수 있습니다. `VkRenderPass` 객체를 저장하기 위한 새로운 클래스 멤버를 `pipelineLayout` 위에 정의합니다.
 
 ```c++
 VkRenderPass renderPass;
 VkPipelineLayout pipelineLayout;
 ```
 
-The render pass object can then be created by filling in the
-`VkRenderPassCreateInfo` structure with an array of attachments and subpasses.
-The `VkAttachmentReference` objects reference attachments using the indices of
-this array.
+렌더 패스 객체는 `VkRenderPassCreateInfo`구조체에 어태치먼트 배열과 서브패스들을 채워서 생성합니다. `VkRenderPassCreateInfo`객체는 그 배열의 인덱스를 명시함으로써 어태치먼트를 참조합니다.
 
 ```c++
 VkRenderPassCreateInfo renderPassInfo{};
@@ -196,8 +140,7 @@ if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCC
 }
 ```
 
-Just like the pipeline layout, the render pass will be referenced throughout the
-program, so it should only be cleaned up at the end:
+파이프라인 레이아웃처럼, 렌더 패스도 프로그램 실행 중 계속 참조되므로 프로그램 종료 시점에 해제되어야 합니다:
 
 ```c++
 void cleanup() {
@@ -207,8 +150,7 @@ void cleanup() {
 }
 ```
 
-That was a lot of work, but in the next chapter it all comes together to finally
-create the graphics pipeline object!
+많은 작업을 했는데요, 이제 다음 챕터에서 모든 것들을 합쳐서 드디어 그래픽스 파이프라인 객체를 만들 것입니다!
 
 [C++ code](/code/11_render_passes.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From a0e18f40650b0ed0478f37236d0272d12e01a8ca Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 19 Oct 2023 22:19:16 +0900
Subject: [PATCH 19/47] kr translate 03-02-03 pipeline, conclusion

---
 .../04_Conclusion.md                          | 79 +++++--------------
 1 file changed, 21 insertions(+), 58 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
index 4a16585e..65ce8dea 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
@@ -1,20 +1,11 @@
-We can now combine all of the structures and objects from the previous chapters
-to create the graphics pipeline! Here's the types of objects we have now, as a
-quick recap:
-
-* Shader stages: the shader modules that define the functionality of the
-programmable stages of the graphics pipeline
-* Fixed-function state: all of the structures that define the fixed-function
-stages of the pipeline, like input assembly, rasterizer, viewport and color
-blending
-* Pipeline layout: the uniform and push values referenced by the shader that can
-be updated at draw time
-* Render pass: the attachments referenced by the pipeline stages and their usage
-
-All of these combined fully define the functionality of the graphics pipeline,
-so we can now begin filling in the `VkGraphicsPipelineCreateInfo` structure at
-the end of the `createGraphicsPipeline` function. But before the calls to 
-`vkDestroyShaderModule` because these are still to be used during the creation.
+이제 이전 장에서 만든 모든 구조체와 객체들을 사용해 그래픽스 파이프라인을 만들 것입니다! 복습으로 우리가 가진 객체들의 종류를 되돌아봅시다:
+
+* 셰이더 단계: 그래픽스 파이프라인 내의 프로그램 가능한 단계들의 기능을 정의하는 셰이더 모듈
+* 고정 함수 상태: 파이프라인의 고정함수 단계들을 정의하는 구조체들. 여기에는 입력 조립기, 래스터화, 뷰포트와 컬러 블렌딩이 포함됨
+* 파이프라인 레이아웃: 셰이더가 참조하는, 그리기 시점에 갱신될 수 있는 uniform과 push 값들
+* 렌더 패스: 파이프라인에서 참조하는 어태치먼트들과 그 사용 용도
+
+이 것들이 모여 그래픽스 파이프라인의 기능을 완전히 명시합니다. 이제 우리는 `createGraphicsPipeline` 함수의 마지막 부분에 `VkGraphicsPipelineCreateInfo` 구조체를 만들 수 있습니다. `vkDestroyShaderModule` 보다는 전이어야 하는데 이것들이 생성 과정에서 사용되기 때문입니다.
 
 ```c++
 VkGraphicsPipelineCreateInfo pipelineInfo{};
@@ -23,7 +14,7 @@ pipelineInfo.stageCount = 2;
 pipelineInfo.pStages = shaderStages;
 ```
 
-We start by referencing the array of `VkPipelineShaderStageCreateInfo` structs.
+`VkPipelineShaderStageCreateInfo`구조체의 배열을 참조하는 것으로 시작합니다.
 
 ```c++
 pipelineInfo.pVertexInputState = &vertexInputInfo;
@@ -36,52 +27,36 @@ pipelineInfo.pColorBlendState = &colorBlending;
 pipelineInfo.pDynamicState = &dynamicState;
 ```
 
-Then we reference all of the structures describing the fixed-function stage.
+그리고 고정함수 단계를 기술하는 구조체들을 참조합니다.
 
 ```c++
 pipelineInfo.layout = pipelineLayout;
 ```
 
-After that comes the pipeline layout, which is a Vulkan handle rather than a
-struct pointer.
+다음으로 파이프라인 레이아웃으로 구조체에 대한 포인터가 아닌 Vulkan 핸들입니다.
 
 ```c++
 pipelineInfo.renderPass = renderPass;
 pipelineInfo.subpass = 0;
 ```
 
-And finally we have the reference to the render pass and the index of the sub
-pass where this graphics pipeline will be used. It is also possible to use other
-render passes with this pipeline instead of this specific instance, but they
-have to be *compatible* with `renderPass`. The requirements for compatibility
-are described [here](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility),
-but we won't be using that feature in this tutorial.
+마지막으로 그래픽스 파이프라인이 사용할 렌더 패스에 대한 참조와 서브패스 인덱스가 있습니다. 이 특정 인스턴스가 아닌 다른 렌더 패스를 사용할 수도 있지만 그러한 경우 그것들이 `renderPass`와 *호환되어야 합니다*. 호환성에 대해서는 [여기](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility)에 설명되어 있지만 그러한 기능은 이 튜토리얼에서는 사용하지 않을 겁니다.
 
 ```c++
 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
 pipelineInfo.basePipelineIndex = -1; // Optional
 ```
 
-There are actually two more parameters: `basePipelineHandle` and
-`basePipelineIndex`. Vulkan allows you to create a new graphics pipeline by
-deriving from an existing pipeline. The idea of pipeline derivatives is that it
-is less expensive to set up pipelines when they have much functionality in
-common with an existing pipeline and switching between pipelines from the same
-parent can also be done quicker. You can either specify the handle of an
-existing pipeline with `basePipelineHandle` or reference another pipeline that
-is about to be created by index with `basePipelineIndex`. Right now there is
-only a single pipeline, so we'll simply specify a null handle and an invalid
-index. These values are only used if the `VK_PIPELINE_CREATE_DERIVATIVE_BIT`
-flag is also specified in the `flags` field of `VkGraphicsPipelineCreateInfo`.
-
-Now prepare for the final step by creating a class member to hold the
-`VkPipeline` object:
+두 개의 매개변수가 사실 더 있습니다. `basePipelineHandle`와
+`basePipelineIndex` 입니다. Vulkan에서는 기존 파이프라인으로부터 새로운 그래픽스 파이프라인을 만들 수도 있습니다. 이러한 파이프라인 유도(derivative)는 대부분의 기능이 비슷한 파이프라인을 설정하는 데 성능적인 이점이 있고, 같은 부모로부터 유도된 파이프라인으로 교체하는 것은 더 빠르게 수행될 수 있습니다. 기존 파이프라인의 핸들을 `basePipelineHandle`에 명시하거나 곧 생성할 파리프라인의 인덱스를 `basePipelineIndex`를 사용해 참조할 수 있습니다. 지금은 하나의 파이프라인만 있으므로 널 핸들과 유효하지 않은 인덱스로 설정해 둡니다. 이러한 기능은 `VkGraphicsPipelineCreateInfo`의 `flag` 필드에 `VK_PIPELINE_CREATE_DERIVATIVE_BIT`가 명시되어 있어야만 사용할 수 있습니다.
+
+마지막으로 `VkPipeline` 객체를 저장할 클래스 멤버를 준비해 둡시다:
 
 ```c++
 VkPipeline graphicsPipeline;
 ```
 
-And finally create the graphics pipeline:
+그리고 최종적으로 그래픽스 파이프라인을 만듭니다:
 
 ```c++
 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
@@ -89,20 +64,11 @@ if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr,
 }
 ```
 
-The `vkCreateGraphicsPipelines` function actually has more parameters than the
-usual object creation functions in Vulkan. It is designed to take multiple
-`VkGraphicsPipelineCreateInfo` objects and create multiple `VkPipeline` objects
-in a single call.
+`vkCreateGraphicsPipelines`함수는 Vulkan의 다른 객체들을 만들 떄보다 더 많은 매개별수를 받습니다. 한 번의 호출로 여러 개의 `VkGraphicsPipelineCreateInfo`를 받아 여러 개의 `VkPipeline`객체를 만들 수 있게 되어있습니다.
 
-The second parameter, for which we've passed the `VK_NULL_HANDLE` argument,
-references an optional `VkPipelineCache` object. A pipeline cache can be used to
-store and reuse data relevant to pipeline creation across multiple calls to
-`vkCreateGraphicsPipelines` and even across program executions if the cache is
-stored to a file. This makes it possible to significantly speed up pipeline
-creation at a later time. We'll get into this in the pipeline cache chapter.
+`VK_NULL_HANDLE`를 념겨준 두 번째 매개변수는 선택적으로 `VkPipelineCache`객체에 대한 참조를 넘겨줄 수 있습니다. 파이프라인 캐시(cache)는 여러 `vkCreateGraphicsPipelines` 호출을 위해, 파이프라인 생성을 위한 데이터를 저장하고 재사용하는데 사용될 수 있습니다. 만일 캐시가 파일로 저장되어 있다면 다른 프로그램에서도 사용될 수 있습니다. 이렇게 하면 나중에 파이프라인 생성을 위해 소요되는 시간을 눈에 띄게 줄일 수 있습니다. 이에 대해서는 파이프라인 캐시 챕터에서 살펴보겠습니다.
 
-The graphics pipeline is required for all common drawing operations, so it
-should also only be destroyed at the end of the program:
+모든 그리기 연산을 위해서는 그래픽스 파이프라인이 필요하므로 프로그램이 종료될 때에만 해제되어야 합니다.
 
 ```c++
 void cleanup() {
@@ -112,10 +78,7 @@ void cleanup() {
 }
 ```
 
-Now run your program to confirm that all this hard work has resulted in a
-successful pipeline creation! We are already getting quite close to seeing
-something pop up on the screen. In the next couple of chapters we'll set up the
-actual framebuffers from the swap chain images and prepare the drawing commands.
+이제 프로그램을 실행하고 작업에 대한 보상으로 성공적으로 파이프라인이 만들어졌는지 확인하세요! 이제 무언가 화면에 나오기까지 얼마 남지 않았습니다. 다음 몇 개 챕터에서는 스왑 체인으로부터 실제 프레임버퍼를 설정하고 그리기 명령을 준비해 보겠습니다.
 
 [C++ code](/code/12_graphics_pipeline_complete.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From 8a05a13f1878b798f42ca7a85d230f9455b762c7 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sat, 21 Oct 2023 13:33:53 +0900
Subject: [PATCH 20/47] kr translate 03-03-00 framebuffers

---
 .../03_Drawing/00_Framebuffers.md             | 42 +++++--------------
 1 file changed, 11 insertions(+), 31 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
index bf7f84a7..53e04156 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
@@ -1,24 +1,14 @@
-We've talked a lot about framebuffers in the past few chapters and we've set up
-the render pass to expect a single framebuffer with the same format as the swap
-chain images, but we haven't actually created any yet.
+지난 몇 개 챕터에서 프레임버퍼에 관련한 이야기를 많이 언급했고, 이제 설정을 마친 렌더 패스는 스왑 체인 이미지와 동일한 포맷을 가진 하나의 프레임버퍼를 예정하여 만들어졌습니다. 하지만 프레임버퍼를 실제로 만들지는 않았습니다.
 
-The attachments specified during render pass creation are bound by wrapping them
-into a `VkFramebuffer` object. A framebuffer object references all of the
-`VkImageView` objects that represent the attachments. In our case that will be
-only a single one: the color attachment. However, the image that we have to use
-for the attachment depends on which image the swap chain returns when we retrieve one
-for presentation. That means that we have to create a framebuffer for all of the
-images in the swap chain and use the one that corresponds to the retrieved image
-at drawing time.
+렌더 패스 생성 중 명시한 어태치먼트는 `VkFramebuffer` 객체로 래핑하여 설정되었습니다. 프레임버퍼 객체는 어태치먼트를 표현하는 모든 `VkImageView` 객체들을 참조합니다. 우리의 경우 색상 어태치먼트 하나입니다. 하지만 우리가 어태치먼트로 사용해야 하는 이미지의 개수는 표현하고자 하는 이미지를 스왑 체인으로부터 반환 받을 때 몇 개의 이미지가 반환되느냐에 달려 있습니다. 즉, 우리는 스왑 체인에 있는 모든 이미지에 대해 프레임버퍼를 생성해야 하고 그리기 시점에 출력된 이미지와 관계된 이미지를 사용해야 합니다.
 
-To that end, create another `std::vector` class member to hold the framebuffers:
+이를 위해 프레임버퍼를 저장할 `std::vector` 클래스 멤버를 하나 더 추가합니다.
 
 ```c++
 std::vector<VkFramebuffer> swapChainFramebuffers;
 ```
 
-We'll create the objects for this array in a new function `createFramebuffers`
-that is called from `initVulkan` right after creating the graphics pipeline:
+이 배열을 위한 객체를 `initVulkan`의 그래픽스 파이프라인 생성 뒤에서 호출되는 `createFramebuffers`라는 새로운 함수를 통해 생성합니다.
 
 ```c++
 void initVulkan() {
@@ -41,7 +31,7 @@ void createFramebuffers() {
 }
 ```
 
-Start by resizing the container to hold all of the framebuffers:
+모든 프레임버퍼가 저장될 수 있도록 컨테이너의 크기를 조정하는 것부터 시작합니다:
 
 ```c++
 void createFramebuffers() {
@@ -49,7 +39,7 @@ void createFramebuffers() {
 }
 ```
 
-We'll then iterate through the image views and create framebuffers from them:
+그리고 이미지 뷰를 순회하며 그로부터 프레임버퍼를 생성합니다:
 
 ```c++
 for (size_t i = 0; i < swapChainImageViews.size(); i++) {
@@ -72,21 +62,13 @@ for (size_t i = 0; i < swapChainImageViews.size(); i++) {
 }
 ```
 
-As you can see, creation of framebuffers is quite straightforward. We first need
-to specify with which `renderPass` the framebuffer needs to be compatible. You
-can only use a framebuffer with the render passes that it is compatible with,
-which roughly means that they use the same number and type of attachments.
+보시는 것처럼 프레임버퍼의 생성은 굉장히 직관적입니다. 먼저 어떤 `renderPass`에 프레임버퍼가 호환되어야 하는지부터 명시합니다. 렌더 패스에 호환되는 프레임버퍼만 사용할 수 있는데, 일단 일반적으로 어태치먼트의 타입과 개수가 같아야 합니다.
 
-The `attachmentCount` and `pAttachments` parameters specify the `VkImageView`
-objects that should be bound to the respective attachment descriptions in
-the render pass `pAttachment` array.
+`attachmentCount`와 `pAttachments` 매개변수가 렌더 패스의 `pAttachment`배열에서 기술된 해당하는 어태치먼트와 바인딩되어야 하는 `VkImageView` 객체를 명시합니다.
 
-The `width` and `height` parameters are self-explanatory and `layers` refers to
-the number of layers in image arrays. Our swap chain images are single images,
-so the number of layers is `1`.
+`width` 와 `height` 매개변수는 이름 그대로고 `layers`는 이미지 배열의 레이어 개수를 의미합니다. 우리의 스왑 체인 이미지는 하나이므로 레이어 개수는 `1`입니다.
 
-We should delete the framebuffers before the image views and render pass that
-they are based on, but only after we've finished rendering:
+프레임버퍼는 이를 기반으로 하는 이미지 뷰와 렌더 패스가 해제되기 전에 해제해야 하고, 렌더링이 끝난 후에 해제해야 합니다:
 
 ```c++
 void cleanup() {
@@ -98,9 +80,7 @@ void cleanup() {
 }
 ```
 
-We've now reached the milestone where we have all of the objects that are
-required for rendering. In the next chapter we're going to write the first
-actual drawing commands.
+이제 렌더링에 필요한 모든 객체의 생성이라는 목표를 달성했습니다. 다음 장에서는 실제 그리기 명령을 처음으로 작성해 보겠습니다.
 
 [C++ code](/code/13_framebuffers.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From 81cee1351dcd1c453124e50ff2e8f012db74c722 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 23 Oct 2023 21:05:38 +0900
Subject: [PATCH 21/47] kr translate 03-03-01 drawing, framebuffers

---
 .../03_Drawing/00_Framebuffers.md             | 42 +++++--------------
 1 file changed, 11 insertions(+), 31 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
index bf7f84a7..f4a1c37e 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
@@ -1,24 +1,14 @@
-We've talked a lot about framebuffers in the past few chapters and we've set up
-the render pass to expect a single framebuffer with the same format as the swap
-chain images, but we haven't actually created any yet.
+지난 몇 개의 챕터에서 프레임버퍼와 관련한 많은 것들을 이야기 했고 스왑 체인 이미지와 같은 포맷인, 단일 프레임버퍼를 사용할 렌더 패스를 설정했습니다. 하지만 아직 생성은 하지 않았습니다.
 
-The attachments specified during render pass creation are bound by wrapping them
-into a `VkFramebuffer` object. A framebuffer object references all of the
-`VkImageView` objects that represent the attachments. In our case that will be
-only a single one: the color attachment. However, the image that we have to use
-for the attachment depends on which image the swap chain returns when we retrieve one
-for presentation. That means that we have to create a framebuffer for all of the
-images in the swap chain and use the one that corresponds to the retrieved image
-at drawing time.
+렌더 패스 생성 과정에서 명시한 어태치먼트는 `VkFramebuffer` 객체로 래핑하여 바인딩됩니다. 프레임버퍼 객체는 어태치먼트를 표현하는 모든 `VkImageView` 객체를 참조합니다. 우리의 경우 어태치먼트는 색상 어태치먼트 하나입니다. 하지만 어태치먼트로 사용할 이미지는 우리가 화면에 표시하기 위한 이미지로 스왑 체인이 어떠한 이미지를 반환했냐에 달려 있습니다. 즉, 우리는 스왑 체인에 있는 모든 이미지에 대해 프레임버퍼를 만들어야 하고, 그리기 시점에는 그 중 하나를 선택해서 사용해야 합니다.
 
-To that end, create another `std::vector` class member to hold the framebuffers:
+이를 위해 프레임버퍼를 저장한 `std::vector` 클래스 멤버를 하나 더 만듭니다:
 
 ```c++
 std::vector<VkFramebuffer> swapChainFramebuffers;
 ```
 
-We'll create the objects for this array in a new function `createFramebuffers`
-that is called from `initVulkan` right after creating the graphics pipeline:
+이 배열을 위한 객체는 `initVulkan`에서 호출할 새로운 `createFramebuffers`함수에서 만들 것입니다. 그래픽스 파이프라인 생성 이후에 호출합니다:
 
 ```c++
 void initVulkan() {
@@ -41,7 +31,7 @@ void createFramebuffers() {
 }
 ```
 
-Start by resizing the container to hold all of the framebuffers:
+모든 프레임버퍼를 저장할 수 있도록 컨테이너 크기부터 조정합니다:
 
 ```c++
 void createFramebuffers() {
@@ -49,7 +39,7 @@ void createFramebuffers() {
 }
 ```
 
-We'll then iterate through the image views and create framebuffers from them:
+그리고 이미지 뷰를 순회하면서 이를 기반으로 프레임버퍼를 만듭니다:
 
 ```c++
 for (size_t i = 0; i < swapChainImageViews.size(); i++) {
@@ -72,21 +62,13 @@ for (size_t i = 0; i < swapChainImageViews.size(); i++) {
 }
 ```
 
-As you can see, creation of framebuffers is quite straightforward. We first need
-to specify with which `renderPass` the framebuffer needs to be compatible. You
-can only use a framebuffer with the render passes that it is compatible with,
-which roughly means that they use the same number and type of attachments.
+보이시는 것처럼 프레임버퍼의 생성은 매우 직관적입니다. 먼저 어떤 `renderPass`에 프레임버퍼가 호환되어야 하는지 명시합니다. 호환되는 경우에만 렌더 패스에 프레임버퍼를 사용할 수 있는데, 같은 수와 타입의 어태치먼트를 사용해야 호환됩니다.
 
-The `attachmentCount` and `pAttachments` parameters specify the `VkImageView`
-objects that should be bound to the respective attachment descriptions in
-the render pass `pAttachment` array.
+`attachmentCount`와 `pAttachments` 매개변수는 렌더 패스의 `pAttachment` 배열에서, 해당하는 어태치먼트 기술자와 바인딩될 `VkImageView` 객체를 명시합니다.
 
-The `width` and `height` parameters are self-explanatory and `layers` refers to
-the number of layers in image arrays. Our swap chain images are single images,
-so the number of layers is `1`.
+`width` 와 `height` 매개변수는 설명하지 않아도 될 것 같고, `layers`는 이미지 배열의 레이어 수를 의미합니다. 우리 스왑 체인 이미지는 하나이므로, 레이어 수도 `1`입니다.
 
-We should delete the framebuffers before the image views and render pass that
-they are based on, but only after we've finished rendering:
+프레임버퍼는 렌더링이 모두 완료된 후에, 이를 사용하는 이미지 뷰와 렌더 패스보다 먼저 해제되어야 합니다:
 
 ```c++
 void cleanup() {
@@ -98,9 +80,7 @@ void cleanup() {
 }
 ```
 
-We've now reached the milestone where we have all of the objects that are
-required for rendering. In the next chapter we're going to write the first
-actual drawing commands.
+이제 렌더링을 위해 필요한 모든 객체를 만들었습니다. 이제 다음 챕터에서는 실제 그리기 명령을 작성해 보도록 하겠습니다.
 
 [C++ code](/code/13_framebuffers.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From f9317ab663aaa125ed4431049b853e284ed4a2c4 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 23 Oct 2023 22:13:15 +0900
Subject: [PATCH 22/47] kr translate 03-03-02 drawing, command buffers

---
 .../03_Drawing/01_Command_buffers.md          | 188 +++++-------------
 1 file changed, 55 insertions(+), 133 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md b/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
index 61a40b4f..8cacaadb 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
@@ -1,23 +1,14 @@
-Commands in Vulkan, like drawing operations and memory transfers, are not
-executed directly using function calls. You have to record all of the operations
-you want to perform in command buffer objects. The advantage of this is that when
-we are ready to tell the Vulkan what we want to do, all of the commands are
-submitted together and Vulkan can more efficiently process the commands since all
-of them are available together. In addition, this allows command recording to
-happen in multiple threads if so desired.
+그리기 명령이나 메모리 전송과 같은 Vulkan의 명령(command)은 함수 호출을 통해 직접 수행되는 것이 아닙니다. 수행하고자 하는 연산들을 모두 명령 버퍼 객체에 먼저 기록해야 합니다. 이로 인해 Vulkan에게 우리가 하고자 하는 것들을 알려줄 준비가 완료되었다면, 모든 명령이 한꺼번에 Vulkan으로 제출(submit)되어 동시에 실행 가능한 상태가 된다는 것입니다. 또한 원한다면 여러 쓰레드에서 명령을 기록할 수 있다는 장점도 있습니다.
 
-## Command pools
+## 명령 풀(Command pools)
 
-We have to create a command pool before we can create command buffers. Command
-pools manage the memory that is used to store the buffers and command buffers
-are allocated from them. Add a new class member to store a `VkCommandPool`:
+명령 버퍼를 만드려면 먼저 명령 풀부터 만들어야 합니다. 명령 풀은 명령 버퍼로 할당될 버퍼의 메모리를 관리합니다. `VkCommandPool`을 저장할 새 클래스 멤버를 추가합니다:
 
 ```c++
 VkCommandPool commandPool;
 ```
 
-Then create a new function `createCommandPool` and call it from `initVulkan`
-after the framebuffers were created.
+그리고 `initVulkan`의 프레임버퍼 생성 이후에 호출할 `createCommandPool` 함수를 새로 만듭니다.
 
 ```c++
 void initVulkan() {
@@ -41,7 +32,7 @@ void createCommandPool() {
 }
 ```
 
-Command pool creation only takes two parameters:
+명렬 풀 생성에는 두 개의 매개변수만 필요합니다:
 
 ```c++
 QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
@@ -52,23 +43,14 @@ poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
 poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
 ```
 
-There are two possible flags for command pools:
+명령 풀에는 두가지 가능한 플래그가 존재합니다:
 
-* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT`: Hint that command buffers are
-rerecorded with new commands very often (may change memory allocation behavior)
-* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`: Allow command buffers to be
-rerecorded individually, without this flag they all have to be reset together
+* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT`: 명령 버퍼가 새로운 명령을 자주 기록할 것을 알려주는 힌트 (이에 따라 메모리 할당 방식이 바뀔 수 있음)
+* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`: 명령 버퍼가 독립적으로 재기록(rerecord)될 수 있음. 이 플래그가 없으면 모두 함께 리셋(reset)되어야 함
 
-We will be recording a command buffer every frame, so we want to be able to
-reset and rerecord over it. Thus, we need to set the
-`VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag bit for our command pool.
-
-Command buffers are executed by submitting them on one of the device queues,
-like the graphics and presentation queues we retrieved. Each command pool can
-only allocate command buffers that are submitted on a single type of queue.
-We're going to record commands for drawing, which is why we've chosen the
-graphics queue family.
+우리는 명령 버퍼를 매 프레임 기록할 것이기 때문에 리셋하고 재기록 하게 하려고 합니다. 따라서 커맨드 풀은 `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` 플래그 비트로 설정합니다.
 
+명령 버퍼는 이를 장치 큐 중 하나에 제출함으로써 실행됩니다. 장치 큐는 예를들어 우리가 획득한 그래픽스 또는 표시 큐와 같은 것입니다. 각 명령 풀은 한 종류의 큐에 제출할 명령 버퍼만 할당 가능합니다. 우리는 그리기를 위한 명령을 기록할 것이라서 그래픽스 큐 패밀리를 선택한 것입니다.
 
 ```c++
 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
@@ -76,10 +58,7 @@ if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS)
 }
 ```
 
-Finish creating the command pool using the `vkCreateCommandPool` function. It
-doesn't have any special parameters. Commands will be used throughout the
-program to draw things on the screen, so the pool should only be destroyed at
-the end:
+마지막으로 `vkCreateCommandPool` 함수를 호출해 명령 풀을 만듭니다. 특별한 매개변수는 없습니다. 명령은 화면에 무언가를 그리기 위해 프로그램 내내 사용할 것이니, 마지막에 가서야 해제하게 됩니다:
 
 ```c++
 void cleanup() {
@@ -89,20 +68,17 @@ void cleanup() {
 }
 ```
 
-## Command buffer allocation
+## 명령 버퍼 할당
 
-We can now start allocating command buffers.
+이제 명령 버퍼 할당을 시작해 봅시다.
 
-Create a `VkCommandBuffer` object as a class member. Command buffers
-will be automatically freed when their command pool is destroyed, so we don't
-need explicit cleanup.
+`VkCommandBuffer` 객체를 클래스 멤버로 추가합니다. 명령 버퍼는 명령 풀이 소멸되면 자동으로 해제되므로 따로 정리 과정은 필요 없습니다.
 
 ```c++
 VkCommandBuffer commandBuffer;
 ```
 
-We'll now start working on a `createCommandBuffer` function to allocate a single
-command buffer from the command pool.
+이제 명령 풀에서 하나의 명령 버퍼를 만들기 위해 `createCommandBuffer` 함수를 만들어 봅시다.
 
 ```c++
 void initVulkan() {
@@ -127,9 +103,7 @@ void createCommandBuffer() {
 }
 ```
 
-Command buffers are allocated with the `vkAllocateCommandBuffers` function,
-which takes a `VkCommandBufferAllocateInfo` struct as parameter that specifies
-the command pool and number of buffers to allocate:
+명령 버퍼는 `vkAllocateCommandBuffers` 함수를 사용해 할당되는데, 명령 풀을 명시하는 `VkCommandBufferAllocateInfo` 매개변수와 할당할 버퍼 개수를 매개변수로 받습니다:
 
 ```c++
 VkCommandBufferAllocateInfo allocInfo{};
@@ -143,27 +117,18 @@ if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS)
 }
 ```
 
-The `level` parameter specifies if the allocated command buffers are primary or
-secondary command buffers.
+`level` 매개변수는 할당된 명령 버퍼가 주(primary) 명령 버퍼인지, 보조(secondary) 명령 버퍼인지를 명시합니다.
 
-* `VK_COMMAND_BUFFER_LEVEL_PRIMARY`: Can be submitted to a queue for execution,
-but cannot be called from other command buffers.
-* `VK_COMMAND_BUFFER_LEVEL_SECONDARY`: Cannot be submitted directly, but can be
-called from primary command buffers.
+* `VK_COMMAND_BUFFER_LEVEL_PRIMARY`: 실행을 위해 큐에 제출될 수 있지만, 다른 명령 버퍼에서 호출은 불가능.
+* `VK_COMMAND_BUFFER_LEVEL_SECONDARY`: 직접 제출은 불가능하지만 주 명령 버퍼로부터 호출될 수 있음.
 
-We won't make use of the secondary command buffer functionality here, but you
-can imagine that it's helpful to reuse common operations from primary command
-buffers.
+보조 명령 버퍼의 기능은 여기에서 사용하진 않을 것이지만, 주 명령 버퍼에서 자주 사용되는 연산을 재사용하기 위해 유용하게 사용될 수 있다는 것은 눈치 채실 수 있을 겁니다.
 
-Since we are only allocating one command buffer, the `commandBufferCount` parameter
-is just one.
+우리는 하나의 명령 버퍼만을 할당하므로 `commandBufferCount`는 1입니다.
 
-## Command buffer recording
+## 명령 버퍼 기록
 
-We'll now start working on the `recordCommandBuffer` function that writes the
-commands we want to execute into a command buffer. The `VkCommandBuffer` used
-will be passed in as a parameter, as well as the index of the current swapchain
-image we want to write to.
+이제 실행하고자 하는 명령을 명령 버퍼에 채우는 `recordCommandBuffer` 함수를 만들어 봅시다. `VkCommandBuffer`와 값을 쓰고자 하는 현재 스왑체인 이미지의 인덱스를 매개변수로 넘겨줍니다.
 
 ```c++
 void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
@@ -171,9 +136,7 @@ void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
 }
 ```
 
-We always begin recording a command buffer by calling `vkBeginCommandBuffer`
-with a small `VkCommandBufferBeginInfo` structure as argument that specifies
-some details about the usage of this specific command buffer.
+명령 버퍼의 기록은 항상 `vkBeginCommandBuffer`에 간단한 `VkCommandBufferBeginInfo` 구조체를 넘겨주는 것으로 시작합니다. 이 구조체는 해당 명령 버퍼의 사용 방식에 대한 세부 사항을 명시합니다.
 
 ```c++
 VkCommandBufferBeginInfo beginInfo{};
@@ -186,30 +149,21 @@ if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
 }
 ```
 
-The `flags` parameter specifies how we're going to use the command buffer. The
-following values are available:
+`flags` 매개변수는 명령 버퍼를 어떻게 사용할 것인지를 명시합니다. 아래와 같은 값들이 될 수 있습니다:
 
-* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`: The command buffer will be
-rerecorded right after executing it once.
-* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT`: This is a secondary
-command buffer that will be entirely within a single render pass.
-* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT`: The command buffer can be
-resubmitted while it is also already pending execution.
+* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`: 명령 버퍼는 한 번 실행 후 즉시 재기록됨.
+* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT`: 이 버퍼는 보조 명령 버퍼로써 하나의 렌더링 패스에 완전하게 속해 있음.
+* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT`: 명령이 지연될 경우에 명령 버퍼가 재제출(resubmit) 될 수 있음
 
-None of these flags are applicable for us right now.
+연재는 어떤 플래그도 해당하지 않습니다.
 
-The `pInheritanceInfo` parameter is only relevant for secondary command buffers.
-It specifies which state to inherit from the calling primary command buffers.
+`pInheritanceInfo` 매개변수는 보조 명령 버퍼에만 해당됩니다. 주 명령 버퍼에서 호출될 떄 어떤 상태를 상속(inherit)하는지 명시합니다.
 
-If the command buffer was already recorded once, then a call to
-`vkBeginCommandBuffer` will implicitly reset it. It's not possible to append
-commands to a buffer at a later time.
+명령 버퍼가 이미 기록된 상태에서 `vkBeginCommandBuffer`를 호출하면 암시적으로 버퍼가 리셋됩니다. 명령을 버퍼에 추가(append)하는 것은 불가능합니다.
 
-## Starting a render pass
+## 렌더 패스 시작하기
 
-Drawing starts by beginning the render pass with `vkCmdBeginRenderPass`. The
-render pass is configured using some parameters in a `VkRenderPassBeginInfo`
-struct.
+그리기는 `vkCmdBeginRenderPass`를 사용해 렌더 패스를 시작함으로써 시작됩니다. 렌더 패스는 `VkRenderPassBeginInfo` 구조체의 매개변수를 기반으로 설정됩니다.
 
 ```c++
 VkRenderPassBeginInfo renderPassInfo{};
@@ -218,21 +172,14 @@ renderPassInfo.renderPass = renderPass;
 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
 ```
 
-The first parameters are the render pass itself and the attachments to bind. We
-created a framebuffer for each swap chain image where it is specified as a color
-attachment. Thus we need to bind the framebuffer for the swapchain image we want
-to draw to. Using the imageIndex parameter which was passed in, we can pick the
-right framebuffer for the current swapchain image.
+첫 매개변수는 렌더패스 그 자체와 바인딩할 어태치먼트입니다. 각 스왑 체인 이미지에 대해 프레임버퍼를 만들었고, 색상 어태치먼트로 명시된 상태입니다. 따라서 그 프레임버퍼를 우리가 그리고자 하는 스왑체인 이미지로 바인딩해야 합니다. 넘어온 imageIndex를 사용해 현재 스왑체인 이미지의 적정한 프레임버퍼를 선택할 수 있습니다.
 
 ```c++
 renderPassInfo.renderArea.offset = {0, 0};
 renderPassInfo.renderArea.extent = swapChainExtent;
 ```
 
-The next two parameters define the size of the render area. The render area
-defines where shader loads and stores will take place. The pixels outside this
-region will have undefined values. It should match the size of the attachments
-for best performance.
+다음 두 매개변수는 렌더 영역(area)의 크기를 명시합니다. 렌더 영역은 셰이더가 값을 읽고 쓰는 영역을 정의합니다. 이 영역 밖의 픽셀은 정의되지 않은 값을 가지게 됩니다. 어태치먼트와 같은 크기여야 성능이 높아집니다.
 
 ```c++
 VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
@@ -240,47 +187,32 @@ renderPassInfo.clearValueCount = 1;
 renderPassInfo.pClearValues = &clearColor;
 ```
 
-The last two parameters define the clear values to use for
-`VK_ATTACHMENT_LOAD_OP_CLEAR`, which we used as load operation for the color
-attachment. I've defined the clear color to simply be black with 100% opacity.
+마지막 두 매개변수는 `VK_ATTACHMENT_LOAD_OP_CLEAR`에 사용될 지우기(clear) 값이고, 색상 어태치먼트의 로드 연산에 사용한 바 있습니다. 여기서는 간단히 검은색의 100% 불투명도로 지우도록 하겠습니다.
 
 ```c++
 vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
 ```
 
-The render pass can now begin. All of the functions that record commands can be
-recognized by their `vkCmd` prefix. They all return `void`, so there will be no
-error handling until we've finished recording.
+이제 렌더 패스가 시작됩니다. 명령을 기록하는 함수는 `vkCmd` 접두어로 구분할 수 있습니다. 이들은 모두 `void` 반환이므로 기록을 끝낼 때 까지는 오류 처리가 불가능합니다.
 
-The first parameter for every command is always the command buffer to record the
-command to. The second parameter specifies the details of the render pass we've
-just provided. The final parameter controls how the drawing commands within the
-render pass will be provided. It can have one of two values:
+모든 명령의 첫 매개변수는 명령을 기록할 명령 버퍼입니다. 두 번째 매개변수는 방금 만든, 렌더 패스 세부사항을 명시합니다. 마지막 매개변수는 렌더 패스 안의 그리기 명령이 어떻게 제공될지를 제어합니다. 두 개의 값 중 하나입니다:
 
-* `VK_SUBPASS_CONTENTS_INLINE`: The render pass commands will be embedded in
-the primary command buffer itself and no secondary command buffers will be
-executed.
-* `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS`: The render pass commands will
-be executed from secondary command buffers.
+* `VK_SUBPASS_CONTENTS_INLINE`: 렌더 패스 명령이 주 명령 버퍼에 포함되어 있고 보조 명령 버퍼는 실행되지 않음.
+* `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS`: 렌더 패스 명령이 보조 명령 버퍼에서 실행됨
 
-We will not be using secondary command buffers, so we'll go with the first
-option.
+보조 명령 버퍼는 사용하지 않을 것이므로, 첫 번째 값을 선택합니다.
 
-## Basic drawing commands
+## 기본 그리기 명령
 
-We can now bind the graphics pipeline:
+이제 그래픽스 파이프라인을 바인딩합니다:
 
 ```c++
 vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
 ```
 
-The second parameter specifies if the pipeline object is a graphics or compute
-pipeline. We've now told Vulkan which operations to execute in the graphics
-pipeline and which attachment to use in the fragment shader.
+두 분째 매개변수는 파이프라인 객체가 그래픽스 파이프라인인지 계산(compute) 파이프라인인지를 명시합니다. 이제 Vulkan에게 그래픽스 파이프라인에서 어떤 명령을 실행하고 프래그먼트 셰이더에서 어떤 어태치먼트를 사용할 것인지를 알려 주었습니다.
 
-As noted in the [fixed functions chapter](../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state), 
-we did specify viewport and scissor state for this pipeline to be dynamic.
-So we need to set them in the command buffer before issuing our draw command:
+[고정 함수 챕터](../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state)에서 이야기 한 것처럼, 우리는 파이프라인에게 뷰포트와 시저 상태가 동적일 것이라고 명시해 둔 상태입니다. 따라서 이들을 명령 버퍼에서 그리기 명령을 수행하기 이전에 설정해 주어야 합니다:
 
 ```c++
 VkViewport viewport{};
@@ -298,34 +230,28 @@ scissor.extent = swapChainExtent;
 vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
 ```
 
-Now we are ready to issue the draw command for the triangle:
+이제 삼각형을 그리기 위한 그리기 명령을 추가합니다:
 
 ```c++
 vkCmdDraw(commandBuffer, 3, 1, 0, 0);
 ```
 
-The actual `vkCmdDraw` function is a bit anticlimactic, but it's so simple
-because of all the information we specified in advance. It has the following
-parameters, aside from the command buffer:
+실제 `vkCmdDraw` 명령은 아주 어렵지 않은데 미리 모든 정보를 설정해 두었기 때문입니다. 이 명령은 명령 버퍼 이외에 다음과 같은 매개변수를 갖습니다:
 
-* `vertexCount`: Even though we don't have a vertex buffer, we technically still
-have 3 vertices to draw.
-* `instanceCount`: Used for instanced rendering, use `1` if you're not doing
-that.
-* `firstVertex`: Used as an offset into the vertex buffer, defines the lowest
-value of `gl_VertexIndex`.
-* `firstInstance`: Used as an offset for instanced rendering, defines the lowest
-value of `gl_InstanceIndex`.
+* `vertexCount`: 정점 버퍼는 없어도, 그리기 위해서는 3개의 정점이 필요합니다.
+* `instanceCount`: 인스턴스(instanced) 렌더링을 위해 사용되는데, 그 기능을 사용하지 않는경우 `1`로 설정합니다.
+* `firstVertex`: 정점 버퍼의 오프셋을 설정하는 데 사용되며, `gl_VertexIndex`의 가장 작은 값을 정의합니다.
+* `firstInstance`: 인스턴스 렌더링의 오프셋을 설정하는 데 사용되며, `gl_InstanceIndex`의 가장 작은 값을 정의합니다.
 
-## Finishing up
+## 마무리
 
-The render pass can now be ended:
+이제 렌더 패스를 끝냅니다:
 
 ```c++
 vkCmdEndRenderPass(commandBuffer);
 ```
 
-And we've finished recording the command buffer:
+그리고 명령 버퍼의 기록도 끝냅니다:
 
 ```c++
 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
@@ -333,11 +259,7 @@ if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
 }
 ```
 
-
-
-In the next chapter we'll write the code for the main loop, which will acquire
-an image from the swap chain, record and execute a command buffer, then return the
-finished image to the swap chain.
+다음 장에서는 메인 루프를 위한 코드를 작성할 것이고, 그 과정에서 스왑 체인 이미지를 얻고, 명령 버퍼를 기록하고 실행하며, 결과 이미지를 스왑 체인에 반환할 것입니다.
 
 [C++ code](/code/14_command_buffers.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From 2c9b38727628b6f8efb9e7f8da50f8e2d15a6f89 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 5 Feb 2024 19:28:27 +0900
Subject: [PATCH 23/47] kr translate fix typo

---
 kr/00_Introduction.md                                     | 2 +-
 kr/01_Overview.md                                         | 2 +-
 kr/02_Development_environment.md                          | 2 +-
 kr/03_Drawing_a_triangle/00_Setup/01_Instance.md          | 2 +-
 kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md | 6 +++---
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/kr/00_Introduction.md b/kr/00_Introduction.md
index 626c6ac5..6feb29f5 100644
--- a/kr/00_Introduction.md
+++ b/kr/00_Introduction.md
@@ -6,7 +6,7 @@
 
 하지만, 이러한 이점을 활용하기 위해 여러분이 지불해야 할 비용은 훨씬 장황한 API를 다루어야 한다는 것입니다. 응용 프로그램에서 모든 그래픽스 API와 관련된 상세 사항들을 처음부터 설정해야 하는데, 초기 프레임 버퍼 생성이나 버퍼나 텍스처 이미지 객체들을 위한 메모리 관리 시스템을 만드는 것 등입니다. 그래픽 드라이버가 해 주는 일이 적어서 여러분의 응용 프로그램이 제대로 동작하기 위해서는 직접 더 많은 작업을 해 주어야 합니다.
 
-여기서 말하고자 하는 것은 Vulkan이 모든 사람들을 위해 만들어진 것은 아니라는 점입니다. Vulkan은 고성능 컴퓨터 그래픽스에 관심이 있고, 여기에 시간을 투자할 의지가 있는 프로그래머를 그 대상으로 하고 있습니다. 여러분이 컴퓨터 그래픽스보다는 게임 개발에 더 관심이 있다면, 그냥 OpenGL이나 Direct3D를 계속 사용하는 것이 더 나을 것입니다. Vulkan이 짧은 시간 내에 그 자리를 대체하지는 않을 겁니다. 다은 대안으로는 [Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4)
+여기서 말하고자 하는 것은 Vulkan이 모든 사람들을 위해 만들어진 것은 아니라는 점입니다. Vulkan은 고성능 컴퓨터 그래픽스에 관심이 있고, 여기에 시간을 투자할 의지가 있는 프로그래머를 그 대상으로 하고 있습니다. 여러분이 컴퓨터 그래픽스보다는 게임 개발에 더 관심이 있다면, 그냥 OpenGL이나 Direct3D를 계속 사용하는 것이 더 나을 것입니다. Vulkan이 짧은 시간 내에 그 자리를 대체하지는 않을 겁니다. 다른 대안으로는 [Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4)
 이나 [Unity](<https://en.wikipedia.org/wiki/Unity_(game_engine)>) 같은 게임 엔진을 사용하는 것입니다. 게임 엔진을 사용하면 훨씬 고수준의 API를 통해 Vulkan을 사용 가능합니다.
 
 그럼 각설하고, 이 튜토리얼을 위해 준비해야 할 사항들은 다음과 같습니다:
diff --git a/kr/01_Overview.md b/kr/01_Overview.md
index eb62c050..6208a36e 100644
--- a/kr/01_Overview.md
+++ b/kr/01_Overview.md
@@ -114,7 +114,7 @@ Vulkan의 많은 구조체들은 해당 구조체의 타입을 `sType` 멤버를
 
 앞서 이야기한 것처럼, Vulkan은 고성능과 적은 드라이버 오버헤드를 위해 설계되었습니다. 따라서 기본적으로는 아주 제한적인 오류 체킹과 디버깅 기능만을 포함하고 있습니다. 코드를 잘못 작성하는 드라이버는 오류 코드를 반환하는 대신 그냥 크래쉬(crash)가 발생하거나, 더 나쁜 경우에는 여러분의 그래픽 카드에서는 제대로 동작하는 것처럼 보이지만 다른 그래픽 카드에서는 전혀 동작하지 않을겁니다.
 
-Vulkan은 꼼꼼한 오류 체크를 *validation layers* 기능을 통해 제공합니다. 검증 레이어는 API와 그래픽 드라이버 사이에 삽입되는 코드로 함수 매개변수에 대한 추가적인 검증이나 메모리 관리 문제를 추적하는 데 사용됩니다. 좋은 점은 이러한 기능을 개발 과정에서 사용하고 릴리즈 할 때에는 완전히 사용하지 않도록 하여 오버헤드를 없앨 수 있다는 것입니다. 스스로 검증 레이어를 작성할 수도 있지만, LunarG가 만든 Vulkan SDK는 표준적인 검증 레이어를 제공하고, 이 튜토리얼에서는 그것을 사용할 것입니다. 여러분은 레이어에서 날아온 디버깅 메시지를 처기하기 위한 콜백 함수를 등록해야 합니다.
+Vulkan은 꼼꼼한 오류 체크를 *검증 레이어(validation layer)* 기능을 통해 제공합니다. 검증 레이어는 API와 그래픽 드라이버 사이에 삽입되는 코드로 함수 매개변수에 대한 추가적인 검증이나 메모리 관리 문제를 추적하는 데 사용됩니다. 좋은 점은 이러한 기능을 개발 과정에서 사용하고 릴리즈 할 때에는 완전히 사용하지 않도록 하여 오버헤드를 없앨 수 있다는 것입니다. 스스로 검증 레이어를 작성할 수도 있지만, LunarG가 만든 Vulkan SDK는 표준적인 검증 레이어를 제공하고, 이 튜토리얼에서는 그것을 사용할 것입니다. 여러분은 레이어에서 날아온 디버깅 메시지를 처기하기 위한 콜백 함수를 등록해야 합니다.
 
 Vulkan의 각 연산은 아주 명시적이고 검증 레이어는 꼼꼼하기 떄문에 화면이 검은 색 밖에 안나오는 경우에 OpenGL이나 Direct3D보다 그 원인을 찾기가 훨씬 쉽습니다!
 
diff --git a/kr/02_Development_environment.md b/kr/02_Development_environment.md
index 2839940c..e4923675 100644
--- a/kr/02_Development_environment.md
+++ b/kr/02_Development_environment.md
@@ -26,7 +26,7 @@ SDK는 [LunarG 웹사이트](https://vulkan.lunarg.com/) 페이지 하단의 버
 
 앞서 언급한 것처럼 Vulkan은 플랫폼 독립적인 API여서 렌더링 결과를 표시할 윈도우 생성을 위한 도구 같은것은 포함되어 있지 않습니다. Vulkan의 크로스 플랫폼 이점을 살리면서도 Win32의 어려움을 회피하는 방법으로 우리는 [GLFW library](http://www.glfw.org/)를 사용하여 윈도우를 만들 것입니다. GLFW는 윈도우, 리눅스와 MacOS를 모두 지원합니다. 비슷한 목적으로 사용 가능한 [SDL](https://www.libsdl.org/)과 같은 라이브러리도 있지만, GLFW는 윈도우 생성뿐만 아니라 Vulkan의 다른 추가적인 플랫폼 의존적인 작업들에 대한 추상화도 제공해 준다는 것입니다.
 
-GLFW의 최신 버전을 [공식 웹사이트](http://www.glfw.org/download.html)에서 찾을 수 있습니다. 이 튜토리얼에서는 64비트 바이너리를 사용할 것인데 32비트 모드로 빌드하셔도 됩니다. 그런 경우 Vulkan SDK의 `Lib` 디렉터리 대신 `Lib32` 디렉터리의 라이브러리들을 링크하셔야 합니다. 다운로드 하시고 나서 편한 곳에 압축을 푸십시오. 저는 내 문서 아래의 비주얼 스튜디오 디렉터리 아래 `Libraries` 폴더를 만들어 그 곳에 넣었습니다.
+GLFW의 최신 버전을 [공식 웹사이트](http://www.glfw.org/download.html)에서 찾을 수 있습니다. 이 튜토리얼에서는 64비트 바이너리를 사용할 것인데 32비트 모드로 빌드하셔도 됩니다. 그 경우에는 Vulkan SDK의 `Lib` 디렉터리 대신 `Lib32` 디렉터리의 라이브러리들을 링크하셔야 합니다. 다운로드 하시고 나서 편한 곳에 압축을 푸십시오. 저는 내 문서 아래의 비주얼 스튜디오 디렉터리 아래 `Libraries` 폴더를 만들어 그 곳에 넣었습니다.
 
 ![](/images/glfw_directory.png)
 
diff --git a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
index 297e2804..f54eefd4 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
@@ -83,7 +83,7 @@ if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
 
 ## VK_ERROR_INCOMPATIBLE_DRIVER 오류에 맞닥뜨린다면:
 
-최신 MultenVK SDK를 MacOS에서 사용중이하면 `vkCreateInstance`로부터 `VK_ERROR_INCOMPATIBLE_DRIVER`가 반환될 수 있습니다. [Getting Start Notes](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html)를 살펴보십시오. 1.3.216 Vulkan SDK부터는 `VK_KHR_PORTABILITY_subset` 확장이 필수적입니다.
+최신 MoltenVK SDK를 MacOS에서 사용중이하면 `vkCreateInstance`로부터 `VK_ERROR_INCOMPATIBLE_DRIVER`가 반환될 수 있습니다. [Getting Start Notes](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html)를 살펴보십시오. 1.3.216 Vulkan SDK부터는 `VK_KHR_PORTABILITY_subset` 확장이 필수적입니다.
 
 오류를 해결하기 위해서는 먼저 `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` 비트를 `VkInstanceCreateInfo` 구조체 플래그에 추가하고, `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME`를 인스턴스의 확장 리스트에 추가하십시오.
 
diff --git a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
index a04094d7..443d3518 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md
@@ -1,6 +1,6 @@
 ## 검증 레이어란?
 
-Vulkan API는 최소한의 드라이버 오버헤드를 기반으로 설계되었고 그를 위해서 기본적으로 API에서는 최소한의 오류 체크 기능만을 포함하고 있습니다. 열거자를 잘못된 값으로 설정한다거나 필요한 매개변수에 널 포인터를 넘긴다거나 하는 간단한 오류도 일반적으로는 명시적으로 처리되지 않아서 크래시나 정의되지 않은 동작을 일으키게 됩니다. Vulkan은 여러분이 하는 작업이 매우 명시적이기를 요구하기 떄문에, 새로운 GPU의 기능을 사용한다거나, 논리적 장치 생성 때 필요한 것들을 깜빡한다거나 하는 작은 실수를 저지르기 쉽습니다.
+Vulkan API는 최소한의 드라이버 오버헤드를 기반으로 설계되었고 그를 위해서 기본적으로 API에서는 최소한의 오류 체크 기능만을 포함하고 있습니다. 열거자를 잘못된 값으로 설정한다거나 필요한 매개변수에 널 포인터를 넘긴다거나 하는 간단한 오류도 일반적으로는 명시적으로 처리되지 않아서 크래시나 정의되지 않은 동작을 일으키게 됩니다. Vulkan에서 여러분이 하는 작업은 매우 명시적이어야 하기 때문에, 새로운 GPU의 기능을 사용한다거나, 논리적 장치 생성 때 필요한 것들을 깜빡한다거나 하는 작은 실수를 저지르기 쉽습니다.
 
 하지만, 그렇다고 그러한 체크 기능이 API에 포함될 수 없는것은 아닙니다. Vulkan은 *검증 레이어*라고 알려진 우아한 해결책을 만들었습니다. 검증 레이어는 선택적인 구성요소로 Vulkan 함수 호출에 후킹(hook)할 수 있는 추가적인 연산입니다. 일반적으로 검증 레이어에서 수행하는 연산은:
 
@@ -39,7 +39,7 @@ Vulkan에는 두 가지 종류의 검증 레이어가 존재하는데 인스턴
 
 이 장에서 우리는 Vulkan SDK에서 제공하는 표준 진단 레이어를 활성화 하는 법을 알아볼 것입니다. 확장과 마찬가지로, 검증 레이어는 그 이름을 명시하여 활성화해야 합니다. 모든 유용한 표준 검증들은 SDK에 포함되어 있는 `VK_LAYER_KHRONOS_validation`이라는 레이어에 포함되어 있습니다.
 
-먼저 프로그램에 두 개의 구성 변수를 추가하여 사용할 레이어를 명시하고 그들을 활성화 할것인지 말지를 알려 줍시오. 저는 디버깅 모드인지 아닌지에 따라 값을 설정하도록 했습니다. `NDEBUG` 매크로는 C++ 표준에 포함된 매크로로 "디버그가 아님"을 의미합니다.
+먼저 프로그램에 두 개의 구성 변수를 추가하여 사용할 레이어를 명시하고 그들을 활성화 할것인지를 알려 줍니다. 저는 디버깅 모드인지 아닌지에 따라 값을 설정하도록 했습니다. `NDEBUG` 매크로는 C++ 표준에 포함된 매크로로 "디버그가 아님"을 의미합니다.
 
 ```c++
 const uint32_t WIDTH = 800;
@@ -105,7 +105,7 @@ void createInstance() {
 
 이제 프로그램을 디버그 모드에서 실행해 오류가 발생하지 않는지 확인하세요. 오류가 발생하면, FAQ를 살펴보세요.
 
-마지막으로, `VkInstanceCreateInfo` 구조체 초기화를 구정해서 사용이 가능한 경우 검증 레이어의 이름을 포함하도록 하세요.
+마지막으로, `VkInstanceCreateInfo` 구조체 초기화를 수정해서 사용이 가능한 경우 검증 레이어의 이름을 포함하도록 하세요.
 
 ```c++
 if (enableValidationLayers) {

From 0b4c98c909846dfbd21a2bbaef6763b309a9da78 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 6 Feb 2024 13:04:22 +0900
Subject: [PATCH 24/47] kr translate fix typo

---
 .../03_Physical_devices_and_queue_families.md        | 12 ++++++------
 .../00_Setup/04_Logical_device_and_queues.md         |  8 ++++----
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
index d0259ae2..952e5db7 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md
@@ -1,6 +1,6 @@
 ## 물리적 장치 선택
 
-VkInstance를 통해 Vulkan 라이브러리를 초기화 한 이후에는 우리가 필요로 하는 기능을 지원하는 시스템의 그래픽 카드를 찾고 선택해야 합니다. 사실 여러 대의 그래픽 카드를 선택하고 동시에 사용할 수도 있습니다. 하지만 이 튜토리얼에서는 우리의 요구에 맞는 첫 번째 그래픽 카드만을 사용하도록 할 것입니다.
+VkInstance를 통해 Vulkan 라이브러리를 초기화 한 이후에는 우리가 필요로 하는 기능을 지원하는 시스템의 그래픽 카드를 찾고 선택해야 합니다. 여러 대의 그래픽 카드를 선택하고 동시에 사용할 수도 있습니다. 하지만 이 튜토리얼에서는 우리의 요구에 맞는 첫 번째 그래픽 카드만을 사용하도록 할 것입니다.
 
 `pickPhysicalDevice` 함수를 추가하고 `initVulkan` 함수에서 이 함수를 호출하도록 합시다.
 
@@ -52,7 +52,7 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-그리고 어떤 물리적 장치든 요구사항에 맞는는 것이 있는지를 확인합니다.
+그리고 어떤 물리적 장치든 요구사항에 맞는 것이 있는지를 확인합니다.
 
 ```c++
 for (const auto& device : devices) {
@@ -71,7 +71,7 @@ if (physicalDevice == VK_NULL_HANDLE) {
 
 ## 기본 장치 적합성(suitability) 확인
 
-장치의 적합성을 확인하기 위해 몇 가지 세부사항을 질의할 것입니다. 장치의 기본적인 속성인 이름, 타입, 지원하는 Vulkan 버전 등을 vkGetPhysicalDeviceProperties를 사용해 질의할 수 있습니다.
+장치의 적합성을 확인하기 위해 몇 가지 세부사항을 질의할 것입니다. 장치의 기본적인 속성인 이름, 타입, 지원하는 Vulkan 버전 등은 vkGetPhysicalDeviceProperties를 사용해 질의할 수 있습니다.
 
 ```c++
 VkPhysicalDeviceProperties deviceProperties;
@@ -191,7 +191,7 @@ QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
 
 큐 패밀리를 지원하지 않으면 어떻게 될까요? `findQueueFamilies`에서 예외를 throw할 수도 있지만, 이 함수는 장치 적합성을 확인하기 위한 목적으로는 적합하지 않습니다. 예를 들어 전송(transfer) 큐 패밀리가 있는 장치를 *선호*하긴 하지만 필수 요구사항은 아닐수도 있습니다. 따라서 특정한 큐 패밀리가 있는지 알려주는 방법이 필요합니다.
 
-큐 패밀리가 존재하지 않는것에 대한 마법같은 인덱스를 사용하는 방법은 없습니다. `0`을 포함해서 모든 `uint32_t` 값이 사실상 유요한 큐 패밀리의 인덱스일 수 있기 때문입니다. 다행히 C++17에서는 값이 존재하는지 아닌지를 구분할 수 있는 자료 구조를 지원합니다.
+큐 패밀리가 존재하지 않는것에 대한 마법같은 인덱스를 사용하는 방법은 없습니다. `0`을 포함해서 모든 `uint32_t` 값이 사실상 유효한 큐 패밀리의 인덱스일 수 있기 때문입니다. 다행히 C++17에서는 값이 존재하는지 아닌지를 구분할 수 있는 자료 구조를 지원합니다.
 
 ```c++
 #include <optional>
@@ -260,7 +260,7 @@ for (const auto& queueFamily : queueFamilies) {
 }
 ```
 
-이제 멋진 큐 패밀리 룩업(lookup) 함수가 있으니 `isDeviceSuitable` 함수에서 이를 사용해 장치가 우리가 사용하고자 하는 명령을 처리할 수 있는지 확인합니다:
+이제 큐 패밀리 룩업(lookup) 함수가 있으니 `isDeviceSuitable` 함수에서 이를 사용해 장치가 우리가 사용하고자 하는 명령을 처리할 수 있는지 확인합니다:
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -270,7 +270,7 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-좀 더 편리하게 사용하기 위해, 구조체 안에도 체크 기능을 추가합니다:
+좀 더 편리하게 사용하기 위해, 구조체 안에도 확인 기능을 추가합니다:
 
 ```c++
 struct QueueFamilyIndices {
diff --git a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
index 3a4060ea..ecc58bc4 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md
@@ -38,7 +38,7 @@ queueCreateInfo.queueCount = 1;
 
 현재의 드라이버들은 큐 패밀리 하나당 적은 수의 큐만을 생성할 수 있도록 제한되어 있고, 여러분도 하나 이상 필요하지는 않을겁니다. 왜냐하면 여러 쓰레드(thread)에 필요한 커맨드 버퍼들을 모두 생성해 두고 메인 쓰레드에서 적은 오버헤드의 호출로 이들을 한꺼번에 제출(submit)할 수 있기 떄문입니다.
 
-Vulkan에서는 커맨드 버퍼의 실행 스케줄에 영향을 주는 큐의 우선순위를 `0.0`과 `1.0` 사이의 부동소수점 수로 명시할 수 있게 되어 있습니다. 큐가 하나밖에 없더라도 이를 명시해 주어야만 합니다:
+Vulkan에서는 커맨드 버퍼의 실행 스케줄에 영향을 주는 큐의 우선순위를 `0.0`과 `1.0` 사이의 부동소수점 값으로 명시할 수 있게 되어 있습니다. 큐가 하나밖에 없더라도 이를 명시해 주어야만 합니다:
 
 ```c++
 float queuePriority = 1.0f;
@@ -47,7 +47,7 @@ queueCreateInfo.pQueuePriorities = &queuePriority;
 
 ## 사용할 장치 기능 명시하기
 
-다음으로는 우리가 사용할 장치의 기능을 명시해야 합니다. 이는 이전 챕터의 지오메트리 셰이더를 `vkGetPhysicalDeviceFeatures`로 질의했던 것과 비슷합니다. 지금은 특별헌 기능이 필요 없으니 그냥 정의만 해 두고 모든 값을 `VK_FALSE`로 둡시다. 나중에 Vulkan을 사용해 좀 더 흥미로운 것들을 할 때 다시 이 구조체를 사용할 것입니다.
+다음으로는 우리가 사용할 장치의 기능을 명시해야 합니다. 이는 이전 챕터의 지오메트리 셰이더를 `vkGetPhysicalDeviceFeatures`로 질의했던 것과 비슷합니다. 지금은 특별한 기능이 필요 없으니 그냥 정의만 해 두고 모든 값을 `VK_FALSE`로 둡시다. 나중에 Vulkan을 사용해 좀 더 흥미로운 것들을 할 때 다시 이 구조체를 사용할 것입니다.
 
 ```c++
 VkPhysicalDeviceFeatures deviceFeatures{};
@@ -75,7 +75,7 @@ createInfo.pEnabledFeatures = &deviceFeatures;
 
 장치에 종속적인 확장 중 하나의 예시로는 `VK_KHR_swapchain`가 있는데, 렌더링된 이미지를 장치로부터 윈도우로 전달하는 기능입니다. 시스템의 Vulkan 장치가 이 기능을 지원하지 않을 수 있습니다. 예를 들어 계산 명령만 수행하는 장치일 경우에 그렇습니다. 이 확장에 대한 설명은 나중에 스왑 체인 챕터에서 다시 살펴볼 것입니다.
 
-Vulkan의 이전 구현에서는 인스턴스와 장치 종속적인 검증 레이어가 구분되어 있었으나, [지금은 아닙니다](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). 즉, `VkDeviceCreateInfo`의 `enabledLayerCount` 와 `ppEnabledLayerNames` 필드가 최신 구현에서는 무시됩니다. 하지만, 이전 버전과의 호환성을 위해 어쨌든 설정해 주는 것이 좋습니다.
+Vulkan의 예전 구현에서는 인스턴스와 장치 종속적인 검증 레이어가 구분되어 있었으나, [지금은 아닙니다](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). 즉, `VkDeviceCreateInfo`의 `enabledLayerCount` 와 `ppEnabledLayerNames` 필드가 최신 구현에서는 무시됩니다. 하지만, 이전 버전과의 호환성을 위해 어쨌든 설정해 주는 것이 좋습니다.
 
 ```c++
 createInfo.enabledExtensionCount = 0;
@@ -109,7 +109,7 @@ void cleanup() {
 }
 ```
 
-논리적 장치는 인스턴스와 직접적으로 상호작용하지 않으므로 매개변수에 포함되지 않습니다.
+논리적 장치는 인스턴스와 직접적으로 상호작용하지 않으므로 인스턴스는 매개변수에 포함되지 않습니다.
 
 ## 큐 핸들 얻기(Retrieving)
 

From 26cfdc55e53ac02908e2757b3266466aadc554eb Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 6 Feb 2024 18:12:33 +0900
Subject: [PATCH 25/47] kr translate fix typo

---
 .../00_Setup/01_Instance.md                   |  7 ++---
 .../01_Presentation/00_Window_surface.md      | 14 +++++-----
 .../01_Presentation/01_Swap_chain.md          | 28 +++++++++----------
 .../01_Presentation/02_Image_views.md         |  2 +-
 .../00_Introduction.md                        | 10 +++----
 .../01_Shader_modules.md                      | 18 ++++++------
 .../02_Fixed_functions.md                     | 22 +++++++--------
 .../03_Render_passes.md                       |  8 +++---
 .../04_Conclusion.md                          |  8 +++---
 .../03_Drawing/00_Framebuffers.md             | 10 +++----
 .../03_Drawing/01_Command_buffers.md          | 12 ++++----
 11 files changed, 68 insertions(+), 71 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
index f54eefd4..b8d97acf 100644
--- a/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
+++ b/kr/03_Drawing_a_triangle/00_Setup/01_Instance.md
@@ -112,10 +112,9 @@ if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
 
 ## 확장 지원 체크하기
 
-`vkCreateInstance`문서를 보면 `VK_ERROR_EXTENSION_NOT_PRESENT` 오류 코드가 반환될 수 있다는 것을 알 수 있습니다. We could simply
-specify the extensions we require and terminate if that error code comes back.
-That makes sense for essential extensions like the window system interface, but
-what if we want to check for optional functionality?
+`vkCreateInstance`문서를 보면 `VK_ERROR_EXTENSION_NOT_PRESENT` 오류 코드가 반환될 수 있다는 것을 알 수 있습니다. 
+필요로 하는 확장을 명시하고 오류가 반환되면 그냥 프로그램을 종료할 수도 있습니다.
+이는 윈도우 시스템 인터페이스와 같은 필수적인 확장에 대해서는 적절한 방법이지만, 추가적인 기능을 체크만 하려고 할 때에는 어떻게 해야 할까요?
 
 인스턴스를 생성하기 전에 지원하는 확장들의 리스트를 얻고 싶으면 `vkEnumerateInstanceExtensionProperties`를 사용하면 됩니다. 확장의 개수를 저장할 변수의 포인터와 확장의 상세 사항을 저장할 `VkExtensionProperties` 배열을 매개변수로 받습니다. 선택적으로 첫 번째 파라메터로 특정한 검증 레이어로 필터링하도록 할 수 있는데, 지금은 무시해도 됩니다.
 
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md b/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
index de781cd4..2e961b41 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md
@@ -1,8 +1,8 @@
-Vulkan은 플랫폼 독립적인 API이기 때문에, 윈도우 시스템과 직접적으로 소통할 수는 없습니다. Vulkan과 윈도우 시스템간의 연결을 만들어 결과물을 화면에 보이도록 하기 위해서 우리는 WIS (Window System Integration)을 사용해야만 합니다. 이 챕터에서는 먼저 `VK_KHR_surface`를 살펴볼 것입니다. `VK_KHR_surface` 객체는 렌더링된 이미지를 표현할 표면(surface)의 추상화된 객체입니다. 우리 프로그램에서 표면은 GLFW를 사용해 열어놓은 윈도우의 뒷받침이 될 것입니다.
+Vulkan은 플랫폼 독립적인 API이기 때문에, 윈도우 시스템과 직접적으로 소통할 수는 없습니다. Vulkan과 윈도우 시스템간의 연결을 만들어 결과물을 화면에 보이도록 하기 위해서 우리는 WSI (Window System Integration)을 사용해야만 합니다. 이 챕터에서는 먼저 `VK_KHR_surface`를 살펴볼 것입니다. `VK_KHR_surface` 객체는 렌더링된 이미지를 표현할 표면(surface)의 추상화된 객체입니다. 우리 프로그램에서 표면은 GLFW를 사용해 열어놓은 윈도우가 뒷받침할 것입니다.
 
 `VK_KHR_surface` 확장은 인스턴스 수준의 확장이고, 우리는 이미 활성화 시켜 놓았습니다. 왜냐하면 `glfwGetRequiredInstanceExtensions`를 통해 반환된 리스트에 포함되어 있거든요. 이 리스트는 다음 몇 챕터에서 사용할 다른 WSI 확장도 포함하고 있습니다.
 
-윈도우 표면은 인스턴스 생성 이후에 곧바로 생성해야 하는데 이는 윈도우 표면이 물리적 장치의 선택에 영향을 주기 때문입니다. 이 내용을 여기까지 미룬 이유는 윈도우 표면이 렌더 타겟과 표현과 관련된 큰 주제이고, 이러한 내용으로 기본적인 세팅 설명을 복잡하게 만들고 싶지 않았기 때문입니다. 또한 윈도우 표면은 Vulkan에서 선택적인 구성요소로, 오프 스크린(off-screen) 렌더링을 할 경우에는 필요하지 않습니다. Vulkan은 보이지 않는 윈도우를 생성하는 등의 편법을 동원하지 않고서도 이런 기능을 사용 가능합니다 (OpenGL에서는 이러한 방식으로 구현해야만 합니다).
+윈도우 표면은 인스턴스 생성 이후에 곧바로 생성해야 하는데 이는 윈도우 표면이 물리적 장치의 선택에 영향을 주기 때문입니다. 이 내용을 여기까지 미룬 이유는 윈도우 표면이 렌더 타겟과 표현과 관련된 큰 주제이고, 이러한 내용으로 인해 기본적인 세팅 설명을 복잡하게 만들고 싶지 않았기 때문입니다. 또한 윈도우 표면은 Vulkan에서 선택적인 구성요소로, 오프 스크린(off-screen) 렌더링을 할 경우에는 필요하지 않습니다. Vulkan은 보이지 않는 윈도우를 생성하는 등의 편법을 동원하지 않고서도 이런 기능을 사용 가능합니다 (OpenGL에서는 편법으로 구현해야만 합니다).
 
 ## 윈도우 표면 생성
 
@@ -14,7 +14,7 @@ VkSurfaceKHR surface;
 
 `VkSurfaceKHR`객체와 그 활용은 플랫폼 독립적이지만, 생성에 있어서는 윈도우 시스템의 세부 사항에 의존적입니다. 예를 들어, 윈도우에서는 `HWND` 와 `HMODULE` 핸들이 필요합니다. 따라서 플랫폼 의존적인 확장들이 존재하고 윈도우의 경우 이 확장은 `VK_KHR_win32_surface`입니다. 이 확장은 `glfwGetRequiredInstanceExtensions`에 자동적으로 포함되어 있습니다.
 
-윈도우즈에서 표면을 생성하기 위해 이러한 플랫폼별 확장을 사용하는 예시를 보여드리겠습니다. 하지만 이 튜토리얼에서 이를 실제 사용하진 않을 것입니다. GLFW와 같은 라이브러리를 사용하면서 플랫폼별 코드를 사용하는 것은 적절하지 않습니다. GLFW에는 이미 `glfwCreateWindowSurface`를 통해 플랫폼별 차이에 따른 코드를 처리해 줍니다. 그래도, 사용하기 전에 뒤쪽에서 어떤 일이 벌어지는지는 알아두는 것이 좋겠죠.
+윈도우즈에서 표면을 생성하기 위해 이러한 플랫폼별 확장을 사용하는 예시를 보여드리겠습니다. 하지만 이 튜토리얼에서 이를 실제 사용하진 않을 것입니다. GLFW와 같은 라이브러리를 사용하면서도 플랫폼별 코드를 사용하는 것은 적절하지 않습니다. GLFW에서는 `glfwCreateWindowSurface`를 통해 플랫폼별 차이에 따른 코드를 처리해 줍니다. 그래도, 사용하기 전에 뒤쪽에서 어떤 일이 벌어지는지는 알아두는 것이 좋겠죠.
 
 네이티브 플랫폼 기능에 접근하기 위해서는 위쪽에 include를 바꿔줘야 합니다.
 
@@ -35,9 +35,9 @@ createInfo.hwnd = glfwGetWin32Window(window);
 createInfo.hinstance = GetModuleHandle(nullptr);
 ```
 
-`glfwGetWin32Window`함수는 GLFW 윈도웅 객체로부터 `HWND`를 얻기위해 사용됩니다. `GetModuleHandle` 호출은 현재 프로세스의 `HINSTANCE` 핸들을 반환해줍니다.
+`glfwGetWin32Window`함수는 GLFW 윈도우 객체로부터 `HWND`를 얻기위해 사용됩니다. `GetModuleHandle` 호출은 현재 프로세스의 `HINSTANCE` 핸들을 반환해줍니다.
 
-이루에는 `vkCreateWin32SurfaceKHR`를 통해 표면을 생성할 수 있는데, 인스턴스, 표면 생성 세부사항, 사용자 정의 할당자와 표면 핸들 저장을 위한 변수가 매개변수입니다. 정확히 하자면 이는 WSI 확장 함수이지만, 자주 사용되는 관계로 표준 Vulkan 로더에 포함되어 있고, 그렇기 때문에 명시적으로 로드할 필요가 없습니다.
+이후에는 `vkCreateWin32SurfaceKHR`를 통해 표면을 생성할 수 있는데 매개변수는 인스턴스, 표면 생성 세부사항, 사용자 정의 할당자와 표면 핸들 저장을 위한 변수입니다. 정확히 하자면 이는 WSI 확장 함수이지만, 자주 사용되는 관계로 표준 Vulkan 로더에 포함되어 있고, 그렇기 때문에 명시적으로 로드할 필요가 없습니다.
 
 ```c++
 if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
@@ -73,7 +73,7 @@ void createSurface() {
 }
 ```
 
-매개변수는 `VkInstance`, GLFW 윈도우에 대한 포인터, 사용자 정의 할당자와 `VkSurfaceKHR` 변수에 대한 포인터입니다. 내부적으로는 플랫폼 관련 호출을 할 뒤에 `VkResult` 값을 전달하여 반환해 줍니다. GLFW는 표면 소멸을 위한 특별한 함수를 제공하지는 않고, 기본(original) API를 통해 간단히 구현하면 됩니다:
+매개변수는 `VkInstance`, GLFW 윈도우에 대한 포인터, 사용자 정의 할당자와 `VkSurfaceKHR` 변수에 대한 포인터입니다. 내부적으로는 플랫폼 관련 호출을 한 뒤에 `VkResult` 값을 전달하여 반환해 줍니다. GLFW는 표면 소멸을 위한 특별한 함수를 제공하지는 않으므로, 기본(original) API를 통해 구현해야 합니다:
 
 ```c++
 void cleanup() {
@@ -103,7 +103,7 @@ struct QueueFamilyIndices {
 };
 ```
 
-다음으로, `findQueueFamilies`함수를 수정해서 윈도우 표면에 표현 기능이 있는 큐 패밀리를 찾아 봅시다. 이를 체크하기 위한 함수는 `vkGetPhysicalDeviceSurfaceSupportKHR` 함수히고, 물리적 장치, 큐 패밀리 인덱스와 표면을 매개변수로 받습니다. `VK_QUEUE_GRAPHICS_BIT`와 동일한 루프에 이 함수의 호출을 추가합니다:
+다음으로, `findQueueFamilies`함수를 수정해서 윈도우 표면에 표현 기능이 있는 큐 패밀리를 찾아 봅시다. 이를 체크하기 위한 함수는 `vkGetPhysicalDeviceSurfaceSupportKHR` 함수이고, 물리적 장치, 큐 패밀리 인덱스와 표면을 매개변수로 받습니다. `VK_QUEUE_GRAPHICS_BIT`와 동일한 루프에 이 함수의 호출을 추가합니다:
 
 ```c++
 VkBool32 presentSupport = false;
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
index c782e189..43555845 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
@@ -2,7 +2,7 @@ Vulkan은 "기본 프레임버퍼(default framebuffer)"의 개념이 없습니
 
 ## 스왑 체인 지원 확인하기
 
-모든 그래픽 카드가 이미지를 곧바로 화면에 표시하는 기능을 지원하는 것은 아닙니다. 예를 들어 서버를 위해 설계된 그래픽 카드는 디스플레이 출력이 없을 수 있습니다. 또한, 이미지의 표현은 윈도우 시스템, 그 윈도우와 연관된 표면(surface)와 밀접하게 관련되어 있기 때문에 Vulkan 코어(core)에는 포함되어 있지 않습니다. 지원하는지를 확인한 후에 `VK_KHR_swapchain` 장치 확장을 활성화시켜줘야만 합니다.
+모든 그래픽 카드가 이미지를 곧바로 화면에 표시하는 기능을 지원하는 것은 아닙니다. 예를 들어 서버를 위해 설계된 그래픽 카드는 디스플레이 출력이 없을 수 있습니다. 또한, 이미지의 표현은 윈도우 시스템, 그 윈도우와 연관된 표면(surface)과 밀접하게 관련되어 있기 때문에 Vulkan 코어(core)에는 포함되어 있지 않습니다. 지원하는지를 확인한 후에 `VK_KHR_swapchain` 장치 확장을 활성화시켜줘야만 합니다.
 
 이러한 목적으로 우리는 먼저 `isDeviceSuitable` 함수를 수정해 이러한 확장을 지원하는지 확인할 것입니다. `VkPhysicalDevice`를 사용해 지원하는 확장의 목록을 얻는 법을 이미 봤기 때문에 어렵지 않을 겁니다. Vulkan 헤더 파일은 `VK_KHR_swapchain`로 정의된 `VK_KHR_SWAPCHAIN_EXTENSION_NAME` 매크로를 지원합니다. 매크로를 사용하면 컴파일러가 타이핑 오류를 탐지할 수 있습니다.
 
@@ -95,7 +95,7 @@ SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
 
 이 장에서는 이러한 정보를 포함한 구조체를 질의하는 방법을 설명합니다. 이 구조체의 의미와 정확히 어떤 데이터들을 가지고 있는지는 다음 장에서 설명할 것입니다.
 
-기본 표면 기능으로 시작해 봅시다. 이러한 속성들은 질의하기 쉽고 `VkSurfaceCapabilitiesKHR` 타입의 단일 구조체로 반환됩니다.
+기본 표면 기능으로 시작해 봅시다. 이러한 속성들은 질의하기 쉽고, `VkSurfaceCapabilitiesKHR` 타입의 단일 구조체로 반환됩니다.
 
 ```c++
 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
@@ -115,7 +115,7 @@ if (formatCount != 0) {
 }
 ```
 
-모든 가능한 포맷을 저장할 수 있도록 벡터의 크기가 변하게 해야 합니다. 마지막으로, 지원하는 표현 모드를 질의하는 것도 `vkGetPhysicalDeviceSurfacePresentModesKHR`를 사용해 동일한 방식으로 이루어집니다:
+모든 가능한 포맷을 저장할 수 있도록 벡터의 크기가 변해야 합니다. 마지막으로, 지원하는 표현 모드를 질의하는 것도 `vkGetPhysicalDeviceSurfacePresentModesKHR`를 사용해 동일한 방식으로 이루어집니다:
 
 ```c++
 uint32_t presentModeCount;
@@ -163,7 +163,7 @@ VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>
 }
 ```
 
-각 `VkSurfaceFormatKHR`는 `format` 과 `colorSpace` 멤버를 가지고 있습니다. `format`은 컬러 채널과 타입을 명시합니다. 예를 들어 `VK_FORMAT_B8G8R8A8_SRGB`는 B,G,R과 알파 채널을 그 순서대로 8비트 부호없는(unsigned) 정수로 저장하여 픽셀달 32비트를 사용합니다. `colorSpace` 멤버는 `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`를 사용해 SRGB 컬러 공간을 지원하는지 여부를 표시합니다. 참고로 이 플래그는 이전 버전 명세에서는 `VK_COLORSPACE_SRGB_NONLINEAR_KHR`였습니다.
+각 `VkSurfaceFormatKHR`는 `format` 과 `colorSpace` 멤버를 가지고 있습니다. `format`은 컬러 채널과 타입을 명시합니다. 예를 들어 `VK_FORMAT_B8G8R8A8_SRGB`는 B,G,R과 알파 채널을 그 순서대로 8비트 부호없는(unsigned) 정수로 저장하여 픽셀당 32비트를 사용합니다. `colorSpace` 멤버는 `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`를 사용해 SRGB 컬러 공간을 지원하는지 여부를 표시합니다. 참고로 이 플래그는 이전 버전 명세에서는 `VK_COLORSPACE_SRGB_NONLINEAR_KHR`였습니다.
 
 컬러 공간에 대해서 우리는 가능하면 SRGB를 사용할 것인데, 이것이 [보다 정확한 색상 인지가 가능하기 때문입니다](http://stackoverflow.com/questions/12524623/). 또한 이는 나중에 살펴볼 (예를들면 텍스처와 같은) 이미지에 대한 표준 컬러 공간입니다. 이러한 이유로 컬러 포맷도 SRGB 컬러 포맷을 사용하는 것이고 가장 흔히 사용되는 것이 `VK_FORMAT_B8G8R8A8_SRGB`입니다.
 
@@ -232,9 +232,9 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 }
 ```
 
-스왑 크기는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 *픽셀 단위에서* 우리가 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)으로 해상도를 명시해야 합니다.
+스왑 크기는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 *픽셀 단위에서* 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)로 해상도를 명시해야 합니다.
 
-GLFW는 크기를 측정하는 두 단위가 있고 이는 픽셀과 [스크린 좌표계](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems) 입니다. 예를 들어 우리가 이전에 윈도우를 생성할 때 명시한 `{WIDTH, HEIGHT}` 해상도는 스크린 좌표계 기준으로 측정한 값입니다. 하지만 Vulkan은 픽셀 단위로 동작하기 때문에, 스왑 체인의 크기도 픽셀 단위로 명시해 주어야만 합니다. 안타깝게도 여러분이 (애플릐 레티나 디스플레이와 같은) 고DPI 디스플레이를 사용하는 경우, 스크린 좌표계가 픽셀 단위와 달라집니다. 높은 픽셀 밀도로 인해 픽셀 단위의 윈도우 해상도는 스크린 좌표계 단위의 윈도우 해상도보다 커집니다. Vulkan이 스왑 크기에 관한 것을 수정해 주지 않는 한, 그냥 `{WIDTH, HEIGHT}`를 사용할 수는 없습니다. 대신에 `glfwGetFramebufferSize`를 사용해서 윈도우의 해상도를 최대 및 최소 이미지 크기와 맞추기 전에 픽셀 단위로 받아와야만 합니다.
+GLFW는 크기를 측정하는 두 단위가 있고 이는 픽셀과 [스크린 좌표계](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems) 입니다. 예를 들어 우리가 이전에 윈도우를 생성할 때 명시한 `{WIDTH, HEIGHT}` 해상도는 스크린 좌표계 기준으로 측정한 값입니다. 하지만 Vulkan은 픽셀 단위로 동작하기 때문에, 스왑 체인의 크기도 픽셀 단위로 명시해 주어야만 합니다. 안타깝게도 여러분이 (애플의 레티나 디스플레이와 같은) 고DPI 디스플레이를 사용하는 경우, 스크린 좌표계가 픽셀 단위와 달라집니다. 높은 픽셀 밀도로 인해 픽셀 단위의 윈도우 해상도는 스크린 좌표계 단위의 윈도우 해상도보다 커집니다. Vulkan이 스왑 크기에 관한 것을 수정해 주지 않는 한, 그냥 `{WIDTH, HEIGHT}`를 사용할 수는 없습니다. 대신에 `glfwGetFramebufferSize`를 사용해서 윈도우의 해상도를 최대 및 최소 이미지 크기와 맞추기 전에 픽셀 단위로 받아와야만 합니다.
 
 ```c++
 #include <cstdint> // Necessary for uint32_t
@@ -263,7 +263,7 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 }
 ```
 
-여기서 `clamp` 함수는 `width`와 `height` 값을 구현에서 허용 가능한 최대와 최소 크기로 제한하기 위해 사용되었습니다.
+여기서 `clamp` 함수는 `width`와 `height` 값을 허용 가능한 최대와 최소 크기로 제한하기 위해 사용되었습니다.
 
 ## 스왑 체인 생성하기
 
@@ -290,7 +290,7 @@ void createSwapChain() {
 }
 ```
 
-이러한 속성들 이외에도 스왑 체인에 몇 개의 이미지를 사용할 것인지 결정해야 합니다. 구현을 통해 동작하기 위한 최소 개수를 명시할 수 있습니다:
+이러한 속성들 이외에도 스왑 체인에 몇 개의 이미지를 사용할 것인지 결정해야 합니다. 아래 구현은 동작하기 위한 최소 개수를 명시합니다:
 
 ```c++
 uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
@@ -329,7 +329,7 @@ createInfo.imageArrayLayers = 1;
 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
 ```
 
-`imageArrayLayers`는 각 이미지가 구성하는 레이어의 개수를 명시합니다. 여러분이 스테레오 3D(stereoscopic 3D) 응용 프로그램을 개발하는 것이 아니라면 이 값은 항상 `1`입니다. `imageUsage` 비트 필드는 스왑 체인의 이미지에 어떤 연산을 적용할 것인지를 명시합니다. 이 튜토리얼에서 우리는 여기에 직접 렌더링을 수행할 것이므로 color attachment로 사용될 것입니다. 먼저 별도의 이미지에 렌더링한 뒤 후처리(post-processing)을 적용하는 것도 가능합니다. 이러한 경우 `VK_IMAGE_USAGE_TRANSFER_DST_BIT`과 같은 값을 사용하고 렌더링된 이미지를 스왑 체인 이미지로 전송하기 위한 메모리 연산을 사용해야 합니다.
+`imageArrayLayers`는 각 이미지가 구성하는 레이어의 개수를 명시합니다. 여러분이 스테레오 3D(stereoscopic 3D) 응용 프로그램을 개발하는 것이 아니라면 이 값은 항상 `1`입니다. `imageUsage` 비트 필드는 스왑 체인의 이미지에 어떤 연산을 적용할 것인지를 명시합니다. 이 튜토리얼에서 우리는 여기에 직접 렌더링을 수행할 것이므로 색상 어태치먼트(color attachment)로 사용될 것입니다. 먼저 별도의 이미지에 렌더링한 뒤 후처리(post-processing)를 적용하는 것도 가능합니다. 이러한 경우 `VK_IMAGE_USAGE_TRANSFER_DST_BIT`과 같은 값을 사용하고 렌더링된 이미지를 스왑 체인 이미지로 전송하기 위한 메모리 연산을 사용해야 합니다.
 
 ```c++
 QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
@@ -351,26 +351,26 @@ if (indices.graphicsFamily != indices.presentFamily) {
 * `VK_SHARING_MODE_EXCLUSIVE`: 하나의 이미지가 한 번에 하나의 큐 패밀리에 의해 소유(own)되고 다른 큐에서 사용되기 전에 명시적으로 전송되어야 합니다. 이 옵션이 성능이 가장 좋습니다.
 * `VK_SHARING_MODE_CONCURRENT`: 소유권의 명시적 이동 없이 이미지가 여러 큐에서 동시에 접근 가능합니다.
 
-큐 패밀리가 다르다면 이 튜토리얼에서는 소유권 챕터로 넘어가기 전에 동시성 모드(concurrent mode)를 사용할 것인데 동시성에 대한 설명은 몇몇 개념 때문에 나중에 설명하는 것이 낫기 때문입니다. 동시성 모드는 어떤 큐 패밀리의 소유권이 공유될 것인지 `queueFamilyIndexCount`와 `pQueueFamilyIndices` 매개변수를 사용해 미리 명시하게 되어 있습니다. 그래픽스 큐 패밀리와 표시 큐 패밀리가 동일하다면 (대부분의 하드웨어에서는 동일함) 독점(exclusive) 모드를 사용할 것입니다. 동시성 모드에서는 최소한 두 개의 서로다른 큐 패밀리는 명시해야만 하기 떄문입니다.
+큐 패밀리가 다르다면 이 튜토리얼에서는 소유권 챕터로 넘어가기 전에는 동시성 모드(concurrent mode)를 사용할 것입니다. 동시성에 대한 설명은 몇몇 개념 때문에 나중에 설명하는 것이 낫기 때문입니다. 동시성 모드는 어떤 큐 패밀리의 소유권이 공유될 것인지 `queueFamilyIndexCount`와 `pQueueFamilyIndices` 매개변수를 사용해 미리 명시하게 되어 있습니다. 그래픽스 큐 패밀리와 표시 큐 패밀리가 동일하다면 (대부분의 하드웨어에서는 동일함) 독점(exclusive) 모드를 사용할 것입니다. 동시성 모드에서는 최소한 두 개의 서로다른 큐 패밀리를 명시해야만 하기 떄문입니다.
 
 ```c++
 createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
 ```
 
-이제 스왑 체인의 이미지에 적용할 특정 변환(transform)을 명시할 수 있습니다. 이를 그 기능이 지원될 때(`capabilities`의 `supportedTransforms`)에 가능한데 예를 들면 시계방향으로 90도 회전이라던가, 수평 뒤집기(flip) 등이 있습니다. 이러한 변환을 적용하지 않을 것이면, 현재 변환(current transformation)으로 명시하면 됩니다.
+이제 스왑 체인의 이미지에 적용할 특정 변환(transform)을 명시할 수 있습니다. 이는 기능이 지원될 때(`capabilities`의 `supportedTransforms`)에만 가능한데 예를 들면 시계방향으로 90도 회전이라던가, 수평 뒤집기(flip) 등이 있습니다. 이러한 변환을 적용하지 않을 것이면, 현재 변환(current transformation)으로 명시하면 됩니다.
 
 ```c++
 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
 ```
 
-`compositeAlpha` 필드는 윈도우 시스템의 다른 윈도우와의 블렌딩(blending)을 위해 알파 채널이 사용될 것인지를 명시합니다. 거의 개부분의 경우 알파 채널은 무시하는 것이 좋으므로 `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`를 사용합니다.
+`compositeAlpha` 필드는 윈도우 시스템의 다른 윈도우와의 블렌딩(blending)을 위해 알파 채널이 사용될 것인지를 명시합니다. 거의 대부분의 경우 알파 채널은 무시하는 것이 좋으므로 `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR`를 사용합니다.
 
 ```c++
 createInfo.presentMode = presentMode;
 createInfo.clipped = VK_TRUE;
 ```
 
-`presentMode`는 이름만 봐도 아실 수 있겠죠. `clipped`가 `VK_TRUE` 면 다려진 픽셀의 색상에 대해서는 신경쓰지 않겠다는 의미인데, 예를 들면 다른 윈도우가 그 픽셀 위에 있는 경우입니다. 뒤쪽의 픽셀 값을 읽어와 의도하는 결과를 얻을 것이 아니라면 그냥 클리핑(clipping)을 활성화 하는게 성능에 좋습니다.
+`presentMode`는 이름만 봐도 아실 수 있겠죠. `clipped`가 `VK_TRUE` 면 가려진 픽셀의 색상에 대해서는 신경쓰지 않겠다는 의미인데, 예를 들면 다른 윈도우가 그 픽셀 위에 있는 경우입니다. 뒤쪽의 픽셀 값을 읽어와 의도하는 결과를 얻을 것이 아니라면 그냥 클리핑(clipping)을 활성화 하는게 성능에 좋습니다.
 
 ```c++
 createInfo.oldSwapchain = VK_NULL_HANDLE;
@@ -439,6 +439,6 @@ swapChainImageFormat = surfaceFormat.format;
 swapChainExtent = extent;
 ```
 
-이제 그림을 그리고 화면에 표시될 이미지가 준비되었습니다. 다음 챕터에서부터는 이미지를 렌더링 타겟(render target)으로 설정하는 법, 실제 그래픽스 파이프라인과 그리지 명령에 대해 살펴볼 것입니다!
+이제 그림을 그리고 화면에 표시될 이미지가 준비되었습니다. 다음 챕터에서부터는 이미지를 렌더 타겟(render target)으로 설정하는 법, 실제 그래픽스 파이프라인과 그리기 명령에 대해 살펴볼 것입니다!
 
 [C++ code](/code/06_swap_chain_creation.cpp)
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md b/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
index fa2db5ba..e3c3490f 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/02_Image_views.md
@@ -1,4 +1,4 @@
-스왑 체인에 포함된 `VkImage`를 사용하기 위해서는 렌더링 파이프라인에서 `VkImageView` 객체를 생성해야 합니다. 이미지 뷰(image view)는 말 그대로 이미지에 대한 뷰 입니다. 이를 통해 이미지에 어떻게 접근하는지와 이미지의 어느 부분에 접근할 것인지를 명시하는데, 예를 들어 2D 텍스처로 취급될 것인지, 밉맵(mipmap) 수준이 없는 깊이 텍스차(depth texture)로 취급될 것인지와 같은 사항입니다.
+스왑 체인에 포함된 `VkImage`를 사용하기 위해서는 렌더링 파이프라인에서 `VkImageView` 객체를 생성해야 합니다. 이미지 뷰(image view)는 말 그대로 이미지에 대한 뷰 입니다. 이를 통해 이미지에 어떻게 접근하는지와 이미지의 어느 부분에 접근할 것인지를 명시하는데, 예를 들어 2D 텍스처로 취급될 것인지, 밉맵(mipmap) 수준이 없는 깊이 텍스처(depth texture)로 취급될 것인지와 같은 사항입니다.
 
 이 장에서 우리는 `createImageViews` 함수를 작성하여 스왑 체인에 있는 모든 이미지에 대한 이미지 뷰를 생성하고 이는 나중에 컬러 타겟으로 사용될 것입니다.
 
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
index 72115d68..f065b6d5 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md
@@ -2,27 +2,27 @@
 
 ![](/images/vulkan_simplified_pipeline.svg)
 
-*입력 조립(input assembler)*은 명시한 버퍼로부터 정점 데이터를 수집합니다. 또한 인덱스 버퍼를 사용하여 특정 요소들을 정점 데이터의 중복 없이 반복 사용할 수 있도록 할 수도 있습니다.
+*입력 조립기(input assembler)*는 명시한 버퍼로부터 정점 데이터를 수집합니다. 또한 인덱스 버퍼를 사용하여 특정 요소들을 정점 데이터의 중복 없이 반복 사용할 수 있도록 할 수도 있습니다.
 
 *정점 셰이더(vertex shader)*는 각 정점에 대해 실행되며, 일반적으로 정점의 위치를 모델 공간으로부터 스크린 공간으로 변환하는 작업을 합니다. 또한 정점별(per-vertex) 데이터를 파이프라인의 다음 단계로 전달합니다.
 
 *테셀레이션 셰이더(tessellation shaders)*는 특정한 규칙에 따라 기하(geometry)를 분할하여 메쉬의 품질을 향상시킬 수 있는 과정입니다. 이는 벽돌로 된 벽이라던가, 계단과 같은 표면에 적용되어 가까이서 봤을 때 덜 평평하게(flat) 보이도록 하는 데 자주 사용됩니다.
 
-*기하 셰이더(geometry shader)*는 모든 프리미티브(삼각형, 선, 점)에 대해 실행되며 그 정보를 탈락(discard)시키거나 입력된 것보다 더 많은 프리미티브를 생성할 수 있습니다. 테셀레이션 셰이더와 비슷하지만 훨씬 유연합니다. 하지만 요즘 응용 프로그램에서는 자주 사용되지 않는데 인텔의 내장 그래픽 카드를 제외하고 대부분 그래픽 카드에서는 성능이 그리 좋지 않기 때문입니다.
+*기하 셰이더(geometry shader)*는 모든 프리미티브(삼각형, 선, 점)에 대해 실행되며 그 정보를 제외(discard)시키거나 입력된 것보다 더 많은 프리미티브를 생성할 수 있습니다. 테셀레이션 셰이더와 비슷하지만 훨씬 유연합니다. 하지만 요즘 응용 프로그램에서는 자주 사용되지 않는데 인텔의 내장 그래픽 카드를 제외하고 대부분 그래픽 카드에서는 성능이 그리 좋지 않기 때문입니다.
 
 *래스터화(rasterization)* 단계는 프리미티브를 *프래그먼트*로 이산화하는 단계입니다. 프래그먼트는 픽셀 요로소 프레임버퍼에 채워집니다. 화면 밖에 놓여 있는 프래그먼트는 버려지고 정점 셰이더의 출력 어트리뷰트(attribute)는 프래그먼트들에 걸쳐 그림에 보이는 것과 같이 보간됩니다. 다른 프리미티브 프래그먼트 뒤에 놓여있는 프래그먼트도 깊이 테스트(depth test)에 의해 버려집니다.
 
 *프래그먼트 셰이더(fragment shader)*는 모든 살아남은 프래그먼트에 대해 실행되며 어떤 프레임버퍼에 프래그먼트가 쓰여질지, 어떤 색상과 깊이값이 쓰여질지를 결정합니다. 이는 정점 셰이더에서 보간된 데이터를 바탕으로 이루어지며 데이터는 텍스처 좌표계와 라이팅(lighting)을 위한 법선(normal) 정보 같은 것들이 포함됩니다.
 
-*컬러 블렌딩(color blending)* 단계는 프레임버퍼의 같은 픽셀에 맵핑되는 다른 프래드먼트들을 섞는 연산을 적용합니다. 프래그먼트 값들이 다른 값들을 대체할 수도 있고, 투명도에 따라 더해지거나 섞일 수 있습니다.
+*컬러 블렌딩(color blending)* 단계는 프레임버퍼의 같은 픽셀에 맵핑되는 다른 프래그먼트들을 섞는 연산을 적용합니다. 프래그먼트 값들이 다른 값들을 대체할 수도 있고, 투명도에 따라 더해지거나 섞일 수 있습니다.
 
 녹색으로 표현된 단계는 *고정 함수(fixed-function)* 단계로 알려져 있습니다. 이 단계들은 매개변수를 사용해 연산을 약간 변경할 수 있지만, 동작 방식 자체는 미리 정의되어 있습니다.
 
 주황색으로 표시된 단계는 `programmable`한 단계인데, 여러분이 작성한 코드를 그래픽 카드에 업로드할 수 있어서 원하는 대로 연산을 할 수 있다는 뜻입니다. 이렇게 되면 예를 들어 프래그먼트 셰이더에서 텍스처링이라던지 레이 트레이싱(ray tracing)을 위한 라이팅 등을 구현할 수 있게 됩니다. 이러한 프로그램은 여러 객체(예를들어 정점 또는 프래그먼트)들을 처리하기 위해 여러 GPU 코어에서 동시에 병렬적으로 실행됩니다.
 
-OpenGL이나 Direct3D같은 예전 API를 사용해 봤다면, 파이프라인의 설정을 바꾸는 `glBlendFunc`와 `OMSetBlendState` 같은 함수의 사용에 익숙할 것입니다. Vulkan의 그래픽스 파이프라인은 거의 완전히 불변적(immutable)이라서, 셰이더를 바꾸거나 다른 프레임버퍼를 바인딩(bind)한다거나 블렌딩 함수를 바꾸거나 할 떄에는 파이프라인을 처음부터 다시 만들어야 합니다. 이에 대한 단점으로는 우리가 사용하고자 하는 렌더링 연산을 위한 다양한 상태를 표현하는 모든 조합에 대해 파이프라인들을 만들어야 한다는 것입니다. 하지만, 파이프라인에서 수행하는 모든 연산에 대해 미리 말게되기 때문에, 드라이브가 훨씬 최적화를 잘 할 수 있는 장점도 있습니다.
+OpenGL이나 Direct3D같은 예전 API를 사용해 봤다면, 파이프라인의 설정을 바꾸는 `glBlendFunc`와 `OMSetBlendState` 같은 함수의 사용에 익숙할 것입니다. Vulkan의 그래픽스 파이프라인은 거의 완전히 불변적(immutable)이라서, 셰이더를 바꾸거나 다른 프레임버퍼를 바인딩(bind)한다거나 블렌딩 함수를 바꾸거나 할 떄에는 파이프라인을 처음부터 다시 만들어야 합니다. 이에 대한 단점으로는 우리가 사용하고자 하는 렌더링 연산을 위한 다양한 상태를 표현하는 모든 조합에 대해 파이프라인들을 만들어야 한다는 것입니다. 하지만, 파이프라인에서 수행하는 모든 연산에 대해 미리 알 수 있기 때문에, 드라이버가 훨씬 최적화를 잘 할 수 있는 장점도 있습니다.
 
-여러분이 하려는 작업에 따라서 몇 개의 프로그램가능한(programmable) 단계는 선택적으로 사용해도 됩니다. 예를 들어 테셀레이션과 기하 단계는 간단한 형상을 그릴 떄에는 활성화하지 않아도 됩니다. 깊이 값에만 관심이 있다면 프래그먼트 셰이더 단계를 비활성화 할수도 있는데 [그림자 맵](https://en.wikipedia.org/wiki/Shadow_mapping) 생성을 할 때에는 유용할 것입니다.
+여러분이 하려는 작업에 따라서 몇 개의 프로그램가능한(programmable) 단계는 선택적으로 사용해도 됩니다. 예를 들어 테셀레이션과 기하 단계는 간단한 형상을 그릴 떄에는 활성화하지 않아도 됩니다. 깊이 값에만 관심이 있다면 프래그먼트 셰이더 단계를 비활성화 할수도 있는데 [그림자 맵](https://en.wikipedia.org/wiki/Shadow_mapping) 생성을 할 때에 유용할 것입니다.
 
 다음 장에서는 삼각형을 화면에 표시하기 위한 두 개의 프로그램 가능한 단계(정점 셰이더와 프래그먼트 셰이더)를 만들어 볼 것입니다. 고정된 함수 구성인 블렌딩 모드, 뷰포트(viewport), 래스터화 같은 단계는 그 다음 챕터에서 설정할 것입니다. Vulkan에서의 그래픽스 파이프라인을 위한 마지막 설정 단계는 입력과 출력 프레임버퍼와 관련되어 있습니다.
 
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
index b10f3582..688c3ea2 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
@@ -1,8 +1,8 @@
-기존 API와는 다르게, Vulkan의 셰이더 코드는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)이나 [HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language)과 같은 사람이 읽을 수 있는(human-readable) 문법이 아닌 바이트코드(bytecode) 포맷으로 명시되어야 합니다. 이 바이트코드 포맷은 [SPIR-V](https://www.khronos.org/spir)라 불리며 Vulkan과 OpenCL에서의 사용을 위해 설계되었습니다(둘 다 크로노스(Khronos)의 API). 이는 사용해 그래픽스 및 계산 셰이더 작성이 가능하지만 이 튜토리얼에서는 Vulkan의 그래픽스 파이프라인에 사용되는 셰이더에 포커스를 맞추도록 하겠습니다.
+기존 API와는 다르게, Vulkan의 셰이더 코드는 [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)이나 [HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language)과 같은 사람이 읽을 수 있는(human-readable) 문법이 아닌 바이트코드(bytecode) 포맷으로 명시되어야 합니다. 이 바이트코드 포맷은 [SPIR-V](https://www.khronos.org/spir)라 불리며 Vulkan과 OpenCL에서의 사용을 위해 설계되었습니다(둘 다 크로노스(Khronos)의 API). 이를 사용해 그래픽스 및 계산 셰이더 작성이 가능하지만 이 튜토리얼에서는 Vulkan의 그래픽스 파이프라인에 사용되는 셰이더에 포커스를 맞추도록 하겠습니다.
 
 바이트코드를 사용함으로써 얻을 수 있는 장점은 GPU 벤더가 작성하는, 셰이더 코드를 네이티브 코드로 변환하는 컴파일러가 훨씬 간단해진다는 것입니다. 과거의 사례를 봤을 때 사람이 읽을 수 있는 GLSL과 같은 문법에서, 어떤 GPU 벤더들은 표준을 유연하게 해석하는 경우가 있었습니다. 이러한 벤더의 GPU에서 여러분이 일반적이지 않은(non-trivial) 셰이더를 작성하는 경우에, 다른 벤더의 드라이버에서는 여러분의 코드가 문법 오류로 판단된다던지, 더 안좋은 상황에서는 다른 방식으로 동작한다던지 하는 문제가 있을 수 있습니다. SPIR-V와 같은 직관적인 바이트코드 포맷을 사용하면 이러한 문제가 해결될 것으로 바라고 있습니다.
 
-그렇다고 우리가 손으로 바이트코드를 작성해야 한다는 뜻은 아닙니다. 크로노스 자체적으로 GLSL을 SPIR-V로 변환하는 벤더 독립적인 컴파일러를 릴리즈하였습니다. 이 컴파일러는 려어분의 셰이더 코드가 표준에 맞는지를 검증하고 프로그램에 사용할 수 있는 SPIR-V 바이너리를 생성합니다. 또한 이 컴파일러를 라이브러리의 형태로 추가하여 런타임에 SPIR-V를 생성하도록 할 수도 있지만, 이 튜토리얼에서 이 기능을 사용하지는 않을 것입니다. 컴파일러는 `glslangValidator.exe`를 통해 직접 사용할수도 있지만 우리는 구글에서 만든 `glslc.exe`를 사용할 것입니다. `glslc`의 장점은 GCC와 Clang과 같은 유명한 컴파일러와 같은 매개변수 포맷을 사용한다는 점, 그리고 *include*와 같은 부가 기능을 제공하는 점입니다. 둘 다 Vulkan SDK에 포함되어 있으므로 추가적으로 다운로드 할 필요는 없습니다.
+그렇다고 우리가 손으로 바이트코드를 작성해야 한다는 뜻은 아닙니다. 크로노스 자체적으로 GLSL을 SPIR-V로 변환하는 벤더 독립적인 컴파일러를 릴리즈하였습니다. 이 컴파일러는 여러분의 셰이더 코드가 표준에 맞는지를 검증하고 프로그램에 사용할 수 있는 SPIR-V 바이너리를 생성합니다. 또한 이 컴파일러를 라이브러리의 형태로 추가하여 런타임에 SPIR-V를 생성하도록 할 수도 있지만, 이 튜토리얼에서 이 기능을 사용하지는 않을 것입니다. 컴파일러는 `glslangValidator.exe`를 통해 직접 사용할수도 있지만 우리는 구글에서 만든 `glslc.exe`를 사용할 것입니다. `glslc`의 장점은 GCC와 Clang과 같은 유명한 컴파일러와 같은 매개변수 포맷을 사용한다는 점, 그리고 *include*와 같은 부가 기능을 제공하는 점입니다. 둘 다 Vulkan SDK에 포함되어 있으므로 추가적으로 다운로드 할 필요는 없습니다.
 
 GLSL은 C 스타일 문법을 가진 셰이더 언어입니다. GLSL로 작성된 프로그램은 `main`함수가 있어서 모든 객체에 대해 실행됩니다. 입력에 매개변수를 사용하고 출력에 반환값을 사용하는 대신, GLSL은 입력과 출력을 관리하는 전역 변수를 가지고 있습니다. 이 언어는 그래픽스 프로그램을 위한 다양한 기능을 포함하고 있는데 내장 벡터(vector)와 행렬(matrix) 타입이 그 예시입니다. 외적(cross product)이나 행렬-벡터 곱, 벡터를 기준으로 한 반사(reflection) 연산을 위한 함수 또한 포함되어 있습니다. 벡터 타입은 `vec`이라고 물리며 요소의 개수를 명시하는 숫자가 뒤에 붙습니다. 예를 들어 3차원 위치는 `vec3`에 저장됩니다. 개별 요소에 대한 접근은 멤버 접근 연산자처러 `.x`로 접근 가능하지만 여러 요소를 갖는 벡터를 새로 만들수도 있습니다. 예를 들어 `vec3(1.0, 2.0, 3.0).xy`는 결과적으로 `vec2` 입니다. 벡터의 생성자(constructor)는 벡터 객체와 스칼라(scalar)값의 조합을 받을 수 있습니다. 예를 들어 `vec3`가 `vec3(vec2(1.0, 2.0), 3.0)`를 통해 만들어질 수 있습니다.
 
@@ -10,13 +10,13 @@ GLSL은 C 스타일 문법을 가진 셰이더 언어입니다. GLSL로 작성
 
 ## 정점 셰이더
 
-정점 셰이더는 각 입력 정점을 처리합니다. 정점 셰이더는 월드 공간 좌표, 색상, 법선과 텍스처 좌표같은 값을 입력 데이터를 어트리뷰트로 받습니다. 출력은 클립 좌표(clip coordinate) 위치와 프래그먼트 셰이더로 전달할 색상과 텍스처 좌표와 같은 어트리뷰트 들입니다. 이 값들은 래스터화 단계에서 프래그먼트에 걸쳐 연속적인 값을 갖도록(smooth gradient) 보간됩니다.
+정점 셰이더는 각 입력 정점을 처리합니다. 정점 셰이더는 월드 공간 좌표, 색상, 법선과 텍스처 좌표같은 입력 데이터를 어트리뷰트로 받습니다. 출력은 클립 좌표(clip coordinate) 위치와 프래그먼트 셰이더로 전달할 색상과 텍스처 좌표와 같은 어트리뷰트 들입니다. 이 값들은 래스터화 단계에서 여러 프래그먼트에 걸쳐 부드럽게 변하도록(smooth gradient) 보간됩니다.
 
 *클립 좌표*는 정점 셰이더에서 도출된 4차원 벡터로 벡터를 마지막 구성요소의 값으로 나눔으로써 *정규화된 장치 좌표(normalized device coordinate)*로 변환됩니다. 정규화된 장치 좌표계는 [동차 좌표(homogeneous coordinates)](https://en.wikipedia.org/wiki/Homogeneous_coordinates)로, 아래 그림과 같이 프레임버퍼와 맵핑되는 [-1, 1]x[-1, 1] 좌표계입니다:
 
 ![](/images/normalized_device_coordinates.svg)
 
-컴퓨터 그래픽스를 좀 보셨다면 이런 것들이 익숙하실 겁니다. OpenGL을 사용해보셨다면 Y좌표가 뒤집혀 있는 것을 눈치채실 겁니다. Z좌표도 이제 Direct3D와 동일하게 0에서 1 사이의 값을 사용합니다.
+컴퓨터 그래픽스를 좀 공부하셨다면 이런 것들이 익숙하실 겁니다. OpenGL을 사용해보셨다면 Y좌표가 뒤집혀 있는 것을 눈치채실 겁니다. Z좌표도 이제 Direct3D와 동일하게 0에서 1 사이의 값을 사용합니다.
 
 첫 번째 삼각형 그릴 때, 우리는 아무 변환도 적용하지 않을 것입니다. 그냥 3개 정점의 위치를 정규화된 장치 좌표에서 직접 명시하여 아래와 같은 모양을 만들 것입니다:
 ![](/images/triangle_coordinates.svg)
@@ -39,7 +39,7 @@ void main() {
 }
 ```
 
-`main` 함수는 모든 정점에 대해 호출됩니다. `gl_VertexIndex` 내장 변수가 현재 정점의 인덱스를 가지고 있습니다. 이는 보통 정점 버퍼의 인덱스이지만 우리의 경우 하드코딩된 정점 데이터의 인덱스를 의미합니다. 각 정점의 위치는 셰이더에 있는 상수 배열로부터 얻어지고, `z`와 `w`값이 클립 좌표값을 위해 합쳐집니다. `gl_Position` 내장 변수가 출력처럼 활용됩니다.
+`main` 함수는 모든 정점에 대해 호출됩니다. `gl_VertexIndex` 내장 변수가 현재 정점의 인덱스를 가지고 있습니다. 이는 보통 정점 버퍼의 인덱스이지만 우리의 경우 하드코딩된 정점 데이터의 인덱스를 의미합니다. 각 정점의 위치는 셰이더에 있는 상수 배열로부터 얻어지고, `z`와 `w`값이 합쳐져 클립 좌표값이 됩니다. `gl_Position` 내장 변수가 출력처럼 활용됩니다.
 
 ## 프래그먼트 셰이더
 
@@ -55,7 +55,7 @@ void main() {
 }
 ```
 
-정점 셰이더가 모든 정점에 대해 `main` 함수를 호출하는 것처럼, 모든 프래그먼트에 대해 `main`함수가 호출됩니다. GLSL에서 색상은 [0, 1] 범위의 R,G,B와 알파 채널의 4차원 벡터로 표현됩니다. 정점 셰이더의 `gl_Position`과는 다르게, 현재 프래그먼트 출력을 위한 내장 변수는 없습니다. 각 프레임버퍼를 위한 출력 변수는 스스로 명시해야 하며 `layout(location = 0)` 수식어가 프레임버퍼의 인덱스를 명시합니다. 빨간색이 이러한 `outColor` 변수에 쓰여졌고, 이는 첫 번째(그리고 유일한) `0`번 인덱스 프레임버퍼와 연결되어 있습니다.
+정점 셰이더가 모든 정점에 대해 `main` 함수를 호출하는 것처럼, 모든 프래그먼트에 대해 `main`함수가 호출됩니다. GLSL에서 색상은 [0, 1] 범위의 R,G,B와 알파 채널의 4차원 벡터로 표현됩니다. 정점 셰이더의 `gl_Position`과는 다르게, 현재 프래그먼트 출력을 위한 내장 변수는 없습니다. 각 프레임버퍼를 위한 출력 변수는 스스로 명시해야 하며 `layout(location = 0)` 수식어가 프레임버퍼의 인덱스를 명시합니다. 빨간색이 이러한 `outColor` 변수에 쓰여졌고, 이는 첫 번째인(그리고 유일한) `0`번 인덱스 프레임버퍼와 연결되어 있습니다.
 
 ## 정점별 색상
 
@@ -63,7 +63,7 @@ void main() {
 
 ![](/images/triangle_coordinates_colors.png)
 
-이를 위해 두 셰이더 모두에 약간의 수정을 하겠습니다. 먼저 세 개의 정점에 각각 다른 색상을 명시해 주어야 합니다. 이제 정점 셰이더는 위치와 함께 색상을 위한 배열을 가집니다:
+이를 위해 두 셰이더 모두에 약간의 수정을 하겠습니다. 먼저 세 개의 정점에 각각 다른 색상을 명시해 주어야 합니다. 이제 정점 셰이더는 위치와 함께 색상을 위한 배열도 가집니다:
 
 ```glsl
 vec3 colors[3] = vec3[](
@@ -73,7 +73,7 @@ vec3 colors[3] = vec3[](
 );
 ```
 
-이제 프래그먼트 셰이더에 정점별 색상을 전달해줘서 프레임버퍼에 보간된 색상을 출력하게 하면 됩니다. 정점 셰이더에 색상 출력을 추가하고 `main`함수에서 여기에 값을 쓰면 됩니다:
+이제 프래그먼트 셰이더에 정점별 색상을 전달해줘서 프레임버퍼에 보간된 색상을 출력하게 하면 됩니다. 정점 셰이더에 색상 출력을 위한 변수를 추가하고 `main`함수에서 값을 쓰면 됩니다:
 
 ```glsl
 layout(location = 0) out vec3 fragColor;
@@ -307,7 +307,7 @@ vertShaderStageInfo.pName = "main";
 
 마지막 하나의 (선택적인) 멤버는 `pSpecializationInfo`이고, 여기서 사용할 것은 아니지만 언급할 필요는 있습니다. 이 멤버는 셰이더 상수(constant)의 값을 명시할 수 있도록 합니다. 하나의 셰이더 모듈을 만들고 파이프라인 생성 단계에서 사용되는 상수의 값을 다르게 명시하여 다르게 동작하도록 할 수 있습니다. 이렇게 하는 것이 변수를 사용하여 렌더링 시점에 셰이더의 동작을 바꾸는 것보다 효율적인데, 이렇게 하면 컴파일러가 이 값에 의존하는 `if` 분기를 제거하는 등의 최적화를 할 수 있습니다. 이에 해당하는 상수가 없다면 이 값은 `nullptr`로 두면 되고, 지금 우리 코드에서는 자동으로 이렇게 됩니다.
 
-프래그먼트 셰이더 경우를 위해 구조체를 수정하는 것은 쉽습니다:
+프래그먼트 셰이더를 위해 구조체를 수정하는 것은 쉽습니다:
 
 ```c++
 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
index f5946a7c..dc9be547 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md
@@ -2,7 +2,7 @@
 
 ## 동적 상태(Dynamic state)
 
-*대부분*의 파이프라인 상태가 파이프라인 상태 객체로 만들어져야만 하지만 몇몇 상태들은 그리기 시점에 파이프라인을 재생성하지 않고도 변경*될 수 있습니다*. 예시로는 뷰포트의 크기라던지, 선의 두께라던지 블렌딩 상수 등이 있습니다. 동적 상태를 사용하고 싶고, 이런 상태들을 계속 제외된 상태로 두고 싶다면, `VkPipelineDynamicStateCreateInfo` 구조체를 아래와 같이 만들어야 합니다.
+*대부분*의 파이프라인 상태가 파이프라인 상태 객체로 만들어져야만 하지만 몇몇 상태들은 그리기 시점에 파이프라인을 *재생성하지 않고도 변경될 수 있습니다*. 예시로는 뷰포트의 크기라던지, 선의 두께라던지 블렌딩 상수 등이 있습니다. 동적 상태를 사용하고 싶고, 이런 상태들을 계속 제외된 상태로 두고 싶다면, `VkPipelineDynamicStateCreateInfo` 구조체를 아래와 같이 만들어야 합니다.
 
 ```c++
 std::vector<VkDynamicState> dynamicStates = {
@@ -23,8 +23,7 @@ dynamicState.pDynamicStates = dynamicStates.data();
 `VkPipelineVertexInputStateCreateInfo` 구조체는 정점 데이터의 포맷을 기술하고, 이는 정점 셰이더로 넘겨집니다. 크게 두 가지 방법으로 기술됩니다:
 
 * 바인딩: 데이터 사이의 간격과 데이터가 정점별 데이터인지 인스턴스별(per-instance) 데이터인지 여부 ([인스턴싱](https://en.wikipedia.org/wiki/Geometry_instancing) 참고)
-* 어트리뷰트 기술: type of the attributes passed to the vertex shader,
-which binding to load them from and at which offset
+* 어트리뷰트 기술: 정점 셰이더에 전달된 어트리뷰트의 타입, 어떤 바인딩에 이들을 로드할 것인지와 오프셋이 얼마인지
 
 우리는 정점 셰이더에 정점 데이터를 하드 코딩하고 있기 때문에 지금은 이 구조체에 로드할 정점 데이터가 없다고 명시할 것입니다. 정점 버퍼 챕터에서 다시 살펴볼 것입니다.
 
@@ -41,7 +40,7 @@ vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional
 
 ## 입력 조립
 
-`VkPipelineInputAssemblyStateCreateInfo`구조체는 두 가지를 기술합니다: 정점으로부터 어떤 기하 형상이 그려질지와 프리미티브 재시작(restart)를 활성화할지 여부입니다. 앞의 내용은 `topology` 멤버에 명시되고 가능한 값들은 아래와 같습니다:
+`VkPipelineInputAssemblyStateCreateInfo`구조체는 두 가지를 기술합니다: 정점으로부터 어떤 기하 형상이 그려질지와 프리미티브 재시작(restart)을 활성화할지 여부입니다. 앞의 내용은 `topology` 멤버에 명시되고 가능한 값들은 아래와 같습니다:
 
 * `VK_PRIMITIVE_TOPOLOGY_POINT_LIST`: 정점으로부터 점을 그림
 * `VK_PRIMITIVE_TOPOLOGY_LINE_LIST`: 재사용 없이 두 정점마다 선을 그림
@@ -90,7 +89,7 @@ scissor.offset = {0, 0};
 scissor.extent = swapChainExtent;
 ```
 
-뷰포트와 시저 사각형은 파이프라인의 정적인 부분으로 명시할 수도 있고 [동적 상태](#dynamic-state)로 명시할 수도 있습니다. 정적으로 하는 경우 다른 상태들과 비슷하게 유지되지만 이들은 동적 상태로 명시하는 것이 유연성을 위해 더 편리합니다. 이렇게 하는 것이 더 일반적이고 이러한 동적 상태는 성능 저하 없이 구현이 가능합니다.
+뷰포트와 시저 사각형은 파이프라인의 정적인 부분으로 명시할 수도 있고 [동적 상태](#dynamic-state)로 명시할 수도 있습니다. 정적으로 하는 경우 다른 상태들과 비슷하게 유지되지만 이들은 동적 상태로 명시하는 것이 유연성을 위해 더 편리한 방법입니다. 이런 방식이 더 일반적이고 동적 상태는 성능 저하 없도록 구현되어 있습니다.
 
 동적 뷰포트와 시저 사각형을 위해서는 해당하는 동적 상태를 파이프라인에서 활성화해야 합니다:
 
@@ -131,7 +130,6 @@ viewportState.pScissors = &scissor;
 ```
 
 어떻게 설정하셨건, 어떤 그래픽 카드에서는 다중 뷰포트와 시저 사각형이 사용 가능하므로 구조체의 멤버는 이들의 배열을 참조하도록 되어 있습니다. 다중 뷰포트 또는 시저 사각형을 사용하려면 GPU 기능을 활성화 하는 것도 필요합니다 (논리적 장치 생성 부분을 참고하세요).
-Independent of how you set them, it's is possible to use multiple viewports and scissor rectangles on some graphics cards, so the structure members reference an array of them. Using multiple requires enabling a GPU feature (see logical device creation).
 
 ## 래스터화
 
@@ -162,7 +160,7 @@ rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
 * `VK_POLYGON_MODE_LINE`: 폴리곤의 모서리가 선으로 그려짐
 * `VK_POLYGON_MODE_POINT`: 폴리곤의 정점이 점으로 그려짐
 
-색상을 채우는 모드 이외에는 GPU 기능을 활성화해야 사용 가능합니다.
+채우기 모드 이외에는 GPU 기능을 활성화해야 사용 가능합니다.
 
 ```c++
 rasterizer.lineWidth = 1.0f;
@@ -214,7 +212,7 @@ multisampling.alphaToOneEnable = VK_FALSE; // Optional
 * 쓰여진 값과 새 값을 섞어 새로운 색상을 만듬
 * 쓰여진 값과 새 값을 비트 연산(bitwise operation)하여 결합
 
-컬러 블렌딩을 구성하는 두 종류의 구조체가 있습니다. 먼저 `VkPipelineColorBlendAttachmentState`는 부착(attach)된 프레임버퍼별 설정을 담은 구조체이며 `VkPipelineColorBlendStateCreateInfo`는 *전역(global)* 컬러 블렌딩 설정을 담고 있습니다. 우리는 하나의 프레임버퍼만 사용합니다:
+컬러 블렌딩을 구성하는 두 종류의 구조체가 있습니다. 먼저 `VkPipelineColorBlendAttachmentState`는 어태치(attach)된 프레임버퍼별 설정을 담은 구조체이며 `VkPipelineColorBlendStateCreateInfo`는 *전역(global)* 컬러 블렌딩 설정을 담고 있습니다. 우리는 하나의 프레임버퍼만 사용합니다:
 
 ```c++
 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
@@ -279,13 +277,13 @@ colorBlending.blendConstants[2] = 0.0f; // Optional
 colorBlending.blendConstants[3] = 0.0f; // Optional
 ```
 
-블렌딩의 두 번째 방법(비트 연산)을 하려면 `logicOpEnable`를 `VK_TRUE`로 설정해야 합니다. 그러면 비트 연산은 `logicOp` 필드에 명시됩니다. 주의하실 점은 이러한 경우 첫 번째 방법은 자동으로 비활성화 됩니다. 마치 여러분이 모든 프레임버퍼에 대해 `blendEnable`를 `VK_FALSE`로 설정한 것과 같이 말이죠! `colorWriteMask`또한 이 모드에서 프레임버퍼의 어떤 채널이 영향을 받을지를 결정하기 위해 사용됩니다. 지금 우리가 한 것처럼 두 모드를 모두 비활성화 하는 것도 가능합니다. 이러한 경우 프래그먼트 색상은 변경되지 않고 그대로 프레임버퍼에 쓰여집니다.
+블렌딩의 두 번째 방법(비트 연산)을 하려면 `logicOpEnable`를 `VK_TRUE`로 설정해야 합니다. 그러면 비트 연산은 `logicOp` 필드에 명시됩니다. 주의하실 점은 이러한 경우 첫 번째 방법은 자동으로 비활성화 됩니다. 마치 여러분이 모든 프레임버퍼에 대해 `blendEnable`를 `VK_FALSE`로 설정한 것과 같이 말이죠! `colorWriteMask` 또한 이 모드에서 프레임버퍼의 어떤 채널이 영향을 받을지를 결정하기 위해 사용됩니다. 지금 우리가 한 것처럼 두 모드를 모두 비활성화 하는 것도 가능합니다. 이러한 경우 프래그먼트 색상은 변경되지 않고 그대로 프레임버퍼에 쓰여집니다.
 
 ## 파이프라인 레이아웃(layout)
 
-셰이더에서 사용하는 `uniform`값은 동적 상태 변수처럼 전역적인 값으로 셰이더를 재생성하지 않고 그리기 시점에 값을 변경하여 다른 동작을 하도록 할 수 있습니다. 이는 주로 변환 행렬을 정점 셰이더에 전달하거나, 프래그먼트 셰이더에 텍스처 샘플러(sampler)를 생성하기 위해 사용됩니다.
+셰이더에서 사용하는 `uniform`은 동적 상태 변수처럼 전역적인 값으로 셰이더를 재생성하지 않고 그리기 시점에 값을 변경하여 다른 동작을 하도록 할 수 있습니다. 이는 주로 변환 행렬을 정점 셰이더에 전달하거나, 프래그먼트 셰이더에 텍스처 샘플러(sampler)를 생성하기 위해 사용됩니다.
 
-이러한 uniform 값은 `VkPipelineLayout` 객체를 생성하여 파이프라인 생성 단계에서 명시되어야 합니다. 나중 챕터까지는 사용하지 않을 것이지만 빈 파이프라인 레이아웃이라도 생성해 두어야만 합니다.
+이러한 uniform 값은 `VkPipelineLayout` 객체를 생성하여 파이프라인 생성 단계에서 명시되어야 합니다. 나중 챕터로 넘어가기 전까지는 사용하지 않을 것이지만 빈 파이프라인 레이아웃이라도 생성해 두어야만 합니다.
 
 이 객체를 저장할 클래스 멤버를 만들 것인데, 나중에 다른 함수에서 참조할 것이기 때문입니다.
 
@@ -321,7 +319,7 @@ void cleanup() {
 
 이로써 고정 함수 상태는 끝입니다! 이 모든 것들을 처음부터 만들어가는 과정은 힘들었지만, 그로 인해 그래픽스 파이프라인에서 일어나는 거의 모든 일들을 알게 되었습니다! 이러한 과정으로 인해 뜻밖의 오류가 발생할 가능성이 줄어들 것인데 특성 구성요소의 기본 상태를 제공한다면 그렇지 않았을 것입니다.
 
-그래픽스 파이프라인 생성을 위해서는 아직도 하나 더 객체를 만들어야 하고, 이는 [렌더 패스(render pass)](!en/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes)입니다.
+그래픽스 파이프라인 생성을 위해서는 아직도 하나 더 객체를 만들어야 하고, 이는 [렌더 패스(render pass)](!kr/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes)입니다.
 
 [C++ code](/code/10_fixed_functions.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
index b8ea6eb4..7655b8c0 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md
@@ -1,6 +1,6 @@
 ## 설정
 
-파이프라인을 마무리 하기 전에, Vulkan에 렌더링을 할 때 사용할 프레임버퍼에 대해 알려줄 필요가 있습니다. 얼마나 많은 색상과 깊이 버퍼가 있을 것인지, 각각에 대해 얼마나 많은 샘플을 사용할 것인지, 렌더링 연산 과정에서 각각의 내용들이 어떻게 처리될 것인지 등을 명시해야 합니다. 이런 모든 정보가 *렌더 패스(render pass)* 객체에 포함되는데, 이를 위해 새롭게 `createRenderPass` 함수를 만들 겁니다. 이 함수를 `initVulkan` 함수에서 `createGraphicsPipeline` 전에 호출합니다.
+파이프라인을 마무리 하기 전에, Vulkan에서 렌더링을 할 때는 사용할 프레임버퍼에 대해 알려줄 필요가 있습니다. 얼마나 많은 색상과 깊이 버퍼가 있을 것인지, 각각에 대해 얼마나 많은 샘플을 사용할 것인지, 렌더링 연산 과정에서 각각의 내용들이 어떻게 처리될 것인지 등을 명시해야 합니다. 이런 모든 정보가 *렌더 패스(render pass)* 객체에 포함되는데, 이를 위해 새롭게 `createRenderPass` 함수를 만들 겁니다. 이 함수를 `initVulkan` 함수에서 `createGraphicsPipeline` 전에 호출합니다.
 
 ```c++
 void initVulkan() {
@@ -24,7 +24,7 @@ void createRenderPass() {
 
 ## 어태치먼트(attachment) 기술
 
-우리의 경우 스왑 체인 안에 있는 이미지들 중 하나로 하나의 색상 버퍼 어태치먼트만 있을 것입니다.
+우리의 경우 스왑 체인 안에 있는 이미지들 중 하나로써 하나의 색상 버퍼 어태치먼트만 있을 예정입니다.
 
 ```c++
 void createRenderPass() {
@@ -81,9 +81,9 @@ Vulkan의 텍스처와 프레임버퍼는 특정 픽셀 포맷의 `VkImage` 객
 
 ## 서브패스(subpass)와 어태치먼트 참조
 
-라나의 렌더 패스는 여러 서브패스로 구성될 수 있습니다. 서브패스는 이전 패스에서 저장된 프레임버퍼의 내용을 가지고 렌더링 연산을 수행하는 일련의 패스입니다. 예를 들어 여러 후처리(post-processing) 과정을 연속적으로 적용하는 경우를 들 수 있습니다. 이러한 렌더링 연산들의 집합을 하나의 렌더 패스에 넣으면, Vulkan이 그 순서를 조정해 메모리 대역폭을 아껴서 보다 나은 성능을 얻을 수 있습니다. 하지만 우리의 삼각형 같은 경우 하나의 서브패스만 있으면 됩니다.
+하나의 렌더 패스는 여러 서브패스로 구성될 수 있습니다. 서브패스는 이전 패스에서 저장된 프레임버퍼의 내용을 가지고 렌더링 연산을 수행하는 일련의 패스입니다. 예를 들어 여러 후처리(post-processing) 과정을 연속적으로 적용하는 경우를 들 수 있습니다. 이러한 렌더링 연산들의 집합을 하나의 렌더 패스에 넣으면, Vulkan이 그 순서를 조정해 메모리 대역폭을 아껴서 보다 나은 성능을 얻을 수 있습니다. 하지만 우리의 삼각형 같은 경우 하나의 서브패스만 있으면 됩니다.
 
-모든 서브패스는 위와 같은 내용에 우리가 이전 장에서 만든 하나 이상의 어태치먼트 구조체를 참조합니다. 이 참조는 `VkAttachmentReference` 구조체로 아래와 같습니다.
+모든 서브패스는 우리가 이전 장에서 만든 하나 이상의 어태치먼트 구조체를 참조합니다. 이 참조는 `VkAttachmentReference` 구조체로 아래와 같습니다.
 
 ```c++
 VkAttachmentReference colorAttachmentRef{};
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
index 65ce8dea..0fe70c20 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
@@ -33,7 +33,7 @@ pipelineInfo.pDynamicState = &dynamicState;
 pipelineInfo.layout = pipelineLayout;
 ```
 
-다음으로 파이프라인 레이아웃으로 구조체에 대한 포인터가 아닌 Vulkan 핸들입니다.
+다음으로 파이프라인 레이아웃이 오는데, 여기에는 구조체에 대한 포인터가 아닌 Vulkan 핸들을 사용합니다.
 
 ```c++
 pipelineInfo.renderPass = renderPass;
@@ -64,11 +64,11 @@ if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr,
 }
 ```
 
-`vkCreateGraphicsPipelines`함수는 Vulkan의 다른 객체들을 만들 떄보다 더 많은 매개별수를 받습니다. 한 번의 호출로 여러 개의 `VkGraphicsPipelineCreateInfo`를 받아 여러 개의 `VkPipeline`객체를 만들 수 있게 되어있습니다.
+`vkCreateGraphicsPipelines`함수는 Vulkan의 다른 객체들을 만들 떄보다 더 많은 매개변수를 받습니다. 한 번의 호출로 여러 개의 `VkGraphicsPipelineCreateInfo`를 받아 여러 개의 `VkPipeline`객체를 만들 수 있게 되어있습니다.
 
-`VK_NULL_HANDLE`를 념겨준 두 번째 매개변수는 선택적으로 `VkPipelineCache`객체에 대한 참조를 넘겨줄 수 있습니다. 파이프라인 캐시(cache)는 여러 `vkCreateGraphicsPipelines` 호출을 위해, 파이프라인 생성을 위한 데이터를 저장하고 재사용하는데 사용될 수 있습니다. 만일 캐시가 파일로 저장되어 있다면 다른 프로그램에서도 사용될 수 있습니다. 이렇게 하면 나중에 파이프라인 생성을 위해 소요되는 시간을 눈에 띄게 줄일 수 있습니다. 이에 대해서는 파이프라인 캐시 챕터에서 살펴보겠습니다.
+`VK_NULL_HANDLE`를 넘겨준 두 번째 매개변수는 선택적으로 `VkPipelineCache`객체에 대한 참조를 넘겨줄 수 있습니다. 파이프라인 캐시(cache)는 여러 `vkCreateGraphicsPipelines` 호출을 위해, 파이프라인 생성을 위한 데이터를 저장하고 재사용하는데 사용될 수 있습니다. 만일 캐시가 파일로 저장되어 있다면 다른 프로그램에서도 사용될 수 있습니다. 이렇게 하면 나중에 파이프라인 생성을 위해 소요되는 시간을 눈에 띄게 줄일 수 있습니다. 이에 대해서는 파이프라인 캐시 챕터에서 살펴보겠습니다.
 
-모든 그리기 연산을 위해서는 그래픽스 파이프라인이 필요하므로 프로그램이 종료될 때에만 해제되어야 합니다.
+모든 그리기 연산 과정에서는 그래픽스 파이프라인이 필요하므로 프로그램이 종료될 때에만 해제되어야 합니다.
 
 ```c++
 void cleanup() {
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
index f4a1c37e..09940a18 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md
@@ -1,8 +1,8 @@
-지난 몇 개의 챕터에서 프레임버퍼와 관련한 많은 것들을 이야기 했고 스왑 체인 이미지와 같은 포맷인, 단일 프레임버퍼를 사용할 렌더 패스를 설정했습니다. 하지만 아직 생성은 하지 않았습니다.
+지난 몇 개의 챕터에서 프레임버퍼와 관련한 많은 것들을 이야기 했고 스왑 체인 이미지와 같은 포맷인, 단일 프레임버퍼를 사용할 렌더 패스를 설정했습니다. 하지만 아직 실제 생성은 하지 않았습니다.
 
-렌더 패스 생성 과정에서 명시한 어태치먼트는 `VkFramebuffer` 객체로 래핑하여 바인딩됩니다. 프레임버퍼 객체는 어태치먼트를 표현하는 모든 `VkImageView` 객체를 참조합니다. 우리의 경우 어태치먼트는 색상 어태치먼트 하나입니다. 하지만 어태치먼트로 사용할 이미지는 우리가 화면에 표시하기 위한 이미지로 스왑 체인이 어떠한 이미지를 반환했냐에 달려 있습니다. 즉, 우리는 스왑 체인에 있는 모든 이미지에 대해 프레임버퍼를 만들어야 하고, 그리기 시점에는 그 중 하나를 선택해서 사용해야 합니다.
+렌더 패스 생성 과정에서 명시한 어태치먼트는 `VkFramebuffer` 객체로 래핑하여 바인딩됩니다. 프레임버퍼 객체는 어태치먼트를 표현하는 모든 `VkImageView` 객체를 참조합니다. 우리의 경우 어태치먼트는 색상 어태치먼트 하나입니다. 하지만 어태치먼트로 사용해야 하는 이미지는 우리가 화면에 표시하기 위한 이미지를 요청했을 때 스왑 체인이 어떠한 이미지를 반환하냐에 달려 있습니다. 즉, 우리는 스왑 체인에 있는 모든 이미지에 대해 프레임버퍼를 만들어야 하고, 그리기 시점에는 그 중 하나를 선택해서 사용해야 합니다.
 
-이를 위해 프레임버퍼를 저장한 `std::vector` 클래스 멤버를 하나 더 만듭니다:
+이를 위해 프레임버퍼를 저장할 `std::vector` 클래스 멤버를 하나 더 만듭니다:
 
 ```c++
 std::vector<VkFramebuffer> swapChainFramebuffers;
@@ -62,9 +62,9 @@ for (size_t i = 0; i < swapChainImageViews.size(); i++) {
 }
 ```
 
-보이시는 것처럼 프레임버퍼의 생성은 매우 직관적입니다. 먼저 어떤 `renderPass`에 프레임버퍼가 호환되어야 하는지 명시합니다. 호환되는 경우에만 렌더 패스에 프레임버퍼를 사용할 수 있는데, 같은 수와 타입의 어태치먼트를 사용해야 호환됩니다.
+보이시는 것처럼 프레임버퍼의 생성은 매우 직관적입니다. 먼저 어떤 `renderPass`에 프레임버퍼가 호환되어야 하는지 명시합니다. 호환되는 경우에만 렌더 패스에 프레임버퍼를 사용할 수 있는데, 숫자와 타입이 같아야 합니다.
 
-`attachmentCount`와 `pAttachments` 매개변수는 렌더 패스의 `pAttachment` 배열에서, 해당하는 어태치먼트 기술자와 바인딩될 `VkImageView` 객체를 명시합니다.
+`attachmentCount`와 `pAttachments` 매개변수는 렌더 패스의 `pAttachment` 배열에 해당하는 어태치먼트 기술자와 바인딩될 `VkImageView` 객체를 명시합니다.
 
 `width` 와 `height` 매개변수는 설명하지 않아도 될 것 같고, `layers`는 이미지 배열의 레이어 수를 의미합니다. 우리 스왑 체인 이미지는 하나이므로, 레이어 수도 `1`입니다.
 
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md b/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
index 8cacaadb..26a2585a 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md
@@ -1,4 +1,4 @@
-그리기 명령이나 메모리 전송과 같은 Vulkan의 명령(command)은 함수 호출을 통해 직접 수행되는 것이 아닙니다. 수행하고자 하는 연산들을 모두 명령 버퍼 객체에 먼저 기록해야 합니다. 이로 인해 Vulkan에게 우리가 하고자 하는 것들을 알려줄 준비가 완료되었다면, 모든 명령이 한꺼번에 Vulkan으로 제출(submit)되어 동시에 실행 가능한 상태가 된다는 것입니다. 또한 원한다면 여러 쓰레드에서 명령을 기록할 수 있다는 장점도 있습니다.
+그리기 명령이나 메모리 전송과 같은 Vulkan의 명령(command)은 함수 호출을 통해 직접 수행되는 것이 아닙니다. 수행하고자 하는 연산들을 모두 명령 버퍼 객체에 먼저 기록해야 합니다. 이로 인해 Vulkan에게 우리가 하고자 하는 것들을 알려줄 준비가 완료되었다면, 모든 명령이 한꺼번에 Vulkan으로 제출(submit)되어 동시에 실행 가능한 상태가 됩니다. 또한 원한다면 여러 쓰레드에서 명령을 기록할 수 있다는 장점도 있습니다.
 
 ## 명령 풀(Command pools)
 
@@ -159,7 +159,7 @@ if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
 
 `pInheritanceInfo` 매개변수는 보조 명령 버퍼에만 해당됩니다. 주 명령 버퍼에서 호출될 떄 어떤 상태를 상속(inherit)하는지 명시합니다.
 
-명령 버퍼가 이미 기록된 상태에서 `vkBeginCommandBuffer`를 호출하면 암시적으로 버퍼가 리셋됩니다. 명령을 버퍼에 추가(append)하는 것은 불가능합니다.
+명령 버퍼가 이미 기록된 상태에서 `vkBeginCommandBuffer`를 호출하면 암시적으로 버퍼가 리셋됩니다. 명령을 버퍼에 나중에 추가(append)하는 것은 불가능합니다.
 
 ## 렌더 패스 시작하기
 
@@ -172,7 +172,7 @@ renderPassInfo.renderPass = renderPass;
 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
 ```
 
-첫 매개변수는 렌더패스 그 자체와 바인딩할 어태치먼트입니다. 각 스왑 체인 이미지에 대해 프레임버퍼를 만들었고, 색상 어태치먼트로 명시된 상태입니다. 따라서 그 프레임버퍼를 우리가 그리고자 하는 스왑체인 이미지로 바인딩해야 합니다. 넘어온 imageIndex를 사용해 현재 스왑체인 이미지의 적정한 프레임버퍼를 선택할 수 있습니다.
+첫 매개변수는 렌더패스 그 자체, 그리고 바인딩할 어태치먼트입니다. 각 스왑 체인 이미지에 대해 프레임버퍼를 만들었고, 색상 어태치먼트로 명시된 상태입니다. 따라서 그 프레임버퍼를 우리가 그리고자 하는 스왑체인 이미지로 바인딩해야 합니다. 전달된 imageIndex를 사용해 현재 스왑체인 이미지 중 적절한 프레임버퍼를 선택할 수 있습니다.
 
 ```c++
 renderPassInfo.renderArea.offset = {0, 0};
@@ -197,7 +197,7 @@ vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE)
 
 모든 명령의 첫 매개변수는 명령을 기록할 명령 버퍼입니다. 두 번째 매개변수는 방금 만든, 렌더 패스 세부사항을 명시합니다. 마지막 매개변수는 렌더 패스 안의 그리기 명령이 어떻게 제공될지를 제어합니다. 두 개의 값 중 하나입니다:
 
-* `VK_SUBPASS_CONTENTS_INLINE`: 렌더 패스 명령이 주 명령 버퍼에 포함되어 있고 보조 명령 버퍼는 실행되지 않음.
+* `VK_SUBPASS_CONTENTS_INLINE`: 렌더 패스 명령이 주 명령 버퍼에 포함되어 있고 보조 명령 버퍼는 실행되지 않음
 * `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS`: 렌더 패스 명령이 보조 명령 버퍼에서 실행됨
 
 보조 명령 버퍼는 사용하지 않을 것이므로, 첫 번째 값을 선택합니다.
@@ -210,7 +210,7 @@ vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE)
 vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
 ```
 
-두 분째 매개변수는 파이프라인 객체가 그래픽스 파이프라인인지 계산(compute) 파이프라인인지를 명시합니다. 이제 Vulkan에게 그래픽스 파이프라인에서 어떤 명령을 실행하고 프래그먼트 셰이더에서 어떤 어태치먼트를 사용할 것인지를 알려 주었습니다.
+두 번째 매개변수는 파이프라인 객체가 그래픽스 파이프라인인지 계산(compute) 파이프라인인지를 명시합니다. 이제 Vulkan에게 그래픽스 파이프라인에서 어떤 명령을 실행하고 프래그먼트 셰이더에서 어떤 어태치먼트를 사용할 것인지를 알려 주었습니다.
 
 [고정 함수 챕터](../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state)에서 이야기 한 것처럼, 우리는 파이프라인에게 뷰포트와 시저 상태가 동적일 것이라고 명시해 둔 상태입니다. 따라서 이들을 명령 버퍼에서 그리기 명령을 수행하기 이전에 설정해 주어야 합니다:
 
@@ -238,7 +238,7 @@ vkCmdDraw(commandBuffer, 3, 1, 0, 0);
 
 실제 `vkCmdDraw` 명령은 아주 어렵지 않은데 미리 모든 정보를 설정해 두었기 때문입니다. 이 명령은 명령 버퍼 이외에 다음과 같은 매개변수를 갖습니다:
 
-* `vertexCount`: 정점 버퍼는 없어도, 그리기 위해서는 3개의 정점이 필요합니다.
+* `vertexCount`: 현재 정점 버퍼는 없을지라도, 드로우(draw)를 위해서는 3개의 정점이 필요합니다.
 * `instanceCount`: 인스턴스(instanced) 렌더링을 위해 사용되는데, 그 기능을 사용하지 않는경우 `1`로 설정합니다.
 * `firstVertex`: 정점 버퍼의 오프셋을 설정하는 데 사용되며, `gl_VertexIndex`의 가장 작은 값을 정의합니다.
 * `firstInstance`: 인스턴스 렌더링의 오프셋을 설정하는 데 사용되며, `gl_InstanceIndex`의 가장 작은 값을 정의합니다.

From 03ea8d382c35ecfbd787b2e9b83f27011f101930 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 7 Feb 2024 12:31:07 +0900
Subject: [PATCH 26/47] kr translate 03-03-02 rendering and presentation

---
 .../02_Rendering_and_presentation.md          | 511 ++++++++----------
 1 file changed, 217 insertions(+), 294 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
index 233c059d..0ab0c208 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
@@ -1,8 +1,6 @@
 
-This is the chapter where everything is going to come together. We're going to
-write the `drawFrame` function that will be called from the main loop to put the
-triangle on the screen. Let's start by creating the function and call it from
-`mainLoop`:
+이제 모든 것이 결합되는 챕터입니다. 메인 루프에서 실행되어 삼각형을 화면에 표시하는 `drawFrame`함수를 작성할 것입니다.
+이 함수를 만들고 `mainLoop`에서 호출하도록 합시다:
 
 ```c++
 void mainLoop() {
@@ -19,159 +17,138 @@ void drawFrame() {
 }
 ```
 
-## Outline of a frame
+## 프레임 개요
 
-At a high level, rendering a frame in Vulkan consists of a common set of steps:
+큰 그림에서 보자면, Vulkan에서 프레임을 하나 렌더링하는 것은 다음 단계들로 이루어집니다:
 
-* Wait for the previous frame to finish
-* Acquire an image from the swap chain
-* Record a command buffer which draws the scene onto that image
-* Submit the recorded command buffer
-* Present the swap chain image
+* 이전 프레임이 끝나기까지 대기
+* 스왑 체인에서 이미지 얻어오기
+* 그 이미지에 장면을 드로우하기 위한 명령 버퍼 기록
+* 기록된 명령 버퍼 제출
+* 스왑 체인 이미지 표시
 
-While we will expand the drawing function in later chapters, for now this is the
-core of our render loop.
+챕터를 진행하면서 드로우 함수를 확장할 것이지만, 지금은 이 정도가 렌더링 루프의 핵심이라 보시면 됩니다.
 
 <!-- Add an image that shows an outline of the frame -->
 
-## Synchronization
+## 동기화(Synchronization)
 
 <!-- Maybe add images for showing synchronization -->
 
-A core design philosophy in Vulkan is that synchronization of execution on
-the GPU is explicit. The order of operations is up to us to define using various
-synchronization primitives which tell the driver the order we want things to run
-in. This means that many Vulkan API calls which start executing work on the GPU
-are asynchronous, the functions will return before the operation has finished.
-
-In this chapter there are a number of events that we need to order explicitly
-because they happen on the GPU, such as:
-
-* Acquire an image from the swap chain
-* Execute commands that draw onto the acquired image
-* Present that image to the screen for presentation, returning it to the swapchain
-
-Each of these events is set in motion using a single function call, but are all
-executed asynchronously. The function calls will return before the operations
-are actually finished and the order of execution is also undefined. That is
-unfortunate, because each of the operations depends on the previous one
-finishing. Thus we need to explore which primitives we can use to achieve
-the desired ordering.
-
-### Semaphores
-
-A semaphore is used to add order between queue operations. Queue operations
-refer to the work we submit to a queue, either in a command buffer or from
-within a function as we will see later. Examples of queues are the graphics
-queue and the presentation queue. Semaphores are used both to order work inside
-the same queue and between different queues.
-
-There happens to be two kinds of semaphores in Vulkan, binary and timeline.
-Because only binary semaphores will be used in this tutorial, we will not
-discuss timeline semaphores. Further mention of the term semaphore exclusively
-refers to binary semaphores.
-
-A semaphore is either unsignaled or signaled. It begins life as unsignaled. The
-way we use a semaphore to order queue operations is by providing the same
-semaphore as a 'signal' semaphore in one queue operation and as a 'wait'
-semaphore in another queue operation. For example, lets say we have semaphore S
-and queue operations A and B that we want to execute in order. What we tell
-Vulkan is that operation A will 'signal' semaphore S when it finishes executing,
-and operation B will 'wait' on semaphore S before it begins executing. When
-operation A finishes, semaphore S will be signaled, while operation B wont
-start until S is signaled. After operation B begins executing, semaphore S
-is automatically reset back to being unsignaled, allowing it to be used again.
-
-Pseudo-code of what was just described:
+(*역주: 여기서 '동기화'는 동시에 실행한다는 의미가 아닌 올바른 실행 스케줄(순서)를 유지한다는 의미*)
+Vulkan의 핵심 설계 철학은 GPU에서의 실행 동기화가 명시적이라는 겁니다.
+연산의 순서는 우리가 다양한 동기화 요소들을 가지고 정의하는 것이고, 이를 통해 드라이버는 원하는 실행 순서를 파악합니다.
+이 말은 많은 Vulkan API 호출이 GPU에서 실제 동작하는 시기는 비동기적이고, 실제 연산이 끝나기 전에 함수가 종료된다는 뜻입니다.
+
+이 챕터에서는 여러 이벤트들에 대해 순서를 명시적으로 지정해야 할 필요가 있습니다.
+이러한 이벤트들이 GPU에서 일어나기 때문인데 그 예로:
+
+* 스왑 체인에서 이미지 얻어오기
+* 그 이미지를 드로우하기 위한 명령 실행
+* 표시를 위해 이미지를 화면에 나타내고 스왑체임으로 다시 반환
+
+이러한 이벤트들은 단일 함수 호출로 동작하지만 실제 실행은 비동기적으로 이루어집니다.
+실제 연산이 끝나기 전에 함수가 반환되고 실행 순서도 정의되어 있지 않습니다.
+각 연산이 이전 연산에 종속적이기 때문에 이렇게 되면 안됩니다.
+따라서 원하는 순서대로 실행이 될 수 있도록 하게 해 주는 요소들을 살펴볼 것입니다.
+
+### 세마포어(Semaphores)
+
+큐 연산들 사이에 순서를 추가하기 위해 세마포어를 사용할 수 있습니다.
+여기서 큐 연산은 우리가 큐에 제출한 작업들이며 명령 버퍼 내의 연산이거나 나중에 볼 함수의 연산들입니다.
+큐의 예시로는 그래픽스 큐와 표시 큐가 있습니다.
+세마포어는 동일 큐 내에서, 그리고 서로 다른 큐 사이에서 순서를 정하기 위해 사용됩니다.
+
+Vulkan에는 바이너리와 타임라인(timeline) 세마포어가 있습니다.
+이 튜토리얼에서는 바이너리 세마포어만 사용할 것이고 타임라인 세마포어에 대해서는 논의하지 않을 것입니다.
+이후에 세마포어라고 한다면 바이너리 세마포어를 의미하는 것입니다.
+
+세마포어는 시그널 상태(signaled)이거나 시그널이 아닌 상태(unsignaled)일 수 있습니다. 일단 시그널이 아닌 상태로 시작됩니다.
+우리가 세마포어를 사용하는 방식은 우선 동일한 세마포어를 한 쪽 큐 연산에는 '시그널(signal)' 세마포어로, 다른 쪽에는 '대기(wait)' 세마포어로 사용하는 것입니다.
+예를 들어 세마포어 S가 있고 큐 연산 A와 B가 있다고 합시다.
+Vulkan에게 우리가 알려주는 것은 실행이 끝나면 연산 A 세마포어 S를 '시그널'하도록 하고, 연산 B는 실행 전에 세마포어 S를 '대기'하도록 하는 것입니다.
+연산 A가 끝나면 세마포어 S가 시그널 상태가 될 것인데, 연산 B는 S가 시그널 상태가 될때까지는 실행되지 않습니다.
+연산 B가 실행이 시작되면 세마포어 S는 자동적으로 시그널이 아닌 상태로 돌아오고 다시 사용 가능한 상태가 됩니다.
+
+방금 설명한 내용을 의사 코드로 표현하자면 다음과 같습니다:
 ```
-VkCommandBuffer A, B = ... // record command buffers
-VkSemaphore S = ... // create a semaphore
+VkCommandBuffer A, B = ... // 명령 버퍼 기록
+VkSemaphore S = ... // 세마포어 생성
 
-// enqueue A, signal S when done - starts executing immediately
+// A를 큐에 등록하고 끝나면 S를 '시그널'하도록 함 - 즉시 실행 시작
 vkQueueSubmit(work: A, signal: S, wait: None)
 
-// enqueue B, wait on S to start
+// B를 큐에 등록하고 시작하기 위해 S를 기다림
 vkQueueSubmit(work: B, signal: None, wait: S)
 ```
 
-Note that in this code snippet, both calls to `vkQueueSubmit()` return
-immediately - the waiting only happens on the GPU. The CPU continues running
-without blocking. To make the CPU wait, we need a different synchronization
-primitive, which we will now describe.
-
-### Fences
-
-A fence has a similar purpose, in that it is used to synchronize execution, but
-it is for ordering the execution on the CPU, otherwise known as the host.
-Simply put, if the host needs to know when the GPU has finished something, we
-use a fence.
-
-Similar to semaphores, fences are either in a signaled or unsignaled state.
-Whenever we submit work to execute, we can attach a fence to that work. When
-the work is finished, the fence will be signaled. Then we can make the host
-wait for the fence to be signaled, guaranteeing that the work has finished
-before the host continues.
-
-A concrete example is taking a screenshot. Say we have already done the
-necessary work on the GPU. Now need to transfer the image from the GPU over
-to the host and then save the memory to a file. We have command buffer A which
-executes the transfer and fence F. We submit command buffer A with fence F,
-then immediately tell the host to wait for F to signal. This causes the host to
-block until command buffer A finishes execution. Thus we are safe to let the
-host save the file to disk, as the memory transfer has completed.
-
-Pseudo-code for what was described:
+주의하셔야 할것은 위 코드에서 두 번의 `vkQueueSubmit()` 호출은 즉시 반환된다는 것입니다.
+대기 과정은 GPU에서만 일어납니다.
+CPU는 블러킹(blocking)없이 계속 실행합니다.
+CPU가 대기하도록 하려면 다른 동기화 요소가 필요합니다.
+
+### 펜스(Fences)
+
+펜스도 비슷한 목적을 가지고 있습니다. 동일하게 동기화 실행을 위해 사용되며 CPU(호스트라고도 함)에서의 순차적 실행이 목적이라는 것만 다릅니다.
+간단히 말해 호스트가 GPU가 어떤 작업을 끝냈다는 것을 알아야만 하는 상황에서 펜스를 사용합니다.
+
+세마포어와 유사하게 펜스도 시그널 상태와 시그널이 아닌 상태를 가집니다.
+작업 실행을 제출할 때 해당 작업에 펜스를 부착할 수 있습니다.
+작업이 끝나면 펜스는 시그널 상태가 됩니다.
+그리고 호스트는 펜스가 시그널 상태가 될때까지 기다리게 하면 호스트가 작업이 끝난 뒤에야만 진행되도록 보장할 수 있습니다.
+
+구체적인 예시로 스크린샷을 찍는 예시가 있습니다.
+예를들어 GPU에서 필요한 작업을 이미 수행했다고 합시다.
+이제 이미지를 GPU에서부터 호스트로 전송하고 메모리를 파일로 저장해야 합니다.
+전송을 위한 명령 버퍼 A와 펜스 F가 있다고 합시다.
+명령 버퍼 A를 펜스 F와 함께 제출하고 호스트에게 F가 시그널 상태가 될때까지 기다리게 합니다.
+이렇게 하면 호스트는 명령 버퍼가 실행을 끝낼 때까지 블러킹 상태가 됩니다.
+따라서 안전하게 메모리 전송이 끝난 뒤 디스크에 파일을 저장할 수 있습니다.
+
+방금 설명한 내용을 의사 코드로 표현하자면 다음과 같습니다:
 ```
-VkCommandBuffer A = ... // record command buffer with the transfer
-VkFence F = ... // create the fence
+VkCommandBuffer A = ... // 전송을 위한 명령 버퍼 기록
+VkFence F = ... // 펜스 생성
 
-// enqueue A, start work immediately, signal F when done
+// A를 큐에 등록하고 즉시 실행을 시작하며, 끝나면 F를 시그널 상태로 만듬
 vkQueueSubmit(work: A, fence: F)
 
-vkWaitForFence(F) // blocks execution until A has finished executing
+vkWaitForFence(F) // A의 실행이 끝날때 까지 실행 중단(블럭)
 
-save_screenshot_to_disk() // can't run until the transfer has finished
+save_screenshot_to_disk() // 전송이 끝날 때까지 싱행이 불가능
 ```
 
-Unlike the semaphore example, this example *does* block host execution. This
-means the host won't do anything except wait until execution has finished. For
-this case, we had to make sure the transfer was complete before we could save
-the screenshot to disk.
+세마포어 예시와는 달리 이 예시는 호스트의 실행을 *블럭*합니다.
+즉 모든 연산이 끝날때까지 호스트는 아무것도 하지 않는다는 뜻입니다.
+이 경우에는 스크린샷을 디스크에 저장하기 전까지 전송이 끝나야만 한다는 것을 보장해야 하기 때문입니다.
 
-In general, it is preferable to not block the host unless necessary. We want to
-feed the GPU and the host with useful work to do. Waiting on fences to signal
-is not useful work. Thus we prefer semaphores, or other synchronization
-primitives not yet covered, to synchronize our work.
+일반적으로 꼭 필요한 경우가 아니라면 호스트를 블럭하지 않는것이 좋습니다.
+GPU에 전달하고 난 뒤 호스트는 다른 유용한 작업을 하는 것이 좋습니다.
+펜스가 시그널 상태가 되는 것을 기다리는 것은 유용한 작업이 아니죠.
+따라서 작업의 동기화를 위해서는 세마포어를 사용하거나, 아직 다루지 않은 다른 동기화 방법을 사용해야 합니다.
 
-Fences must be reset manually to put them back into the unsignaled state. This
-is because fences are used to control the execution of the host, and so the
-host gets to decide when to reset the fence. Contrast this to semaphores which
-are used to order work on the GPU without the host being involved.
+펜스의 경우 시그널이 아닌 상태로 되돌리는 작업은 매뉴얼하게 수행해 주어야 합니다.
+펜스는 호스트의 실행을 제어하기 위해 사용되는 것이고 호스트가 펜스를 되돌리는 시점을 결정하게 됩니다.
+이와는 달리 세마포어는 호스트와 상관없이 GPU 내의 작업 순서를 결정하기 위해 사용됩니다.
 
-In summary, semaphores are used to specify the execution order of operations on
-the GPU while fences are used to keep the CPU and GPU in sync with each-other.
+정리하자면, 세마포어는 GPU에서의 실행 순서를 명시하기 위해 사용하고 펜스는 CPU와 GPU간의 동기화를 위해 사용한다는 것입니다.
 
-### What to choose?
+### 어떻게 결정해야 하나요?
 
-We have two synchronization primitives to use and conveniently two places to
-apply synchronization: Swapchain operations and waiting for the previous frame
-to finish. We want to use semaphores for swapchain operations because they
-happen on the GPU, thus we don't want to make the host wait around if we can
-help it. For waiting on the previous frame to finish, we want to use fences
-for the opposite reason, because we need the host to wait. This is so we don't
-draw more than one frame at a time. Because we re-record the command buffer
-every frame, we cannot record the next frame's work to the command buffer
-until the current frame has finished executing, as we don't want to overwrite
-the current contents of the command buffer while the GPU is using it.
+사용할 수 있는 두 종류의 동기화 요소가 있고 마침 동기화가 필요한 두 과정이 존재합니다.
+스왑체인 연산과 이전 프레임이 끝나기를 기다리는 과정입니다.
+스왑체인 연산에 대해서는 세마포어를 사용할 것인데 이는 GPU에서 수행되는 작업이고, 호스트가 그 동안 기다리는 것을 원치 않기 때문입니다.
+이전 프레임이 끝나기를 기다리는 것은 반대의 이유로 펜스를 사용할 것인데 호스트가 기다려야 하기 때문입니다.
+그리고 기다려야 하는 이유는 한번에 하나 이상의 프레임이 그려지는 것을 원치 않기 때문입니다.
+매 프레임마다 명령 버퍼를 다시 기록하기 때문에 다음 프레임을 위한 작업을 현재 프레임의 실행이 끝나기 전에 기록할 수 없습니다.
+만일 그렇게 하게 되면 GPU가 명령을 실행하는 동안 명령 버퍼가 덮어쓰여지게 됩니다.
 
-## Creating the synchronization objects
+## 동기화 객체의 생성
 
-We'll need one semaphore to signal that an image has been acquired from the
-swapchain and is ready for rendering, another one to signal that rendering has
-finished and presentation can happen, and a fence to make sure only one frame
-is rendering at a time.
+스왑체인으로부터 이미지가 획득되어 렌더링할 준비가 되었는지에 대한 세마포어 하나와 렌더링이 끝나서 표시할 준비가 되었다는 세마포어 하나가 필요합니다.
+또한 한 번에 하나의 프레임만 렌더링하기 위한 펜스 하나가 필요합니다.
 
-Create three class members to store these semaphore objects and fence object:
+이 세마포어와 펜스 객체를 저장하기 위한 클래스 멤버를 만듭니다:
 
 ```c++
 VkSemaphore imageAvailableSemaphore;
@@ -179,8 +156,7 @@ VkSemaphore renderFinishedSemaphore;
 VkFence inFlightFence;
 ```
 
-To create the semaphores, we'll add the last `create` function for this part of
-the tutorial: `createSyncObjects`:
+세마포어를 만들기 위해 이 장의 마지막 `create`함수인 `createSyncObjects`를 추가합니다:
 
 ```c++
 void initVulkan() {
@@ -206,9 +182,7 @@ void createSyncObjects() {
 }
 ```
 
-Creating semaphores requires filling in the `VkSemaphoreCreateInfo`, but in the
-current version of the API it doesn't actually have any required fields besides
-`sType`:
+세마포어 생성을 위해서는 `VkSemaphoreCreateInfo`를 채워야 하는데 현재 API 버전에서는 `sType` 이외의 다른 필드는 요구하지 않습니다:
 
 ```c++
 void createSyncObjects() {
@@ -217,18 +191,16 @@ void createSyncObjects() {
 }
 ```
 
-Future versions of the Vulkan API or extensions may add functionality for the
-`flags` and `pNext` parameters like it does for the other structures.
+나중 버전의 Vulkan API나 확장에서는 다른 구조체처럼 `flags`와 `pNext` 기능이 추가될 수 있습니다.
 
-Creating a fence requires filling in the `VkFenceCreateInfo`:
+펜스를 만들기 위해서는 `VkFenceCreateInfo`를 채워야 합니다:
 
 ```c++
 VkFenceCreateInfo fenceInfo{};
 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
 ```
 
-Creating the semaphores and fence follows the familiar pattern with
-`vkCreateSemaphore` & `vkCreateFence`:
+세마포어와 펜스를 만드는 것은 `vkCreateSemaphore` & `vkCreateFence`를 사용하는 기존과 비슷한 패턴입니다:
 
 ```c++
 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
@@ -238,8 +210,7 @@ if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore)
 }
 ```
 
-The semaphores and fence should be cleaned up at the end of the program, when
-all commands have finished and no more synchronization is necessary:
+프로그램의 종료 시점에 세마포어와 펜스는 정리되어야 하며 이는 모든 명령이 끝나고 더이상 동기화가 필요하지 않은 시점입니다:
 
 ```c++
 void cleanup() {
@@ -248,13 +219,12 @@ void cleanup() {
     vkDestroyFence(device, inFlightFence, nullptr);
 ```
 
-Onto the main drawing function!
+메인 그리기 함수로 가 봅시다!
 
-## Waiting for the previous frame
+## 이전 프레임 기다리기
 
-At the start of the frame, we want to wait until the previous frame has
-finished, so that the command buffer and semaphores are available to use. To do
-that, we call `vkWaitForFences`:
+프레임 시작 시점에 이전 프레임이 끝나기를 기다려야 하므로 명령 버퍼와 세마포어(*역주: 펜스일 듯*)가 사용 가능해야 합니다.
+이를 위해 `vkWaitForFences`를 호출합니다.
 
 ```c++
 void drawFrame() {
@@ -262,31 +232,26 @@ void drawFrame() {
 }
 ```
 
-The `vkWaitForFences` function takes an array of fences and waits on the host
-for either any or all of the fences to be signaled before returning. The
-`VK_TRUE` we pass here indicates that we want to wait for all fences, but in
-the case of a single one it doesn't matter. This function also has a timeout
-parameter that we set to the maximum value of a 64 bit unsigned integer,
-`UINT64_MAX`, which effectively disables the timeout.
+`vkWaitForFences` 함수는 펜스 배열을 받아서, 몇 개 또는 전체 펜스들이 시그널인 상태를 반환할 때까지 호스트를 대기하도록 합니다.
+인자로 넘긴 `VK_TRUE`는 모든 펜스를 기다리겠다는 의미인데 지금은 하나의 펜스만 있으므로 크게 의미는 없습니다.
+이 함수는 또한 타임아웃(timeout) 매개변수를 갖는데 `UINT64_MAX`를 통해 64비트 부호없는 정수의 최대값으로 설정했습니다.
+즉 타임아웃을 사용하지 않겠다는 의미입니다.
+
+대기 후에 `vkResetFences` 호출을 통해 펜스를 시그널이 아닌 상태로 리셋해 주어야 합니다:
 
-After waiting, we need to manually reset the fence to the unsignaled state with
-the `vkResetFences` call:
 ```c++
     vkResetFences(device, 1, &inFlightFence);
 ```
 
-Before we can proceed, there is a slight hiccup in our design. On the first
-frame we call `drawFrame()`, which immediately waits on `inFlightFence` to
-be signaled. `inFlightFence` is only signaled after a frame has finished
-rendering, yet since this is the first frame, there are no previous frames in
-which to signal the fence! Thus `vkWaitForFences()` blocks indefinitely,
-waiting on something which will never happen.
+더 진행하기 전에 현재 설계에 약간의 문제가 있습니다.
+첫 프레임에 `drawFrame()`를 호출하기 때문에 바로 `inFlightFence`가 시그널 상태가 되도록 기다립니다.
+`inFlightFence`는 프레임 렌더링이 끝나야 시그널 상태가 되는데 지금은 첫 번째 프레임이므로 펜스를 시그널 상태로 만들어줄 이전 프레임이 없습니다!
+따라서 `vkWaitForFences()`가 프로세스를 무한정 블럭해서 아무 일도 일어나지 않을 것입니다.
 
-Of the many solutions to this dilemma, there is a clever workaround built into
-the API. Create the fence in the signaled state, so that the first call to
-`vkWaitForFences()` returns immediately since the fence is already signaled.
+이 문제를 해결하는 많은 방법 중 API에 들어있는 똑똑한 해결책이 하나 있습니다.
+펜스를 시그널인 상태로 생성해서 `vkWaitForFences()`의 첫 호출이 바로 반환되도록 하는 것입니다.
 
-To do this, we add the `VK_FENCE_CREATE_SIGNALED_BIT` flag to the `VkFenceCreateInfo`:
+이렇게 하기 위해서 `VK_FENCE_CREATE_SIGNALED_BIT` 플래그를 `VkFenceCreateInfo`에 추가합니다:
 
 ```c++
 void createSyncObjects() {
@@ -300,11 +265,10 @@ void createSyncObjects() {
 }
 ```
 
-## Acquiring an image from the swap chain
+## 스왑 체인에서 이미지 얻어오기
 
-The next thing we need to do in the `drawFrame` function is acquire an image
-from the swap chain. Recall that the swap chain is an extension feature, so we
-must use a function with the `vk*KHR` naming convention:
+다음으로 `drawFrame` 함수에서 할 일은 스왑 체인으로부터 이미지를 얻어오는 것입니다.
+스왑 체인은 확장 기능이므로 `vk*KHR` 네이밍으로 되어 있는 함수를 사용해야 하는 것을 잊지 마세요.
 
 ```c++
 void drawFrame() {
@@ -315,47 +279,42 @@ void drawFrame() {
 }
 ```
 
-The first two parameters of `vkAcquireNextImageKHR` are the logical device and
-the swap chain from which we wish to acquire an image. The third parameter
-specifies a timeout in nanoseconds for an image to become available. Using the
-maximum value of a 64 bit unsigned integer means we effectively disable the
-timeout.
+`vkAcquireNextImageKHR`의 첫 두 매개변수는 논리적 장치와 이미지를 얻어오려고 하는 스왑 체인입니다.
+세 번째 매개변수는 이미지가 가용할때까지의 나노초 단위 타임아웃 시간입니다.
+64비트의 부호없는 정주의 최대값을 사용했고, 그 의미는 타임아웃을 적용하지 않겠다는 의미입니다.
 
-The next two parameters specify synchronization objects that are to be signaled
-when the presentation engine is finished using the image. That's the point in
-time where we can start drawing to it. It is possible to specify a semaphore,
-fence or both. We're going to use our `imageAvailableSemaphore` for that purpose
-here.
+다음 두 매개변수는 표시 엔진이 이미지 사용이 끝나면 시그널 상태로 변환될 동기화 객체들을 명시합니다.
+그 시점이 바로 우리가 새로운 프레임을 드로우 할 시점입니다.
+세마포어나 펜스, 또는 그 둘 다를 명시할 수 있습니다.
+여기서는 `imageAvailableSemaphore`를 사용할 것입니다.
 
-The last parameter specifies a variable to output the index of the swap chain
-image that has become available. The index refers to the `VkImage` in our
-`swapChainImages` array. We're going to use that index to pick the `VkFrameBuffer`.
+마지막 매개변수는 사용이 가능해진 스왑 체인 이미지의 인덱스를 출력할 변수입니다.
+이 인덱스는 `swapChainImages` 배열의 `VkImage`의 인덱스입니다.
+이 인덱스를 사용해 `VkFrameBuffer`를 선택할 것입니다.
 
-## Recording the command buffer
+## 명령 버퍼 기록
 
-With the imageIndex specifying the swap chain image to use in hand, we can now
-record the command buffer. First, we call `vkResetCommandBuffer` on the command
-buffer to make sure it is able to be recorded.
+사용할 스왑 체인 이미지의 imageIndex를 얻었으면 이제 명령 버퍼를 기록할 수 있습니다.
+먼저, `vkResetCommandBuffer`를 호출해 명령 버퍼가 기록이 가능한 상태가 되도록 합니다.
 
 ```c++
 vkResetCommandBuffer(commandBuffer, 0);
 ```
 
-The second parameter of `vkResetCommandBuffer` is a `VkCommandBufferResetFlagBits`
-flag. Since we don't want to do anything special, we leave it as 0.
+`vkResetCommandBuffer`의 두 번째 매개변수는 `VkCommandBufferResetFlagBits` 플래그입니다.
+특별히 무언가 작업을 하지는 않을 것이므로 0으로 두겠습니다.
 
-Now call the function `recordCommandBuffer` to record the commands we want.
+이제 `recordCommandBuffer`를 호출하여 원하는 명령을 기록합니다.
 
 ```c++
 recordCommandBuffer(commandBuffer, imageIndex);
 ```
 
-With a fully recorded command buffer, we can now submit it.
+기록이 완료된 명령 버퍼가 있으니 이제 제출할 수 있습니다.
 
-## Submitting the command buffer
+## 명령 버퍼 제출
 
-Queue submission and synchronization is configured through parameters in the
-`VkSubmitInfo` structure.
+큐 제출과 동기화는 `VkSubmitInfo` 구조체의 매개변수들로 설정합니다.
 
 ```c++
 VkSubmitInfo submitInfo{};
@@ -368,21 +327,18 @@ submitInfo.pWaitSemaphores = waitSemaphores;
 submitInfo.pWaitDstStageMask = waitStages;
 ```
 
-The first three parameters specify which semaphores to wait on before execution
-begins and in which stage(s) of the pipeline to wait. We want to wait with
-writing colors to the image until it's available, so we're specifying the stage
-of the graphics pipeline that writes to the color attachment. That means that
-theoretically the implementation can already start executing our vertex shader
-and such while the image is not yet available. Each entry in the `waitStages`
-array corresponds to the semaphore with the same index in `pWaitSemaphores`.
+첫 세 개의 매개변수는 실행이 시작되기 전 대기할 세마포어, 그리고 파이프라인의 어떤 스테이지(stage)에서 대기할지를 명시합니다.
+우리의 경우 색상 값을 이미지에 기록하는 동안 대기할 것이므로 색상 어태치먼트에 쓰기를 수행하는 스테이지를 명시하였습니다.
+즉 이론적으로는 우리의 구현이 정점 셰이더 등등을 이미지가 가용하지 않은 상태에서 실행될 수 있다는 뜻입니다.
+`waitStages` 배열의 각 요소가 `pWaitSemaphores`의 동일한 인덱스와 대응됩니다.
 
 ```c++
 submitInfo.commandBufferCount = 1;
 submitInfo.pCommandBuffers = &commandBuffer;
 ```
 
-The next two parameters specify which command buffers to actually submit for
-execution. We simply submit the single command buffer we have.
+다음 두 매개변수는 실제로 실행을 위해 어떤 명령 버퍼를 제출할 것인지를 명시합니다.
+만들어둔 유일한 명령 버퍼를 제출합니다.
 
 ```c++
 VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
@@ -390,9 +346,8 @@ submitInfo.signalSemaphoreCount = 1;
 submitInfo.pSignalSemaphores = signalSemaphores;
 ```
 
-The `signalSemaphoreCount` and `pSignalSemaphores` parameters specify which
-semaphores to signal once the command buffer(s) have finished execution. In our
-case we're using the `renderFinishedSemaphore` for that purpose.
+`signalSemaphoreCount`와 `pSignalSemaphores` 매개변수는 명령 버퍼 실행이 끝나면 시그널 상태가 될 세마포어를 명시합니다.
+우리의 경우 `renderFinishedSemaphore`를 사용합니다.
 
 ```c++
 if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
@@ -400,36 +355,27 @@ if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
 }
 ```
 
-We can now submit the command buffer to the graphics queue using
-`vkQueueSubmit`. The function takes an array of `VkSubmitInfo` structures as
-argument for efficiency when the workload is much larger. The last parameter
-references an optional fence that will be signaled when the command buffers
-finish execution. This allows us to know when it is safe for the command
-buffer to be reused, thus we want to give it `inFlightFence`. Now on the next
-frame, the CPU will wait for this command buffer to finish executing before it
-records new commands into it.
-
-## Subpass dependencies
-
-Remember that the subpasses in a render pass automatically take care of image
-layout transitions. These transitions are controlled by *subpass dependencies*,
-which specify memory and execution dependencies between subpasses. We have only
-a single subpass right now, but the operations right before and right after this
-subpass also count as implicit "subpasses".
-
-There are two built-in dependencies that take care of the transition at the
-start of the render pass and at the end of the render pass, but the former does
-not occur at the right time. It assumes that the transition occurs at the start
-of the pipeline, but we haven't acquired the image yet at that point! There are
-two ways to deal with this problem. We could change the `waitStages` for the
-`imageAvailableSemaphore` to `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` to ensure that
-the render passes don't begin until the image is available, or we can make the
-render pass wait for the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage.
-I've decided to go with the second option here, because it's a good excuse to
-have a look at subpass dependencies and how they work.
-
-Subpass dependencies are specified in `VkSubpassDependency` structs. Go to the
-`createRenderPass` function and add one:
+이제 `vkQueueSubmit`를 사용해 명령 버퍼를 그래픽스 큐에 제출합니다.
+이 함수는 `VkSubmitInfo` 구조체 배열을 받을 수 있는데 작업량이 많을 때는 이 방식이 효율적입니다.
+마지막 매개변수는 펜스로 명령 버퍼 실행이 끝나면 시그널 상태가 됩니다.
+이렇게 하면 언제 안전하게 명령 버퍼를 다시 사용할 수 있는 상태가 되는지를 알 수 있으므로 `inFlightFence`를 사용합니다.
+이제 다음 프레임이 되면, CPU는 이번 명령 버퍼의 실행이 끝날때까지 대기하다가 새로룽 명령들을 기록하게 됩니다.
+
+## 서브패스 종속성(dependencies)
+
+렌더패스의 서브패스는 이미지 레이아웃의 전환을 자동적으로 처리해 준다는 사실을 기억하십시오.
+이러한 전환은 *서브패스 종속성*에 의해 제어되는데, 서브패스간의 메모리와 실행 종속성을 명시합니다.
+지금은 하나의 서브패스만 있는 상태지만 이 서브패스의 바로 이전과 이후의 연산 또한 암시적으로 "서브패스"로 간주됩니다.
+
+렌더패스의 시작 시점과 끝 시점에 전환을 처리해주는 내장된 종속성이 있긴 합니다만, 시작 시점의 경우 올바른 시점에 제어되지 않습니다.
+이는 전환이 파이프라인의 시작 시점에 일어난다고 가정하고 설계되었는데 우리의 경우 파이프라인 시작 시점에 이미지를 획득하지는 않은 상태입니다.
+이러한 문제를 해결하기 위한 방법이 두 가지가 있습니다.
+`imageAvailableSemaphore`의 to `waitStages`를 `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT`으로 바꾸어 이미지가 가용할 때까지 렌더패스를 시작하지 않는 방법이 있고,
+렌더패스가 `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` 스테이지를 대기하도록 하는 방법이 있습니다.
+저는 여기서 두 번째 방법을 선택했는데, 서브패스 종속성과 그 동작 방식을 살펴보는 데 좋기 때문입니다.
+
+서브패스 종속성은 `VkSubpassDependency` 구조체에 명시됩니다.
+`createRenderPass` 함수에 라애 코드를 추가합니다:
 
 ```c++
 VkSubpassDependency dependency{};
@@ -437,47 +383,39 @@ dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
 dependency.dstSubpass = 0;
 ```
 
-The first two fields specify the indices of the dependency and the dependent
-subpass. The special value `VK_SUBPASS_EXTERNAL` refers to the implicit subpass
-before or after the render pass depending on whether it is specified in
-`srcSubpass` or `dstSubpass`. The index `0` refers to our subpass, which is the
-first and only one. The `dstSubpass` must always be higher than `srcSubpass` to
-prevent cycles in the dependency graph (unless one of the subpasses is
-`VK_SUBPASS_EXTERNAL`).
+첫 두 필드는 의존(dependency)하는 서브패스와 종속(dependent)되는 서브패스를 명시합니다.(*역주: 의존=선행되어야 하는 서브패스, 종속=후행해야 하는 서브패스*)
+특수한 값인 `VK_SUBPASS_EXTERNAL`은 `srcSubpass` 또는 `dstSubpass` 중 어디에 설정되었느냐에 따라 서브패스의 앞과 뒤에 오는 암시적인 서브패스를 명시하는 값입니다.
+`0` 인덱스는 우리의 첫 번째(그리고 유일한) 서브패스를 의미하는 인덱스입니다.
+종속성 그래프의 사이클을 방지하기 위해 `dstSubpass`는 항상 `srcSubpass`보다 커야 합니다(둘 중 하나가 `VK_SUBPASS_EXTERNAL`가 아닌 경우에 해당).
 
 ```c++
 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
 dependency.srcAccessMask = 0;
 ```
 
-The next two fields specify the operations to wait on and the stages in which
-these operations occur. We need to wait for the swap chain to finish reading
-from the image before we can access it. This can be accomplished by waiting on
-the color attachment output stage itself.
+다음 두 개의 필드는 대기할 연산과 그 연산이 일어날 스테이지를 명시합니다.
+접근하기 전에, 스왑 체인이 이미지를 읽기를 마칠 때까지 기다려야 합니다.
+따라서 색상 어태치먼트 출력 자체를 기다리도록 하면 됩니다.
 
 ```c++
 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
 ```
 
-The operations that should wait on this are in the color attachment stage and
-involve the writing of the color attachment. These settings will
-prevent the transition from happening until it's actually necessary (and
-allowed): when we want to start writing colors to it.
+이 서브패스를 기다려야 하는 연산은 컬러 어태치먼트 스테이지에 있고 컬러 어태치먼트에 값을 쓰는 연산과 관련되어 있습니다.
+이렇게 설정하면 실제로 필요하고 허용되기 전까지는 이미지의 전환이 발생하지 않습니다.: when we want to start writing colors to it.
 
 ```c++
 renderPassInfo.dependencyCount = 1;
 renderPassInfo.pDependencies = &dependency;
 ```
 
-The `VkRenderPassCreateInfo` struct has two fields to specify an array of
-dependencies.
+`VkRenderPassCreateInfo` 구조체는 종속성의 배열을 명시하기 위한 두 개의 필드를 가지고 있습니다.
 
-## Presentation
+## 표시
 
-The last step of drawing a frame is submitting the result back to the swap chain
-to have it eventually show up on the screen. Presentation is configured through
-a `VkPresentInfoKHR` structure at the end of the `drawFrame` function.
+프레임을 그리는 마지막 단계는 결과를 스왑체인에 다시 제출해서 화면에 표시되도록 하는 단계입니다.
+표시는 `drawFrame` 함수 마지막에 `VkPresentInfoKHR` 구조체를 통해 설정됩니다.
 
 ```c++
 VkPresentInfoKHR presentInfo{};
@@ -487,11 +425,8 @@ presentInfo.waitSemaphoreCount = 1;
 presentInfo.pWaitSemaphores = signalSemaphores;
 ```
 
-The first two parameters specify which semaphores to wait on before presentation
-can happen, just like `VkSubmitInfo`. Since we want to wait on the command buffer
-to finish execution, thus our triangle being drawn, we take the semaphores
-which will be signalled and wait on them, thus we use `signalSemaphores`.
-
+첫 두 매개변수는 `VkSubmitInfo`처럼, 표시를 수행하기 전 어떤 세마포어를 기다릴지를 명시합니다.
+우리는 명령 버퍼 실행이 끝나서 삼각형이 그려질 때까지 대기해야 하므로 그 때 시그널 상태가 되는 `signalSemaphores`를 사용합니다.
 
 ```c++
 VkSwapchainKHR swapChains[] = {swapChain};
@@ -500,48 +435,41 @@ presentInfo.pSwapchains = swapChains;
 presentInfo.pImageIndices = &imageIndex;
 ```
 
-The next two parameters specify the swap chains to present images to and the
-index of the image for each swap chain. This will almost always be a single one.
+다음 두 매개변수는 이미지를 표시할 스왑 체인과 각 스왑 체인의 이미지 인덱스를 명시합니다.
+이는 거의 항상 한 개만 사용합니다.
 
 ```c++
 presentInfo.pResults = nullptr; // Optional
 ```
 
-There is one last optional parameter called `pResults`. It allows you to specify
-an array of `VkResult` values to check for every individual swap chain if
-presentation was successful. It's not necessary if you're only using a single
-swap chain, because you can simply use the return value of the present function.
+마지막으로 추가적인 매개변수로 `pResults`가 있습니다.
+여기에는 `VkResult`의 배열을 명시해서 각각의 스왑 체인에서 표시가 성공적으로 이루어졌는지를 체크합니다.
+하나의 스왑 체인만 사용하는 경우 그냥 표시 함수의 반환값으로 확인하면 되기 때문에 현재는 사용하지 않습니다.
 
 ```c++
 vkQueuePresentKHR(presentQueue, &presentInfo);
 ```
 
-The `vkQueuePresentKHR` function submits the request to present an image to the
-swap chain. We'll add error handling for both `vkAcquireNextImageKHR` and
-`vkQueuePresentKHR` in the next chapter, because their failure does not
-necessarily mean that the program should terminate, unlike the functions we've
-seen so far.
+`vkQueuePresentKHR` 함수는 이미지를 표시하라는 요청을 스왑 체인에 제출합니다.
+다음 챕터에서 `vkAcquireNextImageKHR`와 `vkQueuePresentKHR`에 대한 오류 처리를 추가할 것입니다.
+왜냐하면 지금까지와는 다르게 이 떄의 오류는 프로그램을 종료할 정도의 오류는 아닐 수 있기 때문입니다.
 
-If you did everything correctly up to this point, then you should now see
-something resembling the following when you run your program:
+여기까지 모든 단계를 제대로 수행했다면 이제 프로그램을 실행하면 아래와 비슷한 장면을 보시게 될겁니다:
 
 ![](/images/triangle.png)
 
->This colored triangle may look a bit different from the one you're used to seeing in graphics tutorials. That's because this tutorial lets the shader interpolate in linear color space and converts to sRGB color space afterwards. See [this blog post](https://medium.com/@heypete/hello-triangle-meet-swift-and-wide-color-6f9e246616d9) for a discussion of the difference.
+>이 삼각형은 여러분들이 그래픽스 관련한 튜토리얼에서 보던 삼각형과 다를 수 있습니다. 왜냐하면 이 튜토리얼에서는 셰이더가 선형 색상 공간(linear color space)에서 보간을 수행한 뒤 sRGB 색상 공간으로 변환을 수행하기 때문입니다. 이러한 차이점에 대해서는 [이 블로그](https://medium.com/@heypete/hello-triangle-meet-swift-and-wide-color-6f9e246616d9)를 참고하세요.
 
-Yay! Unfortunately, you'll see that when validation layers are enabled, the
-program crashes as soon as you close it. The messages printed to the terminal
-from `debugCallback` tell us why:
+짝짝짝! 하지만 안타깝게도 검증 레이어가 활성화 된 상태라면, 프로그램이 종료될 때 오류가 발생하는 것을 보실 수 있습니다.
+`debugCallback`에 의해 출력되는 메시지가 그 이유를 알려줍니다:
 
 ![](/images/semaphore_in_use.png)
 
-Remember that all of the operations in `drawFrame` are asynchronous. That means
-that when we exit the loop in `mainLoop`, drawing and presentation operations
-may still be going on. Cleaning up resources while that is happening is a bad
-idea.
+`drawFrame`의 모든 연산이 비동기적이라는 것을 기억하십시오.
+즉 `mainLoop`에서 루프를 종료했을 때도 그리기와 표시 연산이 계속 진행되고 있다는 뜻입니다.
+연산이 진행되는 도중에 리소스를 정리하는 것은 좋지 않습니다.
 
-To fix that problem, we should wait for the logical device to finish operations
-before exiting `mainLoop` and destroying the window:
+이 문제를 해결하기 위해 `mainLoop`를 끝내고 윈도우를 소멸하기 이전에 논리적 장치가 연산을 끝내기를 기다려야 합니다.
 
 ```c++
 void mainLoop() {
@@ -554,23 +482,18 @@ void mainLoop() {
 }
 ```
 
-You can also wait for operations in a specific command queue to be finished with
-`vkQueueWaitIdle`. These functions can be used as a very rudimentary way to
-perform synchronization. You'll see that the program now exits without problems
-when closing the window.
+`vkQueueWaitIdle`를 통해 특정 명령 큐의 연산이 끝나기를 기다리도록 할 수도 있습니다.
+이 함수는 동기화를 위한 아주 기초적인 방법으로 사용될 수도 있습니다.
+이제 윈도우를 닫아도 문제 없이 프로그램이 종료되는 것을 보실 수 있습니다.
 
-## Conclusion
+## 결론
 
-A little over 900 lines of code later, we've finally gotten to the stage of seeing
-something pop up on the screen! Bootstrapping a Vulkan program is definitely a
-lot of work, but the take-away message is that Vulkan gives you an immense
-amount of control through its explicitness. I recommend you to take some time
-now to reread the code and build a mental model of the purpose of all of the
-Vulkan objects in the program and how they relate to each other. We'll be
-building on top of that knowledge to extend the functionality of the program
-from this point on.
+900줄이 좀 넘는 코드로 드디어 화면에 뭔가를 표시할 수 있었습니다.
+Vulkan 프로그램을 부트스트래핑(bootstrapping)하는 것은 많은 노력이 필요하지만, 명시성으로 인해 우리에게 엄청난 양의 제어권을 제공한다는 것을 알 수 있었습니다.
+제가 권장하는 것은 이제 시간을 갖고 코드를 다시 읽어보면서 모든 Vulkan 객체들의 목적과 그들이 각각 어떻게 관련되어 있는지에 대한 개념을 복습해 보시라는 것입니다.
+이러한 지식을 가진 상태에서 이제 프로그램의 기능을 확장해 나가 볼 것입니다.
 
-The next chapter will expand the render loop to handle multiple frames in flight.
+다음 챕터에서는 렌더링 루프를 확장하여 여러 프레임을 사용하도록 할 것입니다.
 
 [C++ code](/code/15_hello_triangle.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From dc891b9e174dd38c0956f3277809ac703f2bd4e5 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Fri, 9 Feb 2024 11:51:38 +0900
Subject: [PATCH 27/47] kr translate 03-03-03 frames in flight

---
 .../03_Drawing/03_Frames_in_flight.md         | 78 ++++++++-----------
 1 file changed, 31 insertions(+), 47 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
index e2345e31..fb83a694 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
@@ -1,35 +1,29 @@
-## Frames in flight
+## 여러 프레임 사용하기(frames in-flight)
 
-Right now our render loop has one glaring flaw. We are required to wait on the
-previous frame to finish before we can start rendering the next which results
-in unnecessary idling of the host. 
+지금 우리 렌더링 루프에 눈에 띄는 문제가 하나 있습니다.
+다음 프레임을 렌더링 하기 전에 이전 프레임을 기다려야만 하고 이로 인해 호스트는 불필요한 대기(ideling) 시간을 갖게 됩니다.
 
 <!-- insert diagram showing our current render loop and the 'multi frame in flight' render loop -->
 
-The way to fix this is to allow multiple frames to be *in-flight* at once, that 
-is to say, allow the rendering of one frame to not interfere with the recording
-of the next. How do we do this? Any resource that is accessed and modified
-during rendering must be duplicated. Thus, we need multiple command buffers,
-semaphores, and fences. In later chapters we will also add multiple instances
-of other resources, so we will see this concept reappear.
+이를 수정하는 방법은 여러 프레임을 동시에 사용하는 것입니다.
+즉, 하나의 프레임에 렌더링을 수행하는 것과 다음 프레임의 기록 과정을 서로 간섭이 없도록 할 것입니다.
+어떻게 해야 할까요? 일단 렌더링에 필요한 모든 접근과 수정이 필요한 자원들이 모두 복제되어야만 합니다. 즉, 여러 개의 명령 버퍼, 세마포어와 펜스들이 있어야 합니다.
+나중 챕터에서는 다른 리소스들에 대한 다중 인스턴스를 추가할 것이고, 그 챕터에서 이러한 개념을 다시 보게될 것입니다.
 
-Start by adding a constant at the top of the program that defines how many
-frames should be processed concurrently:
+프로그램 상단에 얼마나 많은 프레임을 동시에 처리할 것인지 정의하는 상수를 먼저 추가합니다:
 
 ```c++
 const int MAX_FRAMES_IN_FLIGHT = 2;
 ```
 
-We choose the number 2 because we don't want the CPU to get *too* far ahead of
-the GPU. With 2 frames in flight, the CPU and the GPU can be working on their
-own tasks at the same time. If the CPU finishes early, it will wait till the
-GPU finishes rendering before submitting more work. With 3 or more frames in
-flight, the CPU could get ahead of the GPU, adding frames of latency.
-Generally, extra latency isn't desired. But giving the application control over
-the number of frames in flight is another example of Vulkan being explicit.
+우리는 CPU가 GPU보다 *너무* 앞서나가는 것을 원하지는 않으므로 2로 설정하였습니다.
+두 개의 프레임을 동시에 사용하면 CPU와 GPU는 각자의 작업을 동시에 수행할 수 있습니다.
+CPU가 먼저 끝나면, 작업을 더 제출할기 전에 GPU가 렌더링을 끝내길 기다릴 것입니다.
+세 개 이상의 프레임을 동시에 사용하면 CPU가 GPU보다 앞서나가 지연되는 프레임이 발생할 수 있습니다.
+일반적으로, 이러한 지연은 좋지 않습니다. 하지만 이렇게 사용되는 프레임의 개수를 조정하는 것 또한 Vulkan의 명시성의 한 예가 될 것입니다.
 
-Each frame should have its own command buffer, set of semaphores, and fence.
-Rename and then change them to be `std::vector`s of the objects:
+각 프레임은 각자의 명령 버퍼, 세마포어 집합과 펜스를 가져야 합니다.
+이들을 `std::vector`들로 바꾸고 이름도 변경합니다.
 
 ```c++
 std::vector<VkCommandBuffer> commandBuffers;
@@ -41,11 +35,9 @@ std::vector<VkSemaphore> renderFinishedSemaphores;
 std::vector<VkFence> inFlightFences;
 ```
 
-Then we need to create multiple command buffers. Rename `createCommandBuffer`
-to `createCommandBuffers`. Next we need to resize the command buffers vector
-to the size of `MAX_FRAMES_IN_FLIGHT`, alter the `VkCommandBufferAllocateInfo`
-to contain that many command buffers, and then change the destination to our
-vector of command buffers:
+이제 여러 개의 명령 버퍼를 생성해야 합니다.
+`createCommandBuffer`를 `createCommandBuffers`로 이름을 변경하고 명령 버퍼 벡터의 크기를 `MAX_FRAMES_IN_FLIGHT`로 수정합니다.
+`VkCommandBufferAllocateInfo`를 수정하여 명령 버퍼의 숫자를 받고록 하고 새로운 명령 버퍼 벡터의 위치를 넘겨줍니다:
 
 ```c++
 void createCommandBuffers() {
@@ -59,7 +51,7 @@ void createCommandBuffers() {
 }
 ```
 
-The `createSyncObjects` function should be changed to create all of the objects:
+`createSyncObjects` 함수는 모든 객체를 생성하도록 수정되어야 합니다:
 
 ```c++
 void createSyncObjects() {
@@ -85,7 +77,7 @@ void createSyncObjects() {
 }
 ```
 
-Similarly, they should also all be cleaned up:
+모두 정리되어야 하는 것도 마찬가지입니다:
 
 ```c++
 void cleanup() {
@@ -99,17 +91,16 @@ void cleanup() {
 }
 ```
 
-Remember, because command buffers are freed for us when we free the command
-pool, there is nothing extra to do for command buffer cleanup.
+명령 풀을 해제하면 명령 버퍼가 자동으로 해제되기 때문에 명령 버퍼 정리에는 별도의 작업이 필요 없다는 사실을 기억하세요.
+
+각 프레임에서 올바른 객체를 사용하기 위해서는 현재 프레임이 무엇인지를 알고 있어야 합니다. 이를 위해 프레임 인덱스를 사용할 것입니다:
 
-To use the right objects every frame, we need to keep track of the current
-frame. We will use a frame index for that purpose:
 
 ```c++
 uint32_t currentFrame = 0;
 ```
 
-The `drawFrame` function can now be modified to use the right objects:
+`drawFrame` 함수에서는 적절한 객체를 사용하도록 수정합니다:
 
 ```c++
 void drawFrame() {
@@ -141,7 +132,7 @@ void drawFrame() {
 }
 ```
 
-Of course, we shouldn't forget to advance to the next frame every time:
+당연히 다음 프레임에는 프레임을 증가시켜주어야 합니다:
 
 ```c++
 void drawFrame() {
@@ -151,25 +142,18 @@ void drawFrame() {
 }
 ```
 
-By using the modulo (%) operator, we ensure that the frame index loops around
-after every `MAX_FRAMES_IN_FLIGHT` enqueued frames.
+모듈로 연산(%)을 사용해 프레임 인덱스가 `MAX_FRAMES_IN_FLIGHT`개의 프레임 뒤에는 다시 0이 되도록 합니다.
 
 <!-- Possibly use swapchain-image-count for renderFinished semaphores, as it can't
 be known with a fence whether the semaphore is ready for re-use. -->
 
-We've now implemented all the needed synchronization to ensure that there are
-no more than `MAX_FRAMES_IN_FLIGHT` frames of work enqueued and that these
-frames are not stepping over eachother. Note that it is fine for other parts of
-the code, like the final cleanup, to rely on more rough synchronization like
-`vkDeviceWaitIdle`. You should decide on which approach to use based on
-performance requirements.
-
-To learn more about synchronization through examples, have a look at [this extensive overview](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present) by Khronos.
-
+이제 동기화화 관련한 모든 구현을 마쳐서 `MAX_FRAMES_IN_FLIGHT`개 이상의 프레임 작업이 동시에 큐에 들어가지 않도록 하였으며 이러한 프레임들이 서로 겹치지도 않게 되었습니다.
+정리 부분과 같은 나머지 부분은 `vkDeviceWaitIdle` 처럼 보다 기초적인 동기화 방법에 의존하고 있습니다.
+어떠한 접근법을 사용할지는 성능 요구사항에 따라서 여러분이 선택하셔야 합니다.
 
-In the next chapter we'll deal with one more small thing that is required for a
-well-behaved Vulkan program.
+동기화에 대해 예를 통해 더 알고 싶으시면 Khronos에서 제공하는 [이 개요 문서](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present)를 살펴보세요.
 
+다음 챕터에서는 잘 동작하는 Vulkan 프로그램에 필요한 또다른 요구사항을 살펴보겠습니다. 
 
 [C++ code](/code/16_frames_in_flight.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From b0b49f26bfd7ae42066cd8475bc0549f9a786300 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Fri, 9 Feb 2024 17:15:08 +0900
Subject: [PATCH 28/47] kr translate 03-03-04 swap chain recreation

---
 .../04_Swap_chain_recreation.md               | 147 ++++++++----------
 1 file changed, 68 insertions(+), 79 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
index 5da07458..619f1c9a 100644
--- a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
+++ b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
@@ -1,16 +1,13 @@
-## Introduction
+## 개요
 
-The application we have now successfully draws a triangle, but there are some
-circumstances that it isn't handling properly yet. It is possible for the window
-surface to change such that the swap chain is no longer compatible with it. One
-of the reasons that could cause this to happen is the size of the window
-changing. We have to catch these events and recreate the swap chain.
+지금까지 만든 프로그램으로 성공적으로 삼각형을 그렸지만 아직 잘 처리하지 못하는 상황이 있습니다.
+윈도우 표면이 변경되어 스왑 체인이 더이상 호환되지 않을 때 입니다.
+이러한 상황이 발생하는 이유 중 하나로 윈도우의 크기가 변하는 경우가 있습니다.
+이러한 이벤트를 탐지하여 스왑 체인을 새로 만들어야만 합니다.
 
-## Recreating the swap chain
+## 스왑 체인 재생성
 
-Create a new `recreateSwapChain` function that calls `createSwapChain` and all
-of the creation functions for the objects that depend on the swap chain or the
-window size.
+`recreateSwapChain` 함수를 새로 만드는데 이 함수는 `createSwapChain` 함수 및 스왑 체인, 그리고 윈도우 크기와 관련한 모든 객체를 만드는 함수를 호출하도록 할 것입니다. 
 
 ```c++
 void recreateSwapChain() {
@@ -22,16 +19,13 @@ void recreateSwapChain() {
 }
 ```
 
-We first call `vkDeviceWaitIdle`, because just like in the last chapter, we
-shouldn't touch resources that may still be in use. Obviously, we'll have to recreate 
-the swap chain itself. The image views need to be recreated because they are based 
-directly on the swap chain images. Finally, the framebuffers directly depend on the 
-swap chain images, and thus must be recreated as well.
+먼저 `vkDeviceWaitIdle`를 호출하는데 이전 장에서처럼 이미 사용 중인 자원을 건드리면 안되기 때문입니다.
+그리고 당연히 스왑 체인은 새로 만들어야 하고요.
+이미지 뷰는 스왑 체인의 이미지와 직접적으로 관련되어 있기 때문에 다시 만들어야 합니다.
+하지막으로 프레임버퍼도 스왑 체인 이미지와 직접적으로 관련되어 있으니 역시나 마찬가지로 다시 만들어 주어야 합니다.
 
-To make sure that the old versions of these objects are cleaned up before
-recreating them, we should move some of the cleanup code to a separate function
-that we can call from the `recreateSwapChain` function. Let's call it
-`cleanupSwapChain`:
+이러한 객체들의 이전 버전은 모두 재생성 되기 전에 정리되어야 하는데, 이를 확실히 하기 위해 정리 코드의 몇 부분을 변도의 함수로 만들어 `recreateSwapChain` 함수에서 호출 가능하도록 할 것입니다.
+이 함수는 `cleanupSwapChain`로 명명합시다:
 
 ```c++
 void cleanupSwapChain() {
@@ -49,10 +43,12 @@ void recreateSwapChain() {
 }
 ```
 
-Note that we don't recreate the renderpass here for simplicity. In theory it can be possible for the swap chain image format to change during an applications' lifetime, e.g. when moving a window from an standard range to an high dynamic range monitor. This may require the application to recreate the renderpass to make sure the change between dynamic ranges is properly reflected.
+여기서는 간략화 해서 렌더패스는 재생성하지 않았습니다.
+이론적으로는 응용 프로그램의 실행 동안 스왑 체인 이미지의 포맷도 바뀔 수 있습니다.
+예를 들어 윈도우를 일반적인 모니터에서 HDR 모니터로 이동한다거나 하는 등을 생각해 볼 수 있습니다.
+이러한 경우 응용 프로램에서 HDR로의 변경이 적절히 적용되도록 렌더패스 재생성도 필요할 수 있습니다.
 
-We'll move the cleanup code of all objects that are recreated as part of a swap
-chain refresh from `cleanup` to `cleanupSwapChain`:
+새로 만들어진 객체들의 정리 코드는 `cleanup`에서 `cleanupSwapChain`로 옮깁니다:
 
 ```c++
 void cleanupSwapChain() {
@@ -98,30 +94,22 @@ void cleanup() {
 }
 ```
 
-Note that in `chooseSwapExtent` we already query the new window resolution to
-make sure that the swap chain images have the (new) right size, so there's no
-need to modify `chooseSwapExtent` (remember that we already had to use
-`glfwGetFramebufferSize` to get the resolution of the surface in pixels when
-creating the swap chain).
+`chooseSwapExtent`에서 이미 새로운 윈도우의 해상도를 질의해서 스왑 페인 이미지가 (새로운) 윈도우에 적합한 크기가 되도록 했다는 것에 주목하십시오.
+따라서 `chooseSwapExtent`를 수정할 필요는 없습니다(`glfwGetFramebufferSize`를 사용해서 스왑 체인 생성 시점에 픽셀 단위의 표면 해상도를 얻어왔다는 것을 기억하세요).
 
-That's all it takes to recreate the swap chain! However, the disadvantage of
-this approach is that we need to stop all rendering before creating the new swap
-chain. It is possible to create a new swap chain while drawing commands on an
-image from the old swap chain are still in-flight. You need to pass the previous
-swap chain to the `oldSwapChain` field in the `VkSwapchainCreateInfoKHR` struct
-and destroy the old swap chain as soon as you've finished using it.
+이로써 스왑 체인을 재생성하는 부분은 끝입니다!
+하지만 이러한 접근법의 단점은 새로운 스왑 체인이 생성될 때까지 모든 렌더링이 중단된다는 것입니다.
+이전 스왑 체인이 사용되는 동안에 그리기가 수행되는 동안에 대 스왑 체인을 만드는 것도 가능합니다.
+그러려면 `VkSwapchainCreateInfoKHR` 구조체의 `oldSwapChain` 필드에 이전 스왑 체인을 전달하고 사용이 끝난 뒤 소멸시키면 됩니다.
 
-## Suboptimal or out-of-date swap chain
+## 최적화되지 않았거나 부적합한 스왑 체인
 
-Now we just need to figure out when swap chain recreation is necessary and call
-our new `recreateSwapChain` function. Luckily, Vulkan will usually just tell us that the swap chain is no longer adequate during presentation. The `vkAcquireNextImageKHR` and
-`vkQueuePresentKHR` functions can return the following special values to
-indicate this.
+이제 언제 스왑 체인 재생성이 필요한지 알아내서 `recreateSwapChain` 함수를 호출하면 됩니다.
+다행히 Vulkan은 대개 표시 단계에서 현재 스왑 체인이 적합하지 않게 된 시점에 이러한 것을 알려 줍니다.
+`vkAcquireNextImageKHR`와 `vkQueuePresentKHR` 함수는 아래와 같은 특정한 값으로 이러한 상황을 알려줍니다.
 
-* `VK_ERROR_OUT_OF_DATE_KHR`: The swap chain has become incompatible with the
-surface and can no longer be used for rendering. Usually happens after a window resize.
-* `VK_SUBOPTIMAL_KHR`: The swap chain can still be used to successfully present
-to the surface, but the surface properties are no longer matched exactly.
+* `VK_ERROR_OUT_OF_DATE_KHR`: 스왑 체인이 표면과 호환이 불가능하여 렌더링이 불가능하게 되었음. 일반적으로 윈도우의 크기가 변했을 때 발생
+* `VK_SUBOPTIMAL_KHR`: 스왑 체인이 표면을 표현하는 데 여전히 사용 가능하지만 표면 속성이 정확히 일치하지는 않음
 
 ```c++
 VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
@@ -134,13 +122,11 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR) {
 }
 ```
 
-If the swap chain turns out to be out of date when attempting to acquire an
-image, then it is no longer possible to present to it. Therefore we should
-immediately recreate the swap chain and try again in the next `drawFrame` call.
+이미지를 획득하려 할 때 스왑체인이 부적합하다고 판단되면 그 이미지는 표현에 활용할 수 없습니다.
+따라서 즉시 스왑 체인을 재생성하고 다음 `drawFrame`을 다시 호출해야 합니다.
 
-You could also decide to do that if the swap chain is suboptimal, but I've
-chosen to proceed anyway in that case because we've already acquired an image.
-Both `VK_SUCCESS` and `VK_SUBOPTIMAL_KHR` are considered "success" return codes.
+스왑 체인이 최적화되지 않은 경우에도 이렇게 하도록 할 수도 있지만 저는 이 경우에는 어쨌든 이미지를 이미 획득했기 때문에 그냥 진행하기로 했습니다.
+`VK_SUCCESS`와 `VK_SUBOPTIMAL_KHR`는 모두 "성공" 반환 코드로 취급합니다.
 
 ```c++
 result = vkQueuePresentKHR(presentQueue, &presentInfo);
@@ -154,26 +140,23 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
 currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
 ```
 
-The `vkQueuePresentKHR` function returns the same values with the same meaning.
-In this case we will also recreate the swap chain if it is suboptimal, because
-we want the best possible result.
+`vkQueuePresentKHR` 함수는 위와 같은 의미를 가진 같은 값들을 반환합니다.
+이 경우에는 최적화되지 않은 경우에도 스왑 체인을 재생성하는데 가능한 좋은 결과를 얻고 싶기 때문입니다.
 
-## Fixing a deadlock
+## 데드락(deadlock) 해소
 
-If we try to run the code now, it is possible to encounter a deadlock.
-Debugging the code, we find that the application reaches `vkWaitForFences` but
-never continues past it. This is because when `vkAcquireNextImageKHR` returns
-`VK_ERROR_OUT_OF_DATE_KHR`, we recreate the swapchain and then return from
-`drawFrame`. But before that happens, the current frame's fence was waited upon
-and reset. Since we return immediately, no work is submitted for execution and
-the fence will never be signaled, causing `vkWaitForFences` to halt forever.
+지금 시점에서 코드를 실행하면 데드락이 발생할 수 있습니다.
+코드를 디버깅해보면 `vkWaitForFences`에는 도달하지만 여기에서 더 이상 진행하지 못하는 것을 볼 수 있습니다.
+이는 `vkAcquireNextImageKHR`이 `VK_ERROR_OUT_OF_DATE_KHR`을 반환하면 스왑체인을 재생성하고 `drawFrame`로 돌아가게 했기 때문입니다.
+하지만 그러한 처리는 현재 프레임의 펜스가 기다리는 상태에서 일어날 수 있습니다.
+바로 반환되는 바람에 아무런 작업도 제출되지 않았고 펜스는 시그널 상태가 될 수 없어서 `vkWaitForFences`에서 멈춘 상태가 됩니다.
 
-There is a simple fix thankfully. Delay resetting the fence until after we
-know for sure we will be submitting work with it. Thus, if we return early, the
-fence is still signaled and `vkWaitForFences` wont deadlock the next time we
-use the same fence object.
+다행히 손쉬운 해결법이 있습니다.
+작업을 다시 제출할 것이 확실한 시점까지 펜스를 리셋하는 것을 미루는 것입니다.
+이렇게 되면 빠른 반환이 일어났을 때 펜스는 여전히 시그널 상태이고 `vkWaitForFences`는 다음 프레임에서 데드락이 발생하지 않을 것입니다.
 
-The beginning of `drawFrame` should now look like this:
+`drawFrame`의 시작 부분으 다음과 같이 되어야 합니다
+:
 ```c++
 vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
 
@@ -187,13 +170,15 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR) {
     throw std::runtime_error("failed to acquire swap chain image!");
 }
 
-// Only reset the fence if we are submitting work
+// 작업을 제출하는 경우에만 펜스를 리셋
 vkResetFences(device, 1, &inFlightFences[currentFrame]);
 ```
 
-## Handling resizes explicitly
+## 크기 변환을 명시적으로 처리하기
 
-Although many drivers and platforms trigger `VK_ERROR_OUT_OF_DATE_KHR` automatically after a window resize, it is not guaranteed to happen. That's why we'll add some extra code to also handle resizes explicitly. First add a new member variable that flags that a resize has happened:
+윈도우 크기 변환에 대해 많은 드라이버와 플랫폼이 `VK_ERROR_OUT_OF_DATE_KHR`를 자동으로 반환해주지만, 이러한 동작이 보장된 것은 아닙니다.
+따라서 추가적인 코드를 통해 크기 변환을 명시적으로 처리해 주도록 하겠습니다.
+먼저 크기 변환이 일어났을 때를 위한 플래그를 멤버 변수로 추가합니다:
 
 ```c++
 std::vector<VkFence> inFlightFences;
@@ -201,7 +186,7 @@ std::vector<VkFence> inFlightFences;
 bool framebufferResized = false;
 ```
 
-The `drawFrame` function should then be modified to also check for this flag:
+`drawFrame`함수에서도 이 플래그를 체크하도록 수정합니다:
 
 ```c++
 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
@@ -212,7 +197,9 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebu
 }
 ```
 
-It is important to do this after `vkQueuePresentKHR` to ensure that the semaphores are in a consistent state, otherwise a signaled semaphore may never be properly waited upon. Now to actually detect resizes we can use the `glfwSetFramebufferSizeCallback` function in the GLFW framework to set up a callback:
+이러한 작업을 `vkQueuePresentKHR` 뒤에 진행해서 세마포어가 적합상 상태에 있도록 하는 것이 중요합니다.
+그렇지 않으면 시그널 상태인 세마포어가 제대로 대기를 하지 못할 수 있습니다.
+이제 실제 크기 변경을 탐지하기 위해 GLFW 프레임워크의 `glfwSetFramebufferSizeCallback` 함수를 사용하여 콜백을 설정합니다:
 
 ```c++
 void initWindow() {
@@ -229,9 +216,9 @@ static void framebufferResizeCallback(GLFWwindow* window, int width, int height)
 }
 ```
 
-The reason that we're creating a `static` function as a callback is because GLFW does not know how to properly call a member function with the right `this` pointer to our `HelloTriangleApplication` instance.
+콜백을 `static` 함수로 만드는 이유는 GLFW가 `HelloTriangleApplication` 인스턴스를 가리키는 `this` 포인터로부터 올바른 멤버 함수를 호출하는 법을 알 수 없기 때문입니다.
 
-However, we do get a reference to the `GLFWwindow` in the callback and there is another GLFW function that allows you to store an arbitrary pointer inside of it: `glfwSetWindowUserPointer`:
+하지만 콜백 내에서 `GLFWwindow`에 대한 참조에 접근할 수 있고, 임의의 포인터를 그 안에 저장할 수 있는 `glfwSetWindowUserPointer`라는 GLFW 함수가 있습니다:
 
 ```c++
 window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
@@ -239,7 +226,7 @@ glfwSetWindowUserPointer(window, this);
 glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
 ```
 
-This value can now be retrieved from within the callback with `glfwGetWindowUserPointer` to properly set the flag:
+이 값은 이제 콜백 내에서 `glfwGetWindowUserPointer`를 사용해 적절히 변환된 뒤 올바른 플래그 설정을 위해 사용됩니다:
 
 ```c++
 static void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
@@ -248,11 +235,14 @@ static void framebufferResizeCallback(GLFWwindow* window, int width, int height)
 }
 ```
 
-Now try to run the program and resize the window to see if the framebuffer is indeed resized properly with the window.
+이제 프로그램을 실행하고 윈도우 크기를 조정하여 프레임버퍼가 윈도우 크기에 맞게 조정되는지 살펴 보세요.
 
-## Handling minimization
+## 최소화 처리하기
 
-There is another case where a swap chain may become out of date and that is a special kind of window resizing: window minimization. This case is special because it will result in a frame buffer size of `0`. In this tutorial we will handle that by pausing until the window is in the foreground again by extending the `recreateSwapChain` function:
+스왑 체인이 부적합하게 되는 다른 또다른 경우로 특수한 윈도우 크기 변경 사례가 있습니다. 바로 윈도우 최소화 입니다.
+이 경우가 특수한 이유는 프레임버퍼 크기가 `0`이 되기 떄문입니다.
+이 튜토리얼에서는 이러한 경우에 대해 윈도우가 다시 활성화가 될때까지 정지하는 방식으로 처리할 것입니다.
+`recreateSwapChain` 함수를 사용합니다:
 
 ```c++
 void recreateSwapChain() {
@@ -269,11 +259,10 @@ void recreateSwapChain() {
 }
 ```
 
-The initial call to `glfwGetFramebufferSize` handles the case where the size is already correct and `glfwWaitEvents` would have nothing to wait on.
+처음의 `glfwGetFramebufferSize` 호출은 올바른 크기일 경우에 대한 것으로 이 경우 `glfwWaitEvents`는 기다릴 것이 없습니다.
 
-Congratulations, you've now finished your very first well-behaved Vulkan
-program! In the next chapter we're going to get rid of the hardcoded vertices in
-the vertex shader and actually use a vertex buffer.
+축하합니다! 이제 올바로 동작하는 것 Vulkan 프로그램을 완성했습니다!
+다음 챕터에서는 정점 셰이더에 하드코딩된 정점을 제거하고 정점 버퍼(vertex buffer)를 사용해 볼 것입니다.
 
 [C++ code](/code/17_swap_chain_recreation.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /

From 36cc8e02d73f070543a45720afc14a9aa42f2a58 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Fri, 9 Feb 2024 18:37:46 +0900
Subject: [PATCH 29/47] kr translate 04-00 vertex input desciption

---
 .../00_Vertex_input_description.md            | 161 ++++++++----------
 1 file changed, 67 insertions(+), 94 deletions(-)

diff --git a/kr/04_Vertex_buffers/00_Vertex_input_description.md b/kr/04_Vertex_buffers/00_Vertex_input_description.md
index e7da3e4f..a235a9de 100644
--- a/kr/04_Vertex_buffers/00_Vertex_input_description.md
+++ b/kr/04_Vertex_buffers/00_Vertex_input_description.md
@@ -1,16 +1,12 @@
-## Introduction
+## 개요
 
-In the next few chapters, we're going to replace the hardcoded vertex data in
-the vertex shader with a vertex buffer in memory. We'll start with the easiest
-approach of creating a CPU visible buffer and using `memcpy` to copy the vertex
-data into it directly, and after that we'll see how to use a staging buffer to
-copy the vertex data to high performance memory.
+다음 몇 챕터동안 정점 셰이더에 하드코딩된 정점 데이터를 메모리의 정점 버퍼(vertex buffer)로 바꾸어 보겠습니다.
+먼저 가장 손쉬운 방법인 CPU에서 보이는(visible) 버퍼를 만든 뒤 `memcpy`를 통해 정점 데이터를 직접 복사하는 방법을 알아볼 것이고, 이후에 스테이징 버퍼(staging buffer)를 사용해 정점 데이터를 고성능 메모리에 복사하는 방법을 알아볼 것입니다.
 
-## Vertex shader
+## 정점 셰이더
 
-First change the vertex shader to no longer include the vertex data in the
-shader code itself. The vertex shader takes input from a vertex buffer using the
-`in` keyword.
+먼저 정점 셰이더가 정점 데이터를 코드로 포함하지 않도록 수정할 것입니다.
+정점 셰이더는 `in` 키워드로 정점 버퍼에서 입력을 받을 것입니다.
 
 ```glsl
 #version 450
@@ -26,36 +22,32 @@ void main() {
 }
 ```
 
-The `inPosition` and `inColor` variables are *vertex attributes*. They're
-properties that are specified per-vertex in the vertex buffer, just like we
-manually specified a position and color per vertex using the two arrays. Make
-sure to recompile the vertex shader!
+`inPosition`와 `inColor` 변수는 *정점 어트리뷰트(vertex attribute)*입니다.
+이는 정점 버퍼에 명시된 정점별 속성이며, 기존처럼 위치와 속성 데이터 입니다.
+정점 셰이더를 수정한 뒤 다시 컴파일하는 것을 잊지 마세요!
 
-Just like `fragColor`, the `layout(location = x)` annotations assign indices to
-the inputs that we can later use to reference them. It is important to know that
-some types, like `dvec3` 64 bit vectors, use multiple *slots*. That means that
-the index after it must be at least 2 higher:
+`fragColor`처럼, `layout(location = x)`는 입력에 대해 나중에 참조하기 위한 인덱스를 할당하는 것입니다.
+예를들어 `dvec3`와 같은 64비트 벡터는 여러 *슬롯(slot)*을 사용한다는 사실을 중요하게 알아두셔야 합니다.
+이러한 경우 그 다음으로 오는 인덱스는 2 이상 큰 인덱스여야 합니다:
 
 ```glsl
 layout(location = 0) in dvec3 inPosition;
 layout(location = 2) in vec3 inColor;
 ```
 
-You can find more info about the layout qualifier in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)).
+레이아웃 한정자(qualifier)에 대해서는 [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL))에서 자세한 정보를 찾아 볼 수 있습니다.
 
-## Vertex data
+## 정점 데이터
 
-We're moving the vertex data from the shader code to an array in the code of our
-program. Start by including the GLM library, which provides us with linear
-algebra related types like vectors and matrices. We're going to use these types
-to specify the position and color vectors.
+정점 데이터를 셰이더 코드에서 우리 프로그램의 배열로 옮길 예정입니다.
+먼저 벡터와 행렬 같은 선형대수 관련 자료형을 제공해 주는 GLM 라이브러를 include 하는 것 부터 시작합니다.
+이 자료형들을 사용해 위치와 색상 벡터를 명시할 것입니다.
 
 ```c++
 #include <glm/glm.hpp>
 ```
 
-Create a new structure called `Vertex` with the two attributes that we're going
-to use in the vertex shader inside it:
+우리가 정점 셰이더에서 사용할 두 어트리뷰트를 포함하는 `Vertex` 구조체를 만듭니다:
 
 ```c++
 struct Vertex {
@@ -64,8 +56,7 @@ struct Vertex {
 };
 ```
 
-GLM conveniently provides us with C++ types that exactly match the vector types
-used in the shader language.
+GLM은 셰이더 언어에서 사용되는 벡터 자료형과 정확히 매치되는 C++ 자료형을 제공해 줍니다:
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -75,18 +66,16 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-Now use the `Vertex` structure to specify an array of vertex data. We're using
-exactly the same position and color values as before, but now they're combined
-into one array of vertices. This is known as *interleaving* vertex attributes.
+이제 `Vertex` 구조체를 사용해 정점 데이터를 명시합니다.
+이전과 완전히 동일한 위치와 색상값을 사용하지만 이제는 정점에 대한 배열 하나에 모두 포함해 두었습니다.
+이러한 방식을 정점 어트리뷰트의 *interleving*이라고 합니다.
 
-## Binding descriptions
+## 바인딩 명세(Binding descriptions)
 
-The next step is to tell Vulkan how to pass this data format to the vertex
-shader once it's been uploaded into GPU memory. There are two types of
-structures needed to convey this information.
+다음 단계는 GPU 메모리에 업로드된 데이터를 정점 셰이더로 어떻게 전달할지를 Vulkan에 알려주는 것입니다.
+이러한 정보를 전달하기 위한 두 종류의 구조체가 필요합니다.
 
-The first structure is `VkVertexInputBindingDescription` and we'll add a member
-function to the `Vertex` struct to populate it with the right data.
+첫 구조체는 `VkVertexInputBindingDescription`이고 `Vertex` 구조체에 멤버 함수를 추가하여 적절한 데이터를 생성할 수 있도록 합니다.
 
 ```c++
 struct Vertex {
@@ -101,9 +90,8 @@ struct Vertex {
 };
 ```
 
-A vertex binding describes at which rate to load data from memory throughout the
-vertices. It specifies the number of bytes between data entries and whether to
-move to the next data entry after each vertex or after each instance.
+정점 바인딩은 정점에 대해 얼만큼의 데이터를 메모리로부터 로드할 것인지를 명시합니다.
+각 데이터별 바이트의 크기, 그리고 각 정점에 대해 다음 데이터로 넘어갈지, 아니면 다음 인스턴스에서 널어갈지를 포함합니다.
 
 ```c++
 VkVertexInputBindingDescription bindingDescription{};
@@ -112,23 +100,20 @@ bindingDescription.stride = sizeof(Vertex);
 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
 ```
 
-All of our per-vertex data is packed together in one array, so we're only going
-to have one binding. The `binding` parameter specifies the index of the binding
-in the array of bindings. The `stride` parameter specifies the number of bytes
-from one entry to the next, and the `inputRate` parameter can have one of the
-following values:
+우리의 정점별 데이터는 하나의 배열에 포장되어(packed) 있으니 바인딩은 하나만 있으면 됩니다.
+`binding` 매개변수는 바인딩 배열의 바인딩할 인덱스를 명시합니다.
+`stride` 매개변수는 한 요소와 다음 요소 사이의 바이트 크기입니다.
+`inputRate` 매개변수는 아래와 같은 값 중 하나를 가집니다:
 
-* `VK_VERTEX_INPUT_RATE_VERTEX`: Move to the next data entry after each vertex
-* `VK_VERTEX_INPUT_RATE_INSTANCE`: Move to the next data entry after each
-instance
+* `VK_VERTEX_INPUT_RATE_VERTEX`: 각 정점에 대해 다음 데이터 요소로 이동함
+* `VK_VERTEX_INPUT_RATE_INSTANCE`: 각 인스턴스에 대해 다음 데이터 요소로 넘어감
 
-We're not going to use instanced rendering, so we'll stick to per-vertex data.
+인스턴스 렌더링을 한 것은 아니므로 정점별 데이터로 해 두겠습니다.
 
-## Attribute descriptions
+## 어트리뷰트 명세
 
-The second structure that describes how to handle vertex input is
-`VkVertexInputAttributeDescription`. We're going to add another helper function
-to `Vertex` to fill in these structs.
+정점 입력을 처리하는 방법을 설명하기 위한 두 번째 구조체는 `VkVertexInputAttributeDescription`입니다. 
+이 구조체를 채우기 위해 또 다른 헬퍼 함수를 `Vertex`에 추가하겠습니다.
 
 ```c++
 #include <array>
@@ -142,11 +127,9 @@ static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions
 }
 ```
 
-As the function prototype indicates, there are going to be two of these
-structures. An attribute description struct describes how to extract a vertex
-attribute from a chunk of vertex data originating from a binding description. We
-have two attributes, position and color, so we need two attribute description
-structs.
+함수 프로토타입에서 알 수 있듯, 두 개의 구조체를 사용할 것입니다.
+어트리뷰트 명세를 위한 구조체는 바인딩 명세를 활용해 얻어진 정점 데이터 덩어리로부터 정점 어트리뷰트를 어떻게 추출할지를 알려줍니다.
+우리는 위치와 색상 두 개의 어트리뷰트가 있으니 두 개의 어트리뷰트 명세 구조체가 필요합니다.
 
 ```c++
 attributeDescriptions[0].binding = 0;
@@ -155,39 +138,32 @@ attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
 attributeDescriptions[0].offset = offsetof(Vertex, pos);
 ```
 
-The `binding` parameter tells Vulkan from which binding the per-vertex data
-comes. The `location` parameter references the `location` directive of the
-input in the vertex shader. The input in the vertex shader with location `0` is
-the position, which has two 32-bit float components.
+`binding` 매개변수는 어떤 바인딩에서 정점별 데이터를 얻어올 것인지를 Vulkan에 알려줍니다.
+`location` 매개변수는 정점 셰이더의 `location` 지시자에 대한 참조입니다.
+정점 셰이더의 location `0`에 대한 입력이 위치값에 해당하고, 이는 두 개의 32비트 부동소수점으로 이루어져 있습니다.
 
-The `format` parameter describes the type of data for the attribute. A bit
-confusingly, the formats are specified using the same enumeration as color
-formats. The following shader types and formats are commonly used together:
+`format` 매개변수는 어트리뷰트의 데이터 자료형을 알려줍니다.
+약간 헷갈리는 점은 이러한 포맷이 색상 포맷과 동일한 열거자로 명시된다는 점입니다.
+아래와 같은 셰이더 자료형에 따르는 포맷이 사용됩니다:
 
 * `float`: `VK_FORMAT_R32_SFLOAT`
 * `vec2`: `VK_FORMAT_R32G32_SFLOAT`
 * `vec3`: `VK_FORMAT_R32G32B32_SFLOAT`
 * `vec4`: `VK_FORMAT_R32G32B32A32_SFLOAT`
 
-As you can see, you should use the format where the amount of color channels
-matches the number of components in the shader data type. It is allowed to use
-more channels than the number of components in the shader, but they will be
-silently discarded. If the number of channels is lower than the number of
-components, then the BGA components will use default values of `(0, 0, 1)`. The
-color type (`SFLOAT`, `UINT`, `SINT`) and bit width should also match the type
-of the shader input. See the following examples:
-
-* `ivec2`: `VK_FORMAT_R32G32_SINT`, a 2-component vector of 32-bit signed
-integers
-* `uvec4`: `VK_FORMAT_R32G32B32A32_UINT`, a 4-component vector of 32-bit
-unsigned integers
-* `double`: `VK_FORMAT_R64_SFLOAT`, a double-precision (64-bit) float
-
-The `format` parameter implicitly defines the byte size of attribute data and
-the `offset` parameter specifies the number of bytes since the start of the
-per-vertex data to read from. The binding is loading one `Vertex` at a time and
-the position attribute (`pos`) is at an offset of `0` bytes from the beginning
-of this struct. This is automatically calculated using the `offsetof` macro.
+보다시피 색상 채널의 수와 일치하는 요소 숫자를 갖는 셰이더 자료형의 포맷을 사용해야 합니다.
+셰이더의 요소 숫자보다 더 많은 채널을 사용하는 것도 허용되지만 남는 값은 무시됩니다.
+요소 숫자보다 채널 수가 적으면 BGA 요소의 기본값인 `(0, 0, 1)`가 사용됩니다. 
+색상 타입인 (`SFLOAT`, `UINT`, `SINT`)와 비트 너비 또한 셰이더 입력의 자료형과 일치해야 합니다.
+예시는 다음과 같습니다:
+
+* `ivec2`: `VK_FORMAT_R32G32_SINT`, 32비트 부호 있는 정수 2개 요소를 갖는 벡터
+* `uvec4`: `VK_FORMAT_R32G32B32A32_UINT`, 32비트 부호 없는 정수 4개의 요소를 갖는 벡터
+* `double`: `VK_FORMAT_R64_SFLOAT`, 64비트 double 부동소수점
+
+`format` 매개변수는 어트리뷰트 데이터의 바이트 크기를 암시적으로 정의하며 `offset` 매개변수는 정점별 데이터를 읽어올 시작 바이트를 명시합니다.
+바인딩은 한 번에 하나의 `Vertex`를 읽어오며 위치 어트리뷰트(`pos`)는 `0` 바이트, 즉 처음부터 읽어옵니다. 
+`offsetof` 매크로를 사용하면 자동으로 계산됩니다.
 
 ```c++
 attributeDescriptions[1].binding = 0;
@@ -196,13 +172,12 @@ attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
 attributeDescriptions[1].offset = offsetof(Vertex, color);
 ```
 
-The color attribute is described in much the same way.
+색상 어트리뷰트도 동일한 방식으로 기술됩니다.
 
-## Pipeline vertex input
+## 파이프라인 정점 입력
 
-We now need to set up the graphics pipeline to accept vertex data in this format
-by referencing the structures in `createGraphicsPipeline`. Find the
-`vertexInputInfo` struct and modify it to reference the two descriptions:
+이제 `createGraphicsPipeline` 안의 구조체를 참조하여 정점 데이터를 위와 같은 포맷으로 받아들이도록 그래픽스 파이프라인을 설정해야 합니다.
+`vertexInputInfo` 구조체를 찾아 두 명세를 참조하도록 수정합니다:
 
 ```c++
 auto bindingDescription = Vertex::getBindingDescription();
@@ -214,11 +189,9 @@ vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
 ```
 
-The pipeline is now ready to accept vertex data in the format of the `vertices`
-container and pass it on to our vertex shader. If you run the program now with
-validation layers enabled, you'll see that it complains that there is no vertex
-buffer bound to the binding. The next step is to create a vertex buffer and move
-the vertex data to it so the GPU is able to access it.
+이제 이 파이프라인은 `vertices` 컨테이너의 정점 데이터를 받아들이고 정점 셰이더로 넘길 준비가 되었습니다.
+검증 레이어를 활성화 한 상태에서 프로그램을 실행하면 바인딩된 정점 버퍼가 없다는 오류 메시지를 보시게 될겁니다.
+다음 단계는 정점 버퍼를 만들고 정점 데이터를 버퍼에 넘겨 GPU가 접근할 수 있도록 하는 것입니다.
 
 [C++ code](/code/18_vertex_input.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /

From 896d4063312b99f682d530ea139b48ead74d338d Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 12 Feb 2024 11:20:17 +0900
Subject: [PATCH 30/47] kr translate 04-01 vertex buffer creation

---
 .../01_Vertex_buffer_creation.md              | 216 +++++++-----------
 1 file changed, 85 insertions(+), 131 deletions(-)

diff --git a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
index 77122c50..62071837 100644
--- a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
+++ b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
@@ -1,17 +1,13 @@
-## Introduction
+## 개요
 
-Buffers in Vulkan are regions of memory used for storing arbitrary data that can
-be read by the graphics card. They can be used to store vertex data, which we'll
-do in this chapter, but they can also be used for many other purposes that we'll
-explore in future chapters. Unlike the Vulkan objects we've been dealing with so
-far, buffers do not automatically allocate memory for themselves. The work from
-the previous chapters has shown that the Vulkan API puts the programmer in
-control of almost everything and memory management is one of those things.
+Vulkan에서 버퍼는 그래픽 카드가 읽을 수 있는, 임의의 데이터를 저장하는 메모리 영역을 의미합니다.
+이 챕터에서의 예시처럼 정점 데이터를 저장하는 데 사용될 수도 있지만 나중 챕터에서 살펴볼 것인데 다른 용도로도 자주 사용됩니다.
+지금까지 살펴본 Vulkan 객체와는 다르게 버퍼는 스스로 메모리를 할당하지 않습니다. 
+지금까지 살펴본 것처럼 Vulkan API는 프로그래머에게 거의 모든 제어권을 주는데, 메모리 관리 또한 이에 포함됩니다.
 
-## Buffer creation
+## 버퍼 생성
 
-Create a new function `createVertexBuffer` and call it from `initVulkan` right
-before `createCommandBuffers`.
+`createVertexBuffer` 함수를 새로 만들고 `initVulkan`의 `createCommandBuffers` 바로 직전에 호출하도록 합니다.
 
 ```c++
 void initVulkan() {
@@ -38,7 +34,7 @@ void createVertexBuffer() {
 }
 ```
 
-Creating a buffer requires us to fill a `VkBufferCreateInfo` structure.
+버퍼 생성을 위해서는 `VkBufferCreateInfo` 구조체를 채워야 합니다.
 
 ```c++
 VkBufferCreateInfo bufferInfo{};
@@ -46,32 +42,29 @@ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
 bufferInfo.size = sizeof(vertices[0]) * vertices.size();
 ```
 
-The first field of the struct is `size`, which specifies the size of the buffer
-in bytes. Calculating the byte size of the vertex data is straightforward with
-`sizeof`.
+첫 번째 필드는 `size`이고, 버퍼의 바이트 단위 크기를 명시합니다. 
+정점 데이터의 바이트 단위 크기를 계산하는 것은 `sizeof`를 사용하면 됩니다.
 
 ```c++
 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
 ```
 
-The second field is `usage`, which indicates for which purposes the data in the
-buffer is going to be used. It is possible to specify multiple purposes using a
-bitwise or. Our use case will be a vertex buffer, we'll look at other types of
-usage in future chapters.
+두 번째 필드는 `usage`인데 버퍼의 데이터가 어떤 목적으로 사용될지를 알려줍니다. 
+bitwise OR를 사용해 목적을 여러개 명기하는것도 가능합니다. 
+우리의 사용 목적은 정점 버퍼이며 다른 타입에 대해서는 다른 챕터에서 알아보겠습니다.
 
 ```c++
 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
 ```
 
-Just like the images in the swap chain, buffers can also be owned by a specific
-queue family or be shared between multiple at the same time. The buffer will
-only be used from the graphics queue, so we can stick to exclusive access.
+스왑 체인의 이미지처럼 버퍼는 특정 큐 패밀리에 의해 소유되거나 여러 큐 패밀리에서 공유될 수 있습니다.
+버퍼는 그래픽스 큐에서만 활용될 예정이므로 독점(exclusive) 접근으로 두겠습니다.
 
-The `flags` parameter is used to configure sparse buffer memory, which is not
-relevant right now. We'll leave it at the default value of `0`.
+`flag` 매개변수는 sparse한 버퍼 메모리를 설정하기 위해 사용되는데, 지금은 사용하지 않습니다. 
+기본값인 `0`으로 둘 것입니다.
 
-We can now create the buffer with `vkCreateBuffer`. Define a class member to
-hold the buffer handle and call it `vertexBuffer`.
+이제 `vkCreateBuffer`로 버퍼를 만들 수 있습니다. 
+버퍼 핸들을 저장할 `vertexBuffer`를 클래스의 멤버로 정의합니다.
 
 ```c++
 VkBuffer vertexBuffer;
@@ -91,9 +84,7 @@ void createVertexBuffer() {
 }
 ```
 
-The buffer should be available for use in rendering commands until the end of
-the program and it does not depend on the swap chain, so we'll clean it up in
-the original `cleanup` function:
+버퍼는 프로그램이 끝날 때까지 렌더링 명령에서 활용되기 위해 유효한 상태로 남아있어야 하고, 스왑 체인에는 종속적이지 않으므로 `cleanup` 함수에서 정리합니다:
 
 ```c++
 void cleanup() {
@@ -105,32 +96,26 @@ void cleanup() {
 }
 ```
 
-## Memory requirements
+## 메모리 요구사항
 
-The buffer has been created, but it doesn't actually have any memory assigned to
-it yet. The first step of allocating memory for the buffer is to query its
-memory requirements using the aptly named `vkGetBufferMemoryRequirements`
-function.
+버퍼가 생성되었지만 아직 실제로 메모리가 할당된 것은 아닙니다. 
+버퍼의 메모리 할당을 위한 첫 단계는 `vkGetBufferMemoryRequirements`라는 이름의 함수로 메모리 요구사항을 질의하는 것입니다.
 
 ```c++
 VkMemoryRequirements memRequirements;
 vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
 ```
 
-The `VkMemoryRequirements` struct has three fields:
+`VkMemoryRequirements` 구조체에는 세 개의 필드가 있습니다:
 
-* `size`: The size of the required amount of memory in bytes, may differ from
-`bufferInfo.size`.
-* `alignment`: The offset in bytes where the buffer begins in the allocated
-region of memory, depends on `bufferInfo.usage` and `bufferInfo.flags`.
-* `memoryTypeBits`: Bit field of the memory types that are suitable for the
-buffer.
+* `size`: 요구되는 메모리의 바이트 단위 크기로, `bufferInfo.size`와는 다를 수 있음
+* `alignment`: `bufferInfo.usage`와 `bufferInfo.flags`에 의해 결정되는, 메모리 영역에서 버퍼가 시작되는 바이트 오프셋(offset)
+* `memoryTypeBits`: 버퍼에 적합한 메모리 타입의 비트 필드
 
-Graphics cards can offer different types of memory to allocate from. Each type
-of memory varies in terms of allowed operations and performance characteristics.
-We need to combine the requirements of the buffer and our own application
-requirements to find the right type of memory to use. Let's create a new
-function `findMemoryType` for this purpose.
+그래픽 카드는 할당할 수 있는 서로 다른 종류의 메모리가 있습니다. 
+각 메모리 타입은 허용 가능한 연산과 성능 특성이 다릅니다. 
+버퍼의 요구사항과 우리 응용 프로그램의 요구사항을 결합하여 적합한 메모리 타입을 결정해야 합니다. 
+이러한 목적을 위해서 `findMemoryType` 함수를 새로 만듭시다.
 
 ```c++
 uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
@@ -138,22 +123,18 @@ uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
 }
 ```
 
-First we need to query info about the available types of memory using
-`vkGetPhysicalDeviceMemoryProperties`.
+먼저 사용 가능한 메모리 타입의 정보를 `vkGetPhysicalDeviceMemoryProperties`를 사용해 질의해야 합니다.
 
 ```c++
 VkPhysicalDeviceMemoryProperties memProperties;
 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
 ```
 
-The `VkPhysicalDeviceMemoryProperties` structure has two arrays `memoryTypes`
-and `memoryHeaps`. Memory heaps are distinct memory resources like dedicated
-VRAM and swap space in RAM for when VRAM runs out. The different types of memory
-exist within these heaps. Right now we'll only concern ourselves with the type
-of memory and not the heap it comes from, but you can imagine that this can
-affect performance.
+`VkPhysicalDeviceMemoryProperties` 구조체는 `memoryTypes`와 `memoryHeaps` 배열을 가지고 있습니다. 
+메모리 힙은 VRAM, 그리고 VRAM이 부족할 때 사용하는 RAM의 스왑 공간 같은 메모리 자원입니다. 이 힙 안에 여러 메모리 타입이 존재하게 됩니다. 
+지금은 메모리 타입만 사용하고 그 메모리가 어떤 힙에 존재하는 것인지 신경쓰지 않을 것이지만 그에 따라 성능에 영향이 있을 수 있다는 것은 예상하실 수 있을겁니다.
 
-Let's first find a memory type that is suitable for the buffer itself:
+먼저 버퍼에 적합한 메모리 타입을 찾습니다:
 
 ```c++
 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
@@ -165,21 +146,17 @@ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
 throw std::runtime_error("failed to find suitable memory type!");
 ```
 
-The `typeFilter` parameter will be used to specify the bit field of memory types
-that are suitable. That means that we can find the index of a suitable memory
-type by simply iterating over them and checking if the corresponding bit is set
-to `1`.
+`typeFilter` 매개변수는 적합한 메모리 타입을 명시하기 위한 비트 필드입니다. 
+즉 적합한 메모리 타입에 대한 인덱스는 그냥 반복문을 돌면서 해당 비트가 1인지를 확인하여 얻을 수 있습니다.
 
-However, we're not just interested in a memory type that is suitable for the
-vertex buffer. We also need to be able to write our vertex data to that memory.
-The `memoryTypes` array consists of `VkMemoryType` structs that specify the heap
-and properties of each type of memory. The properties define special features
-of the memory, like being able to map it so we can write to it from the CPU.
-This property is indicated with `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, but we
-also need to use the `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` property. We'll see
-why when we map the memory.
+하지만 우리는 정점 버퍼를 위한 적합한 메모리 타입에만 관심이 있는 것이 아닙니다. 
+정점 데이터를 해당 메모리에 쓸 수 있어야 합니다. 
+`memoryTypes` 배열은 힙과 각 메모리 타입의 속성을 명시하는 `VkMemoryType` 구조체의 배열입니다. 
+속성은 메모리의 특수 기능을 정의하는데 예를 들자면 CPU에서 값을 쓸 수 있도록 맵핑(map)할 수 있는지 여부와 같은 것입니다. 
+이 속성은 `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`로 명시되는데, 추가적으로 `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` 속성도 필요합니다. 
+그 이유에 대해서는 메모리 맵핑을 할 때 알게 될 겁니다.
 
-We can now modify the loop to also check for the support of this property:
+이제 반복문을 수정해 이러한 속성에 대한 지원 여부를 확인합니다:
 
 ```c++
 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
@@ -189,16 +166,12 @@ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
 }
 ```
 
-We may have more than one desirable property, so we should check if the result
-of the bitwise AND is not just non-zero, but equal to the desired properties bit
-field. If there is a memory type suitable for the buffer that also has all of
-the properties we need, then we return its index, otherwise we throw an
-exception.
+필요한 속성이 하나 이상일 수 있으므로 bitwise AND의 결과가 0이 아닌 것만을 확인하면 안되고 해당하는 비트 필드가 필요한 속성과 동일한지 확인해야 합니다. 
+버퍼에 적합한 메모리 타입이 있고 이러한 속성들을 가지고 있으면 해당 인덱스를 반환하고, 아니면 예외를 발생시키도록 합니다.
 
-## Memory allocation
+## 메모리 할당
 
-We now have a way to determine the right memory type, so we can actually
-allocate the memory by filling in the `VkMemoryAllocateInfo` structure.
+이제 올바른 메모리 타입을 정하는 법이 마련되었으니 `VkMemoryAllocateInfo` 구조체를 채워 실제로 메모리를 할당해 보겠습니다.
 
 ```c++
 VkMemoryAllocateInfo allocInfo{};
@@ -207,10 +180,8 @@ allocInfo.allocationSize = memRequirements.size;
 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
 ```
 
-Memory allocation is now as simple as specifying the size and type, both of
-which are derived from the memory requirements of the vertex buffer and the
-desired property. Create a class member to store the handle to the memory and
-allocate it with `vkAllocateMemory`.
+이제 단지 크기와 타입을 명시하면 되는데, 모두 정점 버퍼의 메모리 요구사항과 원하는 속성으로부터 도출되는 값입니다. 
+메모리에 대한 핸들을 저장하기 위한 클래스 멤버를 만들고 `vkAllocateMemory`를 통해 메모리를 할당받습니다.
 
 ```c++
 VkBuffer vertexBuffer;
@@ -223,22 +194,18 @@ if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUC
 }
 ```
 
-If memory allocation was successful, then we can now associate this memory with
-the buffer using `vkBindBufferMemory`:
+메모리 할당이 성공했으면 `vkBindBufferMemory`로 그 메모리를 버퍼와 연결(associate)시킵니다:
 
 ```c++
 vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
 ```
 
-The first three parameters are self-explanatory and the fourth parameter is the
-offset within the region of memory. Since this memory is allocated specifically
-for this the vertex buffer, the offset is simply `0`. If the offset is non-zero,
-then it is required to be divisible by `memRequirements.alignment`.
+첫 세 매개변수는 특별히 설명할 것이 없고, 네 번째 매개변수는 메모리 영역에서의 오프셋입니다. 
+지금 메모리는 정점 버퍼만을 위해 할당받은 것이므로 오프셋은 `0`입니다. 
+오프셋이 0이 아니면, `memRequirements.alignment`를 사용해 분할 가능해야만 합니다.
 
-Of course, just like dynamic memory allocation in C++, the memory should be
-freed at some point. Memory that is bound to a buffer object may be freed once
-the buffer is no longer used, so let's free it after the buffer has been
-destroyed:
+물론 C++에서의 동적 메모리 할당처럼 이 메모리는 어떤 시점에 해제되어야만 합니다. 
+버퍼 객체와 바인딩된 메모리는 버퍼가 더이상 사용되지 않을 때 해제되면 되기 때문에 버퍼의 소멸 이후에 해제하도록 합시다:
 
 ```c++
 void cleanup() {
@@ -248,24 +215,21 @@ void cleanup() {
     vkFreeMemory(device, vertexBufferMemory, nullptr);
 ```
 
-## Filling the vertex buffer
+## 정점 버퍼 채우기
 
-It is now time to copy the vertex data to the buffer. This is done by [mapping
-the buffer memory](https://en.wikipedia.org/wiki/Memory-mapped_I/O) into CPU
-accessible memory with `vkMapMemory`.
+이제 정점 데이터를 버퍼에 복사할 시간입니다. 
+이는 CPU가 접근 가능한 메모리에 `vkMapMemory`로 [버퍼 메모리 맵핑](https://en.wikipedia.org/wiki/Memory-mapped_I/O)을 함으로써 수행합니다.
 
 ```c++
 void* data;
 vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
 ```
 
-This function allows us to access a region of the specified memory resource
-defined by an offset and size. The offset and size here are `0` and
-`bufferInfo.size`, respectively. It is also possible to specify the special
-value `VK_WHOLE_SIZE` to map all of the memory. The second to last parameter can
-be used to specify flags, but there aren't any available yet in the current API.
-It must be set to the value `0`. The last parameter specifies the output for the
-pointer to the mapped memory.
+이 함수는 오프셋과 크기로 명시된 특정 메모리 리소스 영역에 접근이 가능하도록 해 줍니다. 
+여기서 오프셋과 크기는 각각 `0`과 `bufferInfo.size`입니다. 
+`VK_WHOLE_SIZE`와 같은 특수한 값으로 전체 메모리를 맵핑하는 것도 가능합니다. 
+끝에서 두 번째 매개변수는 플래그를 명시하기 위해 사용될 수도 있지만 현재 API에는 아직 사용 가능한 것이 없습니다. 따라서 값은 `0`이어야만 합니다. 
+마지막 매개변수는 맵핑된 메모리에 대한 포인터 출력입니다.
 
 ```c++
 void* data;
@@ -274,28 +238,23 @@ vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
 vkUnmapMemory(device, vertexBufferMemory);
 ```
 
-You can now simply `memcpy` the vertex data to the mapped memory and unmap it
-again using `vkUnmapMemory`. Unfortunately the driver may not immediately copy
-the data into the buffer memory, for example because of caching. It is also
-possible that writes to the buffer are not visible in the mapped memory yet.
-There are two ways to deal with that problem:
+이제 `memcpy`로 정점 데이터를 맵핑된 메모리에 복사하고 `vkUnmapMemory`를 사용해 다시 언맵핑합니다. 
+안타깝게도 드라이버가 즉시 버퍼 메모리에 복사를 수행하지 못할 수도 있습니다. 예를 들어 캐싱(chching) 떄문에요. 
+또한 버퍼에의 쓰기 작업이 아직 맵핑된 메모리에 보이지 않을 수도 있습니다. 
+이러한 문제를 처리하기 위한 두 가지 방법이 있습니다:
 
-* Use a memory heap that is host coherent, indicated with
-`VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`
-* Call `vkFlushMappedMemoryRanges` after writing to the mapped memory, and
-call `vkInvalidateMappedMemoryRanges` before reading from the mapped memory
+* `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`로 명시된 호스트에 일관성(coherent) 메모리 힙을 사용함
+* 맵핑된 메모리에 쓰기를 수행한 후 `vkFlushMappedMemoryRanges`를 호출하고, 맵핑된 메모리를 읽기 전에 `vkInvalidateMappedMemoryRanges` 를 호출함
 
-We went for the first approach, which ensures that the mapped memory always
-matches the contents of the allocated memory. Do keep in mind that this may lead
-to slightly worse performance than explicit flushing, but we'll see why that
-doesn't matter in the next chapter.
+우리는 첫 번째 방법을 사용했는데 이렇게 하면 맵핑된 메모리의 내용이 할당된 메모리와 항상 동일한 것이 보장됩니다. 
+이러한 방식은 명시적인 플러싱(flushing)에 비해 약간의 성능 손해가 있다는 것을 아셔야 하지만, 크게 상관 없습니다. 왜 그런지는 다음 챕터에서 살펴보도록 하겠습니다.
 
-Flushing memory ranges or using a coherent memory heap means that the driver will be aware of our writes to the buffer, but it doesn't mean that they are actually visible on the GPU yet. The transfer of data to the GPU is an operation that happens in the background and the specification simply [tells us](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-submission-host-writes) that it is guaranteed to be complete as of the next call to `vkQueueSubmit`.
+메모리 영역을 플러싱하거나 일관성 메모리 힙을 사용한다는 이야기는 드라이버가 버퍼에 대한 쓰기 의도를 파악하게 된다는 것이지만 아직 실제로 GPU가 그 메모리 영역을 볼 수 있다는 이야기는 아닙니다. 실제로 데이터가 GPU로 전송되는 것은 백그라운드에서 진행되며 [명세](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-submission-host-writes)에서는 단순히 다음 `vkQueueSubmit` 호출 이전에 완료가 보장된다는 것만 정의하고 있습니다.
 
-## Binding the vertex buffer
+## 정점 버퍼 바인딩
 
-All that remains now is binding the vertex buffer during rendering operations.
-We're going to extend the `recordCommandBuffer` function to do that.
+이제 남은 것은 렌더링 연산에서 정점 버퍼를 바인딩 하는 것입니다.
+`recordCommandBuffer` 함수를 확장하여 이러한 작업을 수행하도록 합니다.
 
 ```c++
 vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
@@ -307,20 +266,16 @@ vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
 vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0);
 ```
 
-The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to
-bindings, like the one we set up in the previous chapter. The first two
-parameters, besides the command buffer, specify the offset and number of
-bindings we're going to specify vertex buffers for. The last two parameters
-specify the array of vertex buffers to bind and the byte offsets to start
-reading vertex data from. You should also change the call to `vkCmdDraw` to pass
-the number of vertices in the buffer as opposed to the hardcoded number `3`.
+`vkCmdBindVertexBuffers` 함수는 정점 버퍼를 이전 챕터에서 설정한 바인딩에 바인딩합니다. 
+명령버퍼를 제외한 첫 두 매개변수는 오프셋과 정점 버퍼의 바인딩 숫자를 명시합니다. 
+마지막 두 매개변수는 바인딩할 정점 버퍼의 배열과 정점 데이터를 읽기 시작할 바이트 오프셋을 명시합니다. 
+또한 `vkCmdDraw` 호출에서도 하드코딩된 숫자 `3`을 사용하는 대신 버퍼의 정점 개수를 넘겨주도록 수정합니다.
 
-Now run the program and you should see the familiar triangle again:
+이제 프로그램을 실행하면 익숙한 삼각형을 다시 볼 수 있습니다:
 
 ![](/images/triangle.png)
 
-Try changing the color of the top vertex to white by modifying the `vertices`
-array:
+`vertices` 배열을 수정하여 위쪽 정점의 색상을 바꿔봅시다:
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -330,12 +285,11 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-Run the program again and you should see the following:
+이제 프로그램을 다시 실행하면 아래와 같은 화면이 보입니다:
 
 ![](/images/triangle_white.png)
 
-In the next chapter we'll look at a different way to copy vertex data to a
-vertex buffer that results in better performance, but takes some more work.
+다음 장에서는 정점 데이터를 정점 버퍼에 복사하는 데 있어 더 좋은 성능을 가지지만, 작업이 좀 더 필요한 방법들을 살펴볼 것입니다.
 
 [C++ code](/code/19_vertex_buffer.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /

From 2211065cf5da2d23953e7e3cfc797e3ccac39a76 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 12 Feb 2024 15:46:42 +0900
Subject: [PATCH 31/47] kr translate 04-02 staging buffer

---
 kr/04_Vertex_buffers/02_Staging_buffer.md | 194 +++++++++-------------
 1 file changed, 76 insertions(+), 118 deletions(-)

diff --git a/kr/04_Vertex_buffers/02_Staging_buffer.md b/kr/04_Vertex_buffers/02_Staging_buffer.md
index 289e74d4..bb82f470 100644
--- a/kr/04_Vertex_buffers/02_Staging_buffer.md
+++ b/kr/04_Vertex_buffers/02_Staging_buffer.md
@@ -1,47 +1,32 @@
-## Introduction
-
-The vertex buffer we have right now works correctly, but the memory type that
-allows us to access it from the CPU may not be the most optimal memory type for
-the graphics card itself to read from. The most optimal memory has the
-`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` flag and is usually not accessible by the
-CPU on dedicated graphics cards. In this chapter we're going to create two
-vertex buffers. One *staging buffer* in CPU accessible memory to upload the data
-from the vertex array to, and the final vertex buffer in device local memory.
-We'll then use a buffer copy command to move the data from the staging buffer to
-the actual vertex buffer.
-
-## Transfer queue
-
-The buffer copy command requires a queue family that supports transfer
-operations, which is indicated using `VK_QUEUE_TRANSFER_BIT`. The good news is
-that any queue family with `VK_QUEUE_GRAPHICS_BIT` or `VK_QUEUE_COMPUTE_BIT`
-capabilities already implicitly support `VK_QUEUE_TRANSFER_BIT` operations. The
-implementation is not required to explicitly list it in `queueFlags` in those
-cases.
-
-If you like a challenge, then you can still try to use a different queue family
-specifically for transfer operations. It will require you to make the following
-modifications to your program:
-
-* Modify `QueueFamilyIndices` and `findQueueFamilies` to explicitly look for a
-queue family with the `VK_QUEUE_TRANSFER_BIT` bit, but not the
-`VK_QUEUE_GRAPHICS_BIT`.
-* Modify `createLogicalDevice` to request a handle to the transfer queue
-* Create a second command pool for command buffers that are submitted on the
-transfer queue family
-* Change the `sharingMode` of resources to be `VK_SHARING_MODE_CONCURRENT` and
-specify both the graphics and transfer queue families
-* Submit any transfer commands like `vkCmdCopyBuffer` (which we'll be using in
-this chapter) to the transfer queue instead of the graphics queue
-
-It's a bit of work, but it'll teach you a lot about how resources are shared
-between queue families.
-
-## Abstracting buffer creation
-
-Because we're going to create multiple buffers in this chapter, it's a good idea
-to move buffer creation to a helper function. Create a new function
-`createBuffer` and move the code in `createVertexBuffer` (except mapping) to it.
+## 개요
+
+지금 만든 정점 버퍼는 잘 동작하지만 CPU에서 접근이 가능하도록 선택한 메모리 타입이 그래픽 카드에서 읽기에는 최적화된 메모리 타입은 아닐 수 있습니다. 
+가장 적합한 메모리는 `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` 플래그를 가지고 있고 대개는 대상 그래픽 카드에 대해 CPU에서는 접근이 불가능합니다. 
+이 챕터에서는 두 정점 버퍼를 만들 것입니다.
+하나는 *스테이징 버퍼(staging buffer)*로 CPU에서 접근 가능하여 정점 배열을 넣을 수 있으며 다른 하나는 장치의 로컬 메모리에 있는 정점 버퍼입니다. 
+그러고 나서 버퍼 복사 명령을 사용해 스테이징 버퍼에서 실제 정점 버퍼로 데이터를 옮길 것입니다.
+
+## 전송 큐(Transfer queue)
+
+버퍼 복사 맹령은 전송 연산을 지원하는 큐 패밀리가 필요하고 이는 `VK_QUEUE_TRANSFER_BIT`로 표기됩니다. 
+좋은 소식은 `VK_QUEUE_GRAPHICS_BIT`이나 `VK_QUEUE_COMPUTE_BIT` 기능이 있는 큐 패밀리는 암시적으로 `VK_QUEUE_TRANSFER_BIT` 연산을 지원한다는 것입니다. 
+이러한 경우 `queueFlags`에 이를 명시적으로 표시하도록 구현되는 것이 강제되지는 않습니다.
+
+도전을 원하신다면 전송 연산을 위해 또 다른 큐 패밀리를 사용하도록 해 보십시오. 
+이렇게 하려면 다음과 같은 추가적인 수정이 필요합니다:
+
+* `QueueFamilyIndices`와 `findQueueFamilies`를 수정하여 `VK_QUEUE_TRANSFER_BIT` 비트를 갖지만 `VK_QUEUE_GRAPHICS_BIT`는 갖지 않는 큐 패밀리를 명시적으로 탐색합니다.
+* `createLogicalDevice`를 수정하여 전송 큐에 대한 핸들을 요청하도록 합니다.
+* 명령 버퍼를 위한 두 번째 명령 풀을 만들어 전송 큐 패밀리에 제출할 것입니다.
+* 리소스의 `sharingMode`를 `VK_SHARING_MODE_CONCURRENT`로 하고 그래픽스와 전송 큐 패밀리를 명시합니다.
+* (이 챕터에서 사용할 예정인) `vkCmdCopyBuffer`와 같은 전송 명령을 그래픽스 큐 대신 전송 큐에 전송합니다.
+
+작업이 좀 필요하지만 이를 통해 큐 패밀리간에 리소스가 어떻게 공유되는지를 배우실 수 있을겁니다.
+
+## 버퍼 생성 추상화
+
+이 장에서는 여러 버퍼를 생성할 것이므로, 버퍼 생성에 관한 헬퍼 함수를 만드는 것이 좋을 것 같습니다.
+새로운 함수인 `createBuffer`를 만들고 `createVertexBuffer`의 (맵핑을 제외한) 코드를 옮겨옵니다.
 
 ```c++
 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
@@ -71,12 +56,10 @@ void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyF
 }
 ```
 
-Make sure to add parameters for the buffer size, memory properties and usage so
-that we can use this function to create many different types of buffers. The
-last two parameters are output variables to write the handles to.
+버퍼의 크기와 메모리 속성, 사용 목적에 대한 매개변수를 만들어서 다른 종류의 버퍼를 만들 때도 사용 가능하도록 함수를 정의하는 것을 잊지 마세요. 
+마지막 두 매개변수는 핸들을 저장할 출력 변수입니다.
 
-You can now remove the buffer creation and memory allocation code from
-`createVertexBuffer` and just call `createBuffer` instead:
+이제 버퍼 생성과 메모리 할당 코드를 `createVertexBuffer`에서 제거하고 대신 `createBuffer`를 호출하면 됩니다:
 
 ```c++
 void createVertexBuffer() {
@@ -90,12 +73,11 @@ void createVertexBuffer() {
 }
 ```
 
-Run your program to make sure that the vertex buffer still works properly.
+프로그램을 실행해 정점 버퍼가 여전히 제대로 동작하는지 확인해보세요.
 
-## Using a staging buffer
+## 스테이징 버퍼 사용
 
-We're now going to change `createVertexBuffer` to only use a host visible buffer
-as temporary buffer and use a device local one as actual vertex buffer.
+이제 `createVertexBuffer`를 수정해 호스트에서 보이는 버퍼는 임시 버퍼로, 장치 로컬을 실제 정점 버퍼로 사용하도록 할것입니다.
 
 ```c++
 void createVertexBuffer() {
@@ -114,24 +96,16 @@ void createVertexBuffer() {
 }
 ```
 
-We're now using a new `stagingBuffer` with `stagingBufferMemory` for mapping and
-copying the vertex data. In this chapter we're going to use two new buffer usage
-flags:
+새로운 `stagingBuffer`와 `stagingBufferMemory`를 사용해 정점 데이터를 맵핑하고 복사할 것입니다. 이 챕터에서 두 개의 새로운 버퍼 사용법(usage) 플래그를 사용합니다:
 
-* `VK_BUFFER_USAGE_TRANSFER_SRC_BIT`: Buffer can be used as source in a memory
-transfer operation.
-* `VK_BUFFER_USAGE_TRANSFER_DST_BIT`: Buffer can be used as destination in a
-memory transfer operation.
+* `VK_BUFFER_USAGE_TRANSFER_SRC_BIT`: 메모리 전송 연산에서 소스(source)로 사용되는 버퍼
+* `VK_BUFFER_USAGE_TRANSFER_DST_BIT`: 메모리 전송 연산에서 목적지(destination)로 사용되는 버퍼
 
-The `vertexBuffer` is now allocated from a memory type that is device local,
-which generally means that we're not able to use `vkMapMemory`. However, we can
-copy data from the `stagingBuffer` to the `vertexBuffer`. We have to indicate
-that we intend to do that by specifying the transfer source flag for the
-`stagingBuffer` and the transfer destination flag for the `vertexBuffer`, along
-with the vertex buffer usage flag.
+`vertexBuffer`는 이제 장치 로컬인 메모리 타입에서 할당되고, 그로 인해 일반적으로 `vkMapMemory`는 사용할 수 없게 됩니다. 
+하지만 `stagingBuffer`에서 `vertexBuffer`로 데이터를 복사할 수는 있습니다. 
+우리가 이렇게 하려고 한다는 것을 `stagingBuffer`에는 전송 소스 플래그를, `vertexBuffer`에는 전송 목적지 플래그와 정점 버퍼 플래그를 사용해 알려주어야 합니다.
 
-We're now going to write a function to copy the contents from one buffer to
-another, called `copyBuffer`.
+이제 한 버퍼에서 다른 버퍼로 복사를 하는 `copyBuffer` 함수를 만듭니다.
 
 ```c++
 void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
@@ -139,12 +113,10 @@ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
 }
 ```
 
-Memory transfer operations are executed using command buffers, just like drawing
-commands. Therefore we must first allocate a temporary command buffer. You may
-wish to create a separate command pool for these kinds of short-lived buffers,
-because the implementation may be able to apply memory allocation optimizations.
-You should use the `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` flag during command
-pool generation in that case.
+메모리 전송 연산은 그리기와 마찬가지로 명령 버퍼를 통해 실행됩니다. 
+그러므로 먼저 임시 명령 버퍼를 할당해야 합니다.
+이렇게 임시 사용되는 버퍼에 대한 별도의 명령 풀을 만들면 메모리 할당 최적화가 수행될 수 있습니다. 
+지금의 경우 명령 풀 생성시에 `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` 플래그를 사용해야 합니다.
 
 ```c++
 void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
@@ -159,7 +131,7 @@ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
 }
 ```
 
-And immediately start recording the command buffer:
+그리고 바로 명령 버퍼에 기록을 시작합니다:
 
 ```c++
 VkCommandBufferBeginInfo beginInfo{};
@@ -169,9 +141,8 @@ beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
 vkBeginCommandBuffer(commandBuffer, &beginInfo);
 ```
 
-We're only going to use the command buffer once and wait with returning from the function until the copy
-operation has finished executing. It's good practice to tell the driver about
-our intent using `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`.
+명령 버퍼를 한 번만 사용하고 복사 연산 실행이 끝나서 함수가 반환될 때까지 대기하도록 할 것입니다. 
+이러한 의도를 `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`를 사용해 드라이버에게 알려주는 것이 좋습니다.
 
 ```c++
 VkBufferCopy copyRegion{};
@@ -181,18 +152,17 @@ copyRegion.size = size;
 vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
 ```
 
-Contents of buffers are transferred using the `vkCmdCopyBuffer` command. It
-takes the source and destination buffers as arguments, and an array of regions
-to copy. The regions are defined in `VkBufferCopy` structs and consist of a
-source buffer offset, destination buffer offset and size. It is not possible to
-specify `VK_WHOLE_SIZE` here, unlike the `vkMapMemory` command.
+버퍼 내용은 `vkCmdCopyBuffer` 명령을 통해 전송됩니다. 
+소스와 목적지 버퍼, 그리고 복사할 영역에 대한 배열을 인자로 받습니다. 
+영역은 `VkBufferCopy`로 정의되며 소스 버퍼의 오프셋, 목적지 버퍼의 오프셋, 크기로 구성됩니다. 
+`vkMapMemory` 명령과는 달리 여기서는 `VK_WHOLE_SIZE`로 명시하는 것은 불가능합니다.
 
 ```c++
 vkEndCommandBuffer(commandBuffer);
 ```
 
-This command buffer only contains the copy command, so we can stop recording
-right after that. Now execute the command buffer to complete the transfer:
+이 명령 버퍼는 복사 명령만을 포함하므로 바로 기록을 중단하면 됩니다. 
+이제 명령 버퍼를 실행하여 전송을 완료합니다:
 
 ```c++
 VkSubmitInfo submitInfo{};
@@ -204,22 +174,20 @@ vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
 vkQueueWaitIdle(graphicsQueue);
 ```
 
-Unlike the draw commands, there are no events we need to wait on this time. We
-just want to execute the transfer on the buffers immediately. There are again
-two possible ways to wait on this transfer to complete. We could use a fence and
-wait with `vkWaitForFences`, or simply wait for the transfer queue to become
-idle with `vkQueueWaitIdle`. A fence would allow you to schedule multiple
-transfers simultaneously and wait for all of them complete, instead of executing
-one at a time. That may give the driver more opportunities to optimize.
+그리기 명령과는 다르게 여기서를 기다려야 할 이벤트가 없습니다. 
+그냥 버퍼에 대한 전송 명령을 바로 실행하기를 원합니다. 
+여기서도 마찬가지로 이러한 전송이 완료되는 것을 기다리는 두 가지 방법이 있습니다. 
+`vkWaitForFences`로 펜스를 사용해 대기하거나, 전송 큐가 아이들(idle) 상태가 될때까지 대기하도록 `vkQueueWaitIdle`를 사용하는 것입니다. 
+하나씩 실행하는 것이 아니라 여러 개의 전송 명령을 동시에 계획하고 전체가 끝날때까지 대기하는 경우에 펜스를 사용하면 됩니다. 
+이렇게 하면 드라이버가 최적화 하기 더 좋습니다.
 
 ```c++
 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
 ```
 
-Don't forget to clean up the command buffer used for the transfer operation.
+전송 연산을 위해 사용한 명령 버퍼를 정리하는 것을 잊지 마세요.
 
-We can now call `copyBuffer` from the `createVertexBuffer` function to move the
-vertex data to the device local buffer:
+이제 `createVertexBuffer` 함수에서 `copyBuffer`를 호출하여 정점 데이터를 장치의 로컬 버퍼로 옮깁니다: 
 
 ```c++
 createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);
@@ -227,8 +195,7 @@ createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERT
 copyBuffer(stagingBuffer, vertexBuffer, bufferSize);
 ```
 
-After copying the data from the staging buffer to the device buffer, we should
-clean it up:
+스테이징 버퍼에서 장치 버퍼로 데이터를 복사한 뒤에는 정리해 주어야 합니다:
 
 ```c++
     ...
@@ -240,27 +207,18 @@ clean it up:
 }
 ```
 
-Run your program to verify that you're seeing the familiar triangle again. The
-improvement may not be visible right now, but its vertex data is now being
-loaded from high performance memory. This will matter when we're going to start
-rendering more complex geometry.
-
-## Conclusion
-
-It should be noted that in a real world application, you're not supposed to
-actually call `vkAllocateMemory` for every individual buffer. The maximum number
-of simultaneous memory allocations is limited by the `maxMemoryAllocationCount`
-physical device limit, which may be as low as `4096` even on high end hardware
-like an NVIDIA GTX 1080. The right way to allocate memory for a large number of
-objects at the same time is to create a custom allocator that splits up a single
-allocation among many different objects by using the `offset` parameters that
-we've seen in many functions.
-
-You can either implement such an allocator yourself, or use the
-[VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
-library provided by the GPUOpen initiative. However, for this tutorial it's okay
-to use a separate allocation for every resource, because we won't come close to
-hitting any of these limits for now.
+프로그램을 실행하여 삼각형이 잘 보이는지 확인하세요. 
+개선점이 바로 눈에 보이지는 않지만 이제 정점 데이터는 고성능 메모리로부터 로드(load)됩니다. 
+보다 복잡한 형상을 렌더링 할 때에는 이러한 사실이 중요해집니다.
+
+## 결론
+
+실제 응용 프로그램에서는 개별 버퍼마다 `vkAllocateMemory`를 호출하지 않는것이 좋다는 점을 주의하십시오. 
+동시에 수행 가능한 메모리 할당은 물리적 장치의 `maxMemoryAllocationCount`에 의해 제한되며 NVIDIA GTX 1080와 같은 고성능 장치에서도 `4096`정도밖에 안됩니다. 
+많은 객체를 위한 메모리 할당을 한꺼번에 수행하는 적정한 방법은 여러 객체들에 대해 `offset` 매개변수를 사용해 한 번에 할당을 수행하는 별도의 할당자(allocator)를 만드는 것입니다.
+
+이러한 할당자를 직접 구현해도 되고, GPUOpen 이니셔티브에서 제공하는 [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) 라이브러리를 사용해도 됩니다. 
+하지만 이 튜토리얼에서는 각 리소스에 대해 별도의 할당을 수행해도 상관없는데 지금은 위와 같은 제한에 걸릴 만큼 복잡한 작업은 하지 않기 떄문입니다.
 
 [C++ code](/code/20_staging_buffer.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /

From 9aae27aac2401d07ccc2b7011934541e7b7f43b7 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 13 Feb 2024 22:54:25 +0900
Subject: [PATCH 32/47] kr translate 04-03 index buffer

---
 kr/04_Vertex_buffers/03_Index_buffer.md | 133 ++++++++++--------------
 1 file changed, 55 insertions(+), 78 deletions(-)

diff --git a/kr/04_Vertex_buffers/03_Index_buffer.md b/kr/04_Vertex_buffers/03_Index_buffer.md
index 088263db..e39ad9cd 100644
--- a/kr/04_Vertex_buffers/03_Index_buffer.md
+++ b/kr/04_Vertex_buffers/03_Index_buffer.md
@@ -1,29 +1,24 @@
-## Introduction
+## 개요
 
-The 3D meshes you'll be rendering in a real world application will often share
-vertices between multiple triangles. This already happens even with something
-simple like drawing a rectangle:
+실제 응용 프로그램에서 렌더링할 3D 메쉬는 여러 삼각형에서 정점을 공유하는 경우가 많습니다. 
+이는 아래와 같은 간단한 사각형을 렌더링 할 때도 적용됩니다:
 
 ![](/images/vertex_vs_index.svg)
 
-Drawing a rectangle takes two triangles, which means that we need a vertex
-buffer with 6 vertices. The problem is that the data of two vertices needs to be
-duplicated resulting in 50% redundancy. It only gets worse with more complex
-meshes, where vertices are reused in an average number of 3 triangles. The
-solution to this problem is to use an *index buffer*.
+사각형을 렌더링하려면 삼각형 두 개가 필요하기 때문에 정점 6개를 갖는 정점 버퍼가 필요합니다. 
+문제는 두 개의 정점은 동일한 데이터이기 때문에 50%의 중복이 발생한다는 것입니다. 
+메쉬가 복잡해지면 평균적으로 정점 한개당 3개의 삼각형에서 재사용되어 상황은 더 나빠집니다. 
+이 문제를 해결하는 방법은 *인덱스 버퍼(index buffer)*를 사용하는 것입니다.
 
-An index buffer is essentially an array of pointers into the vertex buffer. It
-allows you to reorder the vertex data, and reuse existing data for multiple
-vertices. The illustration above demonstrates what the index buffer would look
-like for the rectangle if we have a vertex buffer containing each of the four
-unique vertices. The first three indices define the upper-right triangle and the
-last three indices define the vertices for the bottom-left triangle.
+인덱스 버퍼는 정점 버퍼에 대한 포인터 배열과 같습니다. 
+정점 데이터의 순서를 바꾸고 이미 존재하는 데이터는 여러 정점으로 활용할 수 있게 해줍니다. 
+위 그림에서는 정점 버퍼가 네 개의 정점을 가지고 있을 때 인덱스 버퍼를 사용해 사각형을 표현하는 예시를 보여줍니다. 
+첫 세 개의 인덱스가 위 오른쪽 삼각형을 정의하며 마지막 세 개의 인덱스가 왼쪽 아래 삼각형을 정의합니다.
 
-## Index buffer creation
+## 인덱스 버퍼 생성
 
-In this chapter we're going to modify the vertex data and add index data to
-draw a rectangle like the one in the illustration. Modify the vertex data to
-represent the four corners:
+이 챕터에서 우리는 정점 데이터를 수정하고 인덱스 데이터를 추가하여 위 그림과 같은 사각형을 그려 볼 것입니다. 
+네 개의 꼭지점을 표현하도록 정점 데이터를 수정합니다:
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -34,10 +29,9 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-The top-left corner is red, top-right is green, bottom-right is blue and the
-bottom-left is white. We'll add a new array `indices` to represent the contents
-of the index buffer. It should match the indices in the illustration to draw the
-upper-right triangle and bottom-left triangle.
+왼쪽 위 꼭지점은 빨간색, 오른쪽 위는 초록색, 오른쪽 아래는 파란색, 왼쪽 아래는 흰색입니다. 
+인덱스 버퍼의 내용은 새로운 `indices`를 추가하여 정의합니다. 
+오른쪽 위 삼각형과 왼쪽 아래 삼각형을 위해 그림에서와 같이 인덱스를 정의합니다.
 
 ```c++
 const std::vector<uint16_t> indices = {
@@ -45,13 +39,11 @@ const std::vector<uint16_t> indices = {
 };
 ```
 
-It is possible to use either `uint16_t` or `uint32_t` for your index buffer
-depending on the number of entries in `vertices`. We can stick to `uint16_t` for
-now because we're using less than 65535 unique vertices.
+`vertices`요소 개수에 따라 인덱스 버퍼로 `uint16_t`나 `uint32_t`를 사용하는 것이 모두 가능합니다. 
+지금은 65535개보다는 정점이 적으므로 `uint16_t`를 사용합니다.
 
-Just like the vertex data, the indices need to be uploaded into a `VkBuffer` for
-the GPU to be able to access them. Define two new class members to hold the
-resources for the index buffer:
+정점 데이터와 마찬가지로 인덱스도 `VkBuffer`를 통해 GPU로 전달되어 접근 가능하게 만들어야만 합니다.
+인덱스 버퍼 리소스를 저장할 두 개의 새로운 클래스 멤버를 정의합니다:
 
 ```c++
 VkBuffer vertexBuffer;
@@ -60,8 +52,7 @@ VkBuffer indexBuffer;
 VkDeviceMemory indexBufferMemory;
 ```
 
-The `createIndexBuffer` function that we'll add now is almost identical to
-`createVertexBuffer`:
+새로 추가하는 `createIndexBuffer` 함수는 `createVertexBuffer`와 거의 동일합니다:
 
 ```c++
 void initVulkan() {
@@ -92,16 +83,13 @@ void createIndexBuffer() {
 }
 ```
 
-There are only two notable differences. The `bufferSize` is now equal to the
-number of indices times the size of the index type, either `uint16_t` or
-`uint32_t`. The usage of the `indexBuffer` should be
-`VK_BUFFER_USAGE_INDEX_BUFFER_BIT` instead of
-`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`, which makes sense. Other than that, the
-process is exactly the same. We create a staging buffer to copy the contents of
-`indices` to and then copy it to the final device local index buffer.
+눈에 띄는 차이점은 두 가지입니다. 
+`bufferSize`는 인덱스 자료형인 `uint16_t` 또는 `uint32_t`의 크기 곱하기 인덱스의 개수입니다. 
+`indexBuffer`의 사용법은 당연히 `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`가 아닌 `VK_BUFFER_USAGE_INDEX_BUFFER_BIT` 입니다.
+이 외에는 모든 것이 같습니다. 
+`indices`의 내용을 장치의 로컬 인덱스 버퍼에 복사하기 위해 스테이징 버퍼를 만들어 사용합니다.
 
-The index buffer should be cleaned up at the end of the program, just like the
-vertex buffer:
+인덱스 버퍼 정점 버퍼처럼 프로그램 종료 시점에 정리되어야 합니다:
 
 ```c++
 void cleanup() {
@@ -117,14 +105,12 @@ void cleanup() {
 }
 ```
 
-## Using an index buffer
+## 인덱스 버퍼 사용
 
-Using an index buffer for drawing involves two changes to
-`recordCommandBuffer`. We first need to bind the index buffer, just like we did
-for the vertex buffer. The difference is that you can only have a single index
-buffer. It's unfortunately not possible to use different indices for each vertex
-attribute, so we do still have to completely duplicate vertex data even if just
-one attribute varies.
+그리기를 위해 인덱스 버퍼를 사용하기 위해서는 `recordCommandBuffer`에 두 가지 변화가 필요합니다. 
+우선 정범 버퍼터럼 인덱스 버퍼도 바인딩 해야 합니다. 
+차이점은 하나의 인덱스 버퍼만 가질 수 있다는 것입니다. 
+각 정점 어트리뷰트에 대해 안타깝게도 여러 개의 인덱스를 사용할 수는 없으니 하나의 어트리뷰트만 바뀌어도 전체 정점 데이터를 복사해야 합니다.
 
 ```c++
 vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
@@ -132,47 +118,38 @@ vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
 vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
 ```
 
-An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer,
-a byte offset into it, and the type of index data as parameters. As mentioned
-before, the possible types are `VK_INDEX_TYPE_UINT16` and
-`VK_INDEX_TYPE_UINT32`.
+인덱스 버퍼의 바인딩은 `vkCmdBindIndexBuffer`로 수행되며 이 함수는 인덱스 버퍼, 바이트 오프셋, 인덱스 데이터의 타입을 매개변수로 받습니다. 
+앞서 이야기한 것처럼 타입은 `VK_INDEX_TYPE_UINT16` 또는 `VK_INDEX_TYPE_UINT32` 입니다.
 
-Just binding an index buffer doesn't change anything yet, we also need to change
-the drawing command to tell Vulkan to use the index buffer. Remove the
-`vkCmdDraw` line and replace it with `vkCmdDrawIndexed`:
+인덱스 버퍼를 바인딩한 것만으로 아직 바뀐 것은 없습니다. 
+Vulkan에 인덱스 버퍼를 사용하도록 그리기 명령 또한 수정해야 합니다. 
+`vkCmdDraw` 라인을 `vkCmdDrawIndexed`로 바꿉니다:
 
 ```c++
 vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
 ```
 
-A call to this function is very similar to `vkCmdDraw`. The first two parameters
-specify the number of indices and the number of instances. We're not using
-instancing, so just specify `1` instance. The number of indices represents the
-number of vertices that will be passed to the vertex shader. The next parameter
-specifies an offset into the index buffer, using a value of `1` would cause the
-graphics card to start reading at the second index. The second to last parameter
-specifies an offset to add to the indices in the index buffer. The final
-parameter specifies an offset for instancing, which we're not using.
+`vkCmdDraw` 호출과 매우 유사합니다. 
+첫 두 매개변수는 인덱스의 개수와 인스턴스 개수를 명시합니다. 
+인스턴싱을 하지 않으므로 `1`로 두었습니다. 
+인덱스의 개수는 정점 셰이더에 넘겨질 정점의 개수를 의미합니다. 
+다음 매개변수는 인덱스 버터의 오프셋이고 `1`을 사용하면 두 번째 인덱스부터 렌더링을 시작합니다. 
+마지막에서 두 번째 매개변수는 인덱스 버퍼에 추가할 인덱스의 오프셋을 의미합니다. 
+마지막 매개변수는 인스턴싱을 위한 오프셋이고, 여기서는 사용하지 않습니다.
 
-Now run your program and you should see the following:
+이제 프로그램을 실행하면 아래와 같은 화면이 보입니다:
 
 ![](/images/indexed_rectangle.png)
 
-You now know how to save memory by reusing vertices with index buffers. This
-will become especially important in a future chapter where we're going to load
-complex 3D models.
-
-The previous chapter already mentioned that you should allocate multiple
-resources like buffers from a single memory allocation, but in fact you should
-go a step further. [Driver developers recommend](https://developer.nvidia.com/vulkan-memory-management)
-that you also store multiple buffers, like the vertex and index buffer, into a
-single `VkBuffer` and use offsets in commands like `vkCmdBindVertexBuffers`. The
-advantage is that your data is more cache friendly in that case, because it's
-closer together. It is even possible to reuse the same chunk of memory for
-multiple resources if they are not used during the same render operations,
-provided that their data is refreshed, of course. This is known as *aliasing*
-and some Vulkan functions have explicit flags to specify that you want to do
-this.
+이제 인덱스 버퍼를 사용해 정점을 재사용하여 메모리를 아끼는 법을 배웠습니다. 
+나중에 복잡한 3D 모델을 로딩할 챕터에서는 이러한 기능이 특히 중요합니다.
+
+이전 챕터에서 버퍼와 같은 다중 리소스들을 한 번의 메모리 할당으로 진행해야 한다고 언급했지만 사실 그 이상으로 해야 할 일들이 있습니다. 
+[드라이버 개발자가 추천하길](https://developer.nvidia.com/vulkan-memory-management) 정점과 인덱스 버퍼와 같은 다중 버퍼를 하나의 `VkBuffer`에 저장하고 `vkCmdBindVertexBuffers`와 같은 명령에서 오프셋을 사용하라고 합니다. 
+이렇게 하면 데이터가 함께 존재하기 때문에 더 캐시 친화적입니다. 
+또한 같은 렌더링 연산에 사용되는 것이 아니라면, 메모리 덩어리(chunk)를 여러 리소스에서 재사용 하는 것도 가능합니다.
+이는 *앨리어싱(aliasing)*이라고 불리며 몇몇 Vulkan 함수는 이러한 동작을 수행하려 한다는 것을 알려주기 위한 명시적 플래그도 존재합니다.
+이렇게 하면 
 
 [C++ code](/code/21_index_buffer.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /

From 4808eb199a1386c218826da1f2f1a6916ff70223 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 15 Feb 2024 12:39:57 +0900
Subject: [PATCH 33/47] kr translate 05-00 descriptor layout and buffer

---
 .../00_Descriptor_layout_and_buffer.md        | 201 +++++-------------
 1 file changed, 52 insertions(+), 149 deletions(-)

diff --git a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
index b274cb38..b522f3bd 100644
--- a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
+++ b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
@@ -1,34 +1,16 @@
-## Introduction
-
-We're now able to pass arbitrary attributes to the vertex shader for each
-vertex, but what about global variables? We're going to move on to 3D graphics
-from this chapter on and that requires a model-view-projection matrix. We could
-include it as vertex data, but that's a waste of memory and it would require us
-to update the vertex buffer whenever the transformation changes. The
-transformation could easily change every single frame.
-
-The right way to tackle this in Vulkan is to use *resource descriptors*. A
-descriptor is a way for shaders to freely access resources like buffers and
-images. We're going to set up a buffer that contains the transformation matrices
-and have the vertex shader access them through a descriptor. Usage of
-descriptors consists of three parts:
-
-* Specify a descriptor layout during pipeline creation
-* Allocate a descriptor set from a descriptor pool
-* Bind the descriptor set during rendering
-
-The *descriptor layout* specifies the types of resources that are going to be
-accessed by the pipeline, just like a render pass specifies the types of
-attachments that will be accessed. A *descriptor set* specifies the actual
-buffer or image resources that will be bound to the descriptors, just like a
-framebuffer specifies the actual image views to bind to render pass attachments.
-The descriptor set is then bound for the drawing commands just like the vertex
-buffers and framebuffer.
-
-There are many types of descriptors, but in this chapter we'll work with uniform
-buffer objects (UBO). We'll look at other types of descriptors in future
-chapters, but the basic process is the same. Let's say we have the data we want
-the vertex shader to have in a C struct like this:
+## 개요
+
+이제 각 정점에 대한 어트리뷰트를 정점 셰이더에 넘길 수 있게 되었습니다만, 전역 변수는 어떤가요? 이 챕터에서부터 3D 그래픽스로 넘어갈 것인데 그러려면 모델-뷰-투영(projection) 행렬이 있어야 합니다. 이를 정점 데이터로 포함할 수도 있지만 그건 메모리도 낭비될 뿐만 아니라 변환(transformation)이 바뀌게 되면 정점 버퍼가 업데이트되어야 하는 문제가 있습니다. 변환 정보는 매 프레임마다 변화합니다.
+
+Vulkan에서 이를 처리하는 올바른 방법은 *리소스 기술자(resource descriptor)*를 사용하는 것입니다. 기술자는 셰이더가 버퍼나 이미지와 같은 리소스에 자유롭게 접근하게 해 주는 방법입니다. 우리는 변환 행렬을 가지고 있는 버퍼를 설정하고 정점 셰이더가 기술자를 통해 이에 접근할 수 있도록 할 것입니다. 기술자의 사용은 세 부분으로 이루어져 있습니다:
+
+* 파이프라인 생성 시점에 기술자 레이아웃 명시
+* 기술자 풀로부터 기술자 집합(set) 할당
+* 렌더링 시점에 기술자 바인딩
+
+*기술자 레이아웃*은 파이프라인에서 접근할 리소스의 타입을 명시하고, 이는 렌더 패스에서 접근할 어태치먼트의 타입을 명시하는 것과 비슷합니다. *기술자 집합*은 기술자에 바인딩될 버퍼나 이미지를 명시하는데, 이는 프레임버퍼가 렌더 패스 어태치먼트에 바인딩될 실제 이미지 뷰를 명시하는 것과 비슷합니다. 이후에 기술자 집합은 정점 버퍼나 프레임버퍼와 유사하게 그리기 명령에 바인딩됩니다.
+
+기술자에는 다양한 종류가 있는데 이 챕터에서는 유니폼 버퍼 객체(uniform buffer object, UBO)를 사용할 것입니다. 다른 타입의 기술자에 대해서는 나중 챕터에서 알아볼 것이고, 사용 방법은 동일합니다. 정점 셰이더에서 사용하려고 하는 데이터가 아래와 같은 C 구조체 형식의 데이터라고 해 봅시다:
 
 ```c++
 struct UniformBufferObject {
@@ -38,8 +20,7 @@ struct UniformBufferObject {
 };
 ```
 
-Then we can copy the data to a `VkBuffer` and access it through a uniform buffer
-object descriptor from the vertex shader like this:
+이 데이터를 `VkBuffer`를 통해 복사하고 UBO 기술자를 사용하여 정점 셰이더에서 아래와 같이 접근할 수 있습니다:
 
 ```glsl
 layout(binding = 0) uniform UniformBufferObject {
@@ -54,15 +35,11 @@ void main() {
 }
 ```
 
-We're going to update the model, view and projection matrices every frame to
-make the rectangle from the previous chapter spin around in 3D.
+매 프레임마다 모델, 뷰, 투영 행렬을 갱신하여 이전 챕터에서 만든 사각형이 3D 회전하도록 만들어 보겠습니다.
 
-## Vertex shader
+## 정점 셰이더
 
-Modify the vertex shader to include the uniform buffer object like it was
-specified above. I will assume that you are familiar with MVP transformations.
-If you're not, see [the resource](https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/)
-mentioned in the first chapter.
+정점 셰이더가 UBO를 사용하도록 수정하는 것은 위에서 본 것과 같습니다. 또한 여러분들이 MVP 변환에는 익숙하다고 가정하고 있습니다. 잘 모르시면 첫 번째 챕터에서 언급한 [관련 자료](https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/)를 살펴보십시오.
 
 ```glsl
 #version 450
@@ -84,20 +61,11 @@ void main() {
 }
 ```
 
-Note that the order of the `uniform`, `in` and `out` declarations doesn't
-matter. The `binding` directive is similar to the `location` directive for
-attributes. We're going to reference this binding in the descriptor layout. The
-line with `gl_Position` is changed to use the transformations to compute the
-final position in clip coordinates. Unlike the 2D triangles, the last component
-of the clip coordinates may not be `1`, which will result in a division when
-converted to the final normalized device coordinates on the screen. This is used
-in perspective projection as the *perspective division* and is essential for
-making closer objects look larger than objects that are further away.
+`uniform`, `in`, `out` 선언의 순서는 상관 없다는 것에 유의하십시오. `binding` 지시자는 어트리뷰트의 `location` 지시자와 유사합니다. 이 바인딩을 기술자 레이아웃에서 참조할 것입니다. `gl_Potision`이 있는 라인은 클립 공간에서의 위치를 계산하기 위해 변환 행렬들을 사용하는 것으로 수정되었습니다. 2D 삼각형의 경우와는 다르게, 클립 공간 좌표의 마지막 요소는 `1`이 아닐 수 있습고, 이는 최종적으로 정규화된 장치 좌표계(normalized device coordinate)로 변환될 때 나눠지게 됩니다. 원근 투영을 위해 이러한 과정이 수행되고, 이를 *perspective division*이라고 합니다. 이로 인해 가까운 물체가 멀리 있는 물체보다 크게 표현됩니다.
 
-## Descriptor set layout
+## 기술자 집합 레이아웃
 
-The next step is to define the UBO on the C++ side and to tell Vulkan about this
-descriptor in the vertex shader.
+다음 단계는 C++ 쪽에서 UBO를 정의하고 Vulkan에 이 기술자를 알려주는 것입니다.
 
 ```c++
 struct UniformBufferObject {
@@ -107,15 +75,9 @@ struct UniformBufferObject {
 };
 ```
 
-We can exactly match the definition in the shader using data types in GLM. The
-data in the matrices is binary compatible with the way the shader expects it, so
-we can later just `memcpy` a `UniformBufferObject` to a `VkBuffer`.
+GLM을 사용하면 셰이더에서 정의한 데이터 타입과 정확히 일치됩니다. 행렬 내의 데이터는 셰이더에 전달되어야 하는 형식과 바이너리 수준에서 호환되기 때문에 단순히 `UniformBufferObject`를 `VkBuffer`로 `memcpy`하기만 하면 됩니다.
 
-We need to provide details about every descriptor binding used in the shaders
-for pipeline creation, just like we had to do for every vertex attribute and its
-`location` index. We'll set up a new function to define all of this information
-called `createDescriptorSetLayout`. It should be called right before pipeline
-creation, because we're going to need it there.
+셰이더에서 사용하는 기술자 바인딩에 대한 세부사항을 파이프라인 생성시에 알려주어야 하며, 이는 정점 어트리뷰트와 `location` 인덱스에 대해 했던 작업과 비슷합니다. 이러한 정보들을 정의하기 위한 `createDescriptorSetLayout` 함수를 새로 만듭니다. 파이프라인 생성 시점에서 이러한 정보를 사용해야 하기 때문에, 파이프라인 생성 직전에 호출하도록 합니다.
 
 ```c++
 void initVulkan() {
@@ -132,8 +94,7 @@ void createDescriptorSetLayout() {
 }
 ```
 
-Every binding needs to be described through a `VkDescriptorSetLayoutBinding`
-struct.
+바인딩은 `VkDescriptorSetLayoutBinding` 구조체를 통해 기술됩니다.
 
 ```c++
 void createDescriptorSetLayout() {
@@ -144,41 +105,28 @@ void createDescriptorSetLayout() {
 }
 ```
 
-The first two fields specify the `binding` used in the shader and the type of
-descriptor, which is a uniform buffer object. It is possible for the shader
-variable to represent an array of uniform buffer objects, and `descriptorCount`
-specifies the number of values in the array. This could be used to specify a
-transformation for each of the bones in a skeleton for skeletal animation, for
-example. Our MVP transformation is in a single uniform buffer object, so we're
-using a `descriptorCount` of `1`.
+첫 두 필드는 셰이더에서 사용하는 `binding`과 기술자의 타입인 UBO를 명시합니다. 셰이더 변수가 UBO의 배열을 표현하는 것도 가능하며 `descriptorCount`는 그 배열의 개수를 명시합니다. 애니메이션을 위해 스켈레톤(skeleton) 관절들의 회전을 명시하는 등의 목적으로 사용이 가능합니다. MVP 변환은 하나의 UBO이므로 `descriptorCount`는 `1`을 사용합니다.
 
 ```c++
 uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
 ```
 
-We also need to specify in which shader stages the descriptor is going to be
-referenced. The `stageFlags` field can be a combination of `VkShaderStageFlagBits` values
-or the value `VK_SHADER_STAGE_ALL_GRAPHICS`. In our case, we're only referencing
-the descriptor from the vertex shader.
+또한 어떤 셰이더 단계를 기술자가 참조할지를 명시해야 합니다. `stageFlags` 필드는 `VkShaderStageFlagBits` 값의 조합, 또는 `VK_SHADER_STAGE_ALL_GRAPHICS` 일 수 있습니다. 우리의 경우 정점 셰이더에서만 기술자를 참조합니다.
 
 ```c++
 uboLayoutBinding.pImmutableSamplers = nullptr; // Optional
 ```
 
-The `pImmutableSamplers` field is only relevant for image sampling related
-descriptors, which we'll look at later. You can leave this to its default value.
+`pImmutableSamplers` 필드는 이미지 샘플링과 관련된 기술자에서만 사용되며 나중에 살펴볼 것입니다. 지금은 그냥 기본값으로 둡니다.
 
-All of the descriptor bindings are combined into a single
-`VkDescriptorSetLayout` object. Define a new class member above
-`pipelineLayout`:
+모든 기술자의 바인딩은 하나의 `VkDescriptorSetLayout` 객체로 표현됩니다. `pipelineLayout` 위에 새로운 클래스 멤버를 정의합니다:
 
 ```c++
 VkDescriptorSetLayout descriptorSetLayout;
 VkPipelineLayout pipelineLayout;
 ```
 
-We can then create it using `vkCreateDescriptorSetLayout`. This function accepts
-a simple `VkDescriptorSetLayoutCreateInfo` with the array of bindings:
+`vkCreateDescriptorSetLayout`를 사용해 레이아웃을 생성합니다. 이 함수는 `VkDescriptorSetLayoutCreateInfo`와 바인딩 배열을 받습니다:
 
 ```c++
 VkDescriptorSetLayoutCreateInfo layoutInfo{};
@@ -191,10 +139,7 @@ if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayo
 }
 ```
 
-We need to specify the descriptor set layout during pipeline creation to tell
-Vulkan which descriptors the shaders will be using. Descriptor set layouts are
-specified in the pipeline layout object. Modify the `VkPipelineLayoutCreateInfo`
-to reference the layout object:
+파이프라인 생성 시점에 기술자 집합 레이아웃을 명시해서 Vulkan에게 셰이더가 어떤 기술자를 사용할 것인지를 알려 주어야 합니다. 기술자 집합 레이아웃은 파이프라인 레이아웃 객체에 명시됩니다. `VkPipelineLayoutCreateInfo`를 수정하여 레이아웃 객체를 참조하도록 합니다:
 
 ```c++
 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
@@ -203,13 +148,9 @@ pipelineLayoutInfo.setLayoutCount = 1;
 pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
 ```
 
-You may be wondering why it's possible to specify multiple descriptor set
-layouts here, because a single one already includes all of the bindings. We'll
-get back to that in the next chapter, where we'll look into descriptor pools and
-descriptor sets.
+이 부분에 왜 여러개의 기술자 집합 레이아웃을 명시할 수 있게 되어있는지 궁금하실겁니다. 하나의 기술자 집합 레이아웃에 이미 모든 바인딩 정보가 들어있는데도 말이죠. 다음 챕터에서 기술자 풀과 기술자 집합을 살펴보면서 이에 대한 이야기를 해 보도록 하겠습니다.
 
-The descriptor layout should stick around while we may create new graphics
-pipelines i.e. until the program ends:
+기술자 레이아웃은 새로운 그래픽스 파이프라인이 생성될 동안, 즉 프로그램 종료 시까지 유지되어야 합니다:
 
 ```c++
 void cleanup() {
@@ -221,21 +162,13 @@ void cleanup() {
 }
 ```
 
-## Uniform buffer
+## 유니폼 버퍼(Uniform buffer)
 
-In the next chapter we'll specify the buffer that contains the UBO data for the
-shader, but we need to create this buffer first. We're going to copy new data to
-the uniform buffer every frame, so it doesn't really make any sense to have a
-staging buffer. It would just add extra overhead in this case and likely degrade
-performance instead of improving it.
+다음 챕터에서 우리는 셰이더에서 사용할 UBO 데이터를 가진 버퍼를 명시할 예정입니다. 그러려면 먼저 버퍼를 만들어야겠죠. 매 프레임 새로운 데이터를 유니폼 버퍼에 복사할 예정이므로 스테이징 버퍼를 사용하는 것은 적절하지 않습니다. 단지 추가적인 작업으로 인해 성능이 낮아질 뿐입니다.
 
-We should have multiple buffers, because multiple frames may be in flight at the same
-time and we don't want to update the buffer in preparation of the next frame while a
-previous one is still reading from it! Thus, we need to have as many uniform buffers
-as we have frames in flight, and write to a uniform buffer that is not currently
-being read by the GPU.
+여러 프레임이 동시에 작업되기 때문에 버퍼가 여러 개 필요합니다. 버퍼에서 값을 읽는 동안 다름 프레임을 위한 데이터를 덮어 쓰면 안되기 때문이죠. 따라서 사용하고 있는 프레임의 개수만큼 유니폼 버퍼가 필요하며, GPU가 읽고 있는 버퍼가 아닌 다른 버퍼에 값을 기록해야 합니다.
 
-To that end, add new class members for `uniformBuffers`, and `uniformBuffersMemory`:
+`uniformBuffers`와 `uniformBuffersMemory`를 새로운 클래스 멤버로 추가합니다:
 
 ```c++
 VkBuffer indexBuffer;
@@ -246,8 +179,7 @@ std::vector<VkDeviceMemory> uniformBuffersMemory;
 std::vector<void*> uniformBuffersMapped;
 ```
 
-Similarly, create a new function `createUniformBuffers` that is called after
-`createIndexBuffer` and allocates the buffers:
+비슷하게, 버퍼를 할당하는 `createUniformBuffers` 함수를 새로 만들고 `createIndexBuffer` 뒤에 호출하도록 합니다:
 
 ```c++
 void initVulkan() {
@@ -275,9 +207,9 @@ void createUniformBuffers() {
 }
 ```
 
-We map the buffer right after creation using `vkMapMemory` to get a pointer to which we can write the data later on. The buffer stays mapped to this pointer for the application's whole lifetime. This technique is called **"persistent mapping"** and works on all Vulkan implementations. Not having to map the buffer every time we need to update it increases performances, as mapping is not free.
+`vkMapMemory`를 사용해 버퍼를 생성한 뒤에 곧바로 맵핑하여 데이터를 쓰기 위한 포인터를 얻습니다. 프로그램의 실행 내내 버퍼는 이 포인터에 맵핑된 상태가 됩니다. 이러한 기술은 **"지속적 맵핑(persistent mapping)"**이라고 하며 모든 Vulkan 구현에서 동작합니다. 매번 데이터를 갱신할 때마다 버퍼를 맵핑하지 않아도 되기 때문에 성능이 증가합니다.
 
-The uniform data will be used for all draw calls, so the buffer containing it should only be destroyed when we stop rendering.
+유니폼 데이터는 모든 드로우 콜(draw call)에서 활용되기 때문에 렌더링이 끝났을 때 해제되어야 합니다.
 
 ```c++
 void cleanup() {
@@ -295,9 +227,9 @@ void cleanup() {
 }
 ```
 
-## Updating uniform data
+## 유니폼 데이터 갱신
 
-Create a new function `updateUniformBuffer` and add a call to it from the `drawFrame` function before submitting the next frame:
+`updateUniformBuffer` 함수를 새로 만들고 `drawFrame` 함수에서 다음 프레임의 제출 전에 호출하도록 합니다:
 
 ```c++
 void drawFrame() {
@@ -320,9 +252,7 @@ void updateUniformBuffer(uint32_t currentImage) {
 }
 ```
 
-This function will generate a new transformation every frame to make the
-geometry spin around. We need to include two new headers to implement this
-functionality:
+이 함수에서 매 프레임 새로운 변환을 생성하여 물체가 회전하도록 합니다. 이러한 기능을 구현하기 위해 두 개의 새로운 헤더를 include합니다:
 
 ```c++
 #define GLM_FORCE_RADIANS
@@ -332,15 +262,9 @@ functionality:
 #include <chrono>
 ```
 
-The `glm/gtc/matrix_transform.hpp` header exposes functions that can be used to
-generate model transformations like `glm::rotate`, view transformations like
-`glm::lookAt` and projection transformations like `glm::perspective`. The
-`GLM_FORCE_RADIANS` definition is necessary to make sure that functions like
-`glm::rotate` use radians as arguments, to avoid any possible confusion.
+`glm/gtc/matrix_transform.hpp` 헤더는 `glm::rotate`와 같은 모델 변환, `glm::lookAt`과 같은 뷰 변환, `glm::perspective`와 같은 투영 변환을 생성하기 위한 함수를 제공합니다. `glm::rotate`과 같은 함수가 라디안(radian)을 받도록 `GLM_FORCE_RADIANS`를 정의하여 혼동이 없도록 해야 합니다.
 
-The `chrono` standard library header exposes functions to do precise
-timekeeping. We'll use this to make sure that the geometry rotates 90 degrees
-per second regardless of frame rate.
+`chrono` 표준 라이브러리 헤더는 정확한 시간과 관련된 함수를 제공합니다. 이를 사용하여 프레임 레이트와 상관없이 물체가 1초에 90도 회전하도록 할 것입니다.
 
 ```c++
 void updateUniformBuffer(uint32_t currentImage) {
@@ -351,65 +275,44 @@ void updateUniformBuffer(uint32_t currentImage) {
 }
 ```
 
-The `updateUniformBuffer` function will start out with some logic to calculate
-the time in seconds since rendering has started with floating point accuracy.
+`updateUniformBuffer` 함수는 렌더링 시작부터 현재까지의 시간을 부동소수점 정밀도로 계산하기 위한 로직을 작성하는 것부터 시작합니다.
 
-We will now define the model, view and projection transformations in the
-uniform buffer object. The model rotation will be a simple rotation around the
-Z-axis using the `time` variable:
+이제 UBO에 모델, 뷰, 투영 변환을 정의합니다. 모델 회전은 Z축에 대한 회전이며, `time` 변수를 사용합니다:
 
 ```c++
 UniformBufferObject ubo{};
 ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
 ```
 
-The `glm::rotate` function takes an existing transformation, rotation angle and
-rotation axis as parameters. The `glm::mat4(1.0f)` constructor returns an
-identity matrix. Using a rotation angle of `time * glm::radians(90.0f)`
-accomplishes the purpose of rotation 90 degrees per second.
+`glm::rotate` 함수는 기존 변환과 회전 각도, 회전축을 매개변수로 받습니다. `glm::mat4(1.0f)` 생성자는 단위 행렬(identity matrix)를 반환합니다. `time * glm::radians(90.0f)`를 회전 각도로 사용함으로써 1초에 90도 회전을 하게 할 수 있습니다.
 
 ```c++
 ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
 ```
 
-For the view transformation I've decided to look at the geometry from above at a
-45 degree angle. The `glm::lookAt` function takes the eye position, center
-position and up axis as parameters.
+뷰 변환에 대해서는 45도 위에서 물체를 바라보도록 했습니다. `glm::lookAt`은 눈의 위치, 바라보는 지점과 업(up) 벡터를 매개변수로 받습니다.
 
 ```c++
 ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f);
 ```
 
-I've chosen to use a perspective projection with a 45 degree vertical
-field-of-view. The other parameters are the aspect ratio, near and far
-view planes. It is important to use the current swap chain extent to calculate
-the aspect ratio to take into account the new width and height of the window
-after a resize.
+45도의 수직 시야각(field-of-view)를 갖도록 원근 투영을 정의했습니다. 나머지 매개변수는 종횡비(aspect ratio), 근면(near plane)과 원면(far plane)입니다. 현재 스왑 체인의 범위(extent)를 기반으로 종횡비를 계산하여 윈도우 크기가 변해도 새로운 너비와 높이를 반영할 수 있도록 하는 것이 중요합니다.
 
 ```c++
 ubo.proj[1][1] *= -1;
 ```
 
-GLM was originally designed for OpenGL, where the Y coordinate of the clip
-coordinates is inverted. The easiest way to compensate for that is to flip the
-sign on the scaling factor of the Y axis in the projection matrix. If you don't
-do this, then the image will be rendered upside down.
+GLM은 원래 OpenGL을 기반으로 설계되었기 때문에 클립 좌표계의 Y축이 뒤집혀 있습니다. 이를 보정하기 위한 가장 간단한 방법은 투영행렬의 Y축 크기변환(scaling) 요소의 부호를 바꿔주는 것입니다. 이렇게 하지 않으면 위아래가 뒤집혀서 렌더링됩니다.
 
-All of the transformations are defined now, so we can copy the data in the
-uniform buffer object to the current uniform buffer. This happens in exactly the same
-way as we did for vertex buffers, except without a staging buffer. As noted earlier, we only map the uniform buffer once, so we can directly write to it without having to map again:
+모든 변환이 정의되었으니 UBO의 데이터를 현재 유니폼 버퍼로 복사할 수 있습니다. 이 과정은 정점 버퍼에서와 완전히 동일하며, 스테이징 버퍼가 없다는 점만 다릅니다. 전에 이야기한 것처럼 유니폼 버퍼는 한 번만 맵핑하므로 다시 맵핑할 필요 없이 쓰기만 수행하면 됩니다:
 
 ```c++
 memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
 ```
 
-Using a UBO this way is not the most efficient way to pass frequently changing
-values to the shader. A more efficient way to pass a small buffer of data to
-shaders are *push constants*. We may look at these in a future chapter.
+UBO를 이런 방식으로 사용하는 것은 자주 바뀌는 값을 셰이더에 전달하기 위한 효율적인 방법은 아닙니다. 작은 버퍼의 데이터를 셰이더에 전달하기 위한 더 효율적인 방법은 *상수 푸쉬(push constant)*입니다. 이에 대해서는 나중 챕터에서 알아보도록 하겠습니다.
 
-In the next chapter we'll look at descriptor sets, which will actually bind the
-`VkBuffer`s to the uniform buffer descriptors so that the shader can access this
-transformation data.
+다음 챕터에서는 `VkBuffer`들을 유니폼 버퍼 기술자에 바인딩하는 기술자 집합에 대해 알아볼 것입니다. 이를 통해 셰이더가 이러한 변환 데이터에 접근할 수 있게 될 것입니다.
 
 [C++ code](/code/22_descriptor_layout.cpp) /
 [Vertex shader](/code/22_shader_ubo.vert) /

From ecce6c65979c57ddd25b24e167509b78607d3e22 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 15 Feb 2024 13:27:38 +0900
Subject: [PATCH 34/47] kr translate fix newline inconsistency

---
 .../02_Rendering_and_presentation.md          | 187 +++++-------------
 .../03_Drawing/03_Frames_in_flight.md         |  25 +--
 .../04_Swap_chain_recreation.md               |  69 ++-----
 .../00_Vertex_input_description.md            |  69 ++-----
 .../01_Vertex_buffer_creation.md              |  77 ++------
 kr/04_Vertex_buffers/02_Staging_buffer.md     |  57 ++----
 kr/04_Vertex_buffers/03_Index_buffer.md       |  62 ++----
 7 files changed, 137 insertions(+), 409 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
index 0ab0c208..3c818cad 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
@@ -1,6 +1,5 @@
 
-이제 모든 것이 결합되는 챕터입니다. 메인 루프에서 실행되어 삼각형을 화면에 표시하는 `drawFrame`함수를 작성할 것입니다.
-이 함수를 만들고 `mainLoop`에서 호출하도록 합시다:
+이제 모든 것이 결합되는 챕터입니다. 메인 루프에서 실행되어 삼각형을 화면에 표시하는 `drawFrame`함수를 작성할 것입니다. 이 함수를 만들고 `mainLoop`에서 호출하도록 합시다:
 
 ```c++
 void mainLoop() {
@@ -35,40 +34,25 @@ void drawFrame() {
 
 <!-- Maybe add images for showing synchronization -->
 
-(*역주: 여기서 '동기화'는 동시에 실행한다는 의미가 아닌 올바른 실행 스케줄(순서)를 유지한다는 의미*)
-Vulkan의 핵심 설계 철학은 GPU에서의 실행 동기화가 명시적이라는 겁니다.
-연산의 순서는 우리가 다양한 동기화 요소들을 가지고 정의하는 것이고, 이를 통해 드라이버는 원하는 실행 순서를 파악합니다.
-이 말은 많은 Vulkan API 호출이 GPU에서 실제 동작하는 시기는 비동기적이고, 실제 연산이 끝나기 전에 함수가 종료된다는 뜻입니다.
+(*역주: 여기서 '동기화'는 동시에 실행한다는 의미가 아닌 올바른 실행 스케줄(순서)를 유지한다는 의미*) Vulkan의 핵심 설계 철학은 GPU에서의 실행 동기화가 명시적이라는 겁니다. 연산의 순서는 우리가 다양한 동기화 요소들을 가지고 정의하는 것이고, 이를 통해 드라이버는 원하는 실행 순서를 파악합니다. 이 말은 많은 Vulkan API 호출이 GPU에서 실제 동작하는 시기는 비동기적이고, 실제 연산이 끝나기 전에 함수가 종료된다는 뜻입니다.
 
-이 챕터에서는 여러 이벤트들에 대해 순서를 명시적으로 지정해야 할 필요가 있습니다.
-이러한 이벤트들이 GPU에서 일어나기 때문인데 그 예로:
+이 챕터에서는 여러 이벤트들에 대해 순서를 명시적으로 지정해야 할 필요가 있습니다. 이러한 이벤트들이 GPU에서 일어나기 때문인데 그 예로:
 
 * 스왑 체인에서 이미지 얻어오기
 * 그 이미지를 드로우하기 위한 명령 실행
 * 표시를 위해 이미지를 화면에 나타내고 스왑체임으로 다시 반환
 
 이러한 이벤트들은 단일 함수 호출로 동작하지만 실제 실행은 비동기적으로 이루어집니다.
-실제 연산이 끝나기 전에 함수가 반환되고 실행 순서도 정의되어 있지 않습니다.
-각 연산이 이전 연산에 종속적이기 때문에 이렇게 되면 안됩니다.
-따라서 원하는 순서대로 실행이 될 수 있도록 하게 해 주는 요소들을 살펴볼 것입니다.
+실제 연산이 끝나기 전에 함수가 반환되고 실행 순서도 정의되어 있지 않습니다. 각 연산이 이전 연산에 종속적이기 때문에 이렇게 되면 안됩니다. 따라서 원하는 순서대로 실행이 될 수 있도록 하게 해 주는 요소들을 살펴볼 것입니다.
 
 ### 세마포어(Semaphores)
 
-큐 연산들 사이에 순서를 추가하기 위해 세마포어를 사용할 수 있습니다.
-여기서 큐 연산은 우리가 큐에 제출한 작업들이며 명령 버퍼 내의 연산이거나 나중에 볼 함수의 연산들입니다.
-큐의 예시로는 그래픽스 큐와 표시 큐가 있습니다.
-세마포어는 동일 큐 내에서, 그리고 서로 다른 큐 사이에서 순서를 정하기 위해 사용됩니다.
+큐 연산들 사이에 순서를 추가하기 위해 세마포어를 사용할 수 있습니다. 여기서 큐 연산은 우리가 큐에 제출한 작업들이며 명령 버퍼 내의 연산이거나 나중에 볼 함수의 연산들입니다.
+큐의 예시로는 그래픽스 큐와 표시 큐가 있습니다. 세마포어는 동일 큐 내에서, 그리고 서로 다른 큐 사이에서 순서를 정하기 위해 사용됩니다.
 
-Vulkan에는 바이너리와 타임라인(timeline) 세마포어가 있습니다.
-이 튜토리얼에서는 바이너리 세마포어만 사용할 것이고 타임라인 세마포어에 대해서는 논의하지 않을 것입니다.
-이후에 세마포어라고 한다면 바이너리 세마포어를 의미하는 것입니다.
+Vulkan에는 바이너리와 타임라인(timeline) 세마포어가 있습니다. 이 튜토리얼에서는 바이너리 세마포어만 사용할 것이고 타임라인 세마포어에 대해서는 논의하지 않을 것입니다. 이후에 세마포어라고 한다면 바이너리 세마포어를 의미하는 것입니다.
 
-세마포어는 시그널 상태(signaled)이거나 시그널이 아닌 상태(unsignaled)일 수 있습니다. 일단 시그널이 아닌 상태로 시작됩니다.
-우리가 세마포어를 사용하는 방식은 우선 동일한 세마포어를 한 쪽 큐 연산에는 '시그널(signal)' 세마포어로, 다른 쪽에는 '대기(wait)' 세마포어로 사용하는 것입니다.
-예를 들어 세마포어 S가 있고 큐 연산 A와 B가 있다고 합시다.
-Vulkan에게 우리가 알려주는 것은 실행이 끝나면 연산 A 세마포어 S를 '시그널'하도록 하고, 연산 B는 실행 전에 세마포어 S를 '대기'하도록 하는 것입니다.
-연산 A가 끝나면 세마포어 S가 시그널 상태가 될 것인데, 연산 B는 S가 시그널 상태가 될때까지는 실행되지 않습니다.
-연산 B가 실행이 시작되면 세마포어 S는 자동적으로 시그널이 아닌 상태로 돌아오고 다시 사용 가능한 상태가 됩니다.
+세마포어는 시그널 상태(signaled)이거나 시그널이 아닌 상태(unsignaled)일 수 있습니다. 일단 시그널이 아닌 상태로 시작됩니다. 우리가 세마포어를 사용하는 방식은 우선 동일한 세마포어를 한 쪽 큐 연산에는 '시그널(signal)' 세마포어로, 다른 쪽에는 '대기(wait)' 세마포어로 사용하는 것입니다. 예를 들어 세마포어 S가 있고 큐 연산 A와 B가 있다고 합시다. Vulkan에게 우리가 알려주는 것은 실행이 끝나면 연산 A 세마포어 S를 '시그널'하도록 하고, 연산 B는 실행 전에 세마포어 S를 '대기'하도록 하는 것입니다. 연산 A가 끝나면 세마포어 S가 시그널 상태가 될 것인데, 연산 B는 S가 시그널 상태가 될때까지는 실행되지 않습니다. 연산 B가 실행이 시작되면 세마포어 S는 자동적으로 시그널이 아닌 상태로 돌아오고 다시 사용 가능한 상태가 됩니다.
 
 방금 설명한 내용을 의사 코드로 표현하자면 다음과 같습니다:
 ```
@@ -82,28 +66,15 @@ vkQueueSubmit(work: A, signal: S, wait: None)
 vkQueueSubmit(work: B, signal: None, wait: S)
 ```
 
-주의하셔야 할것은 위 코드에서 두 번의 `vkQueueSubmit()` 호출은 즉시 반환된다는 것입니다.
-대기 과정은 GPU에서만 일어납니다.
-CPU는 블러킹(blocking)없이 계속 실행합니다.
-CPU가 대기하도록 하려면 다른 동기화 요소가 필요합니다.
+주의하셔야 할것은 위 코드에서 두 번의 `vkQueueSubmit()` 호출은 즉시 반환된다는 것입니다. 대기 과정은 GPU에서만 일어납니다. CPU는 블러킹(blocking)없이 계속 실행합니다. CPU가 대기하도록 하려면 다른 동기화 요소가 필요합니다.
 
 ### 펜스(Fences)
 
-펜스도 비슷한 목적을 가지고 있습니다. 동일하게 동기화 실행을 위해 사용되며 CPU(호스트라고도 함)에서의 순차적 실행이 목적이라는 것만 다릅니다.
-간단히 말해 호스트가 GPU가 어떤 작업을 끝냈다는 것을 알아야만 하는 상황에서 펜스를 사용합니다.
+펜스도 비슷한 목적을 가지고 있습니다. 동일하게 동기화 실행을 위해 사용되며 CPU(호스트라고도 함)에서의 순차적 실행이 목적이라는 것만 다릅니다. 간단히 말해 호스트가 GPU가 어떤 작업을 끝냈다는 것을 알아야만 하는 상황에서 펜스를 사용합니다.
 
-세마포어와 유사하게 펜스도 시그널 상태와 시그널이 아닌 상태를 가집니다.
-작업 실행을 제출할 때 해당 작업에 펜스를 부착할 수 있습니다.
-작업이 끝나면 펜스는 시그널 상태가 됩니다.
-그리고 호스트는 펜스가 시그널 상태가 될때까지 기다리게 하면 호스트가 작업이 끝난 뒤에야만 진행되도록 보장할 수 있습니다.
+세마포어와 유사하게 펜스도 시그널 상태와 시그널이 아닌 상태를 가집니다. 작업 실행을 제출할 때 해당 작업에 펜스를 부착할 수 있습니다. 작업이 끝나면 펜스는 시그널 상태가 됩니다. 그리고 호스트는 펜스가 시그널 상태가 될때까지 기다리게 하면 호스트가 작업이 끝난 뒤에야만 진행되도록 보장할 수 있습니다.
 
-구체적인 예시로 스크린샷을 찍는 예시가 있습니다.
-예를들어 GPU에서 필요한 작업을 이미 수행했다고 합시다.
-이제 이미지를 GPU에서부터 호스트로 전송하고 메모리를 파일로 저장해야 합니다.
-전송을 위한 명령 버퍼 A와 펜스 F가 있다고 합시다.
-명령 버퍼 A를 펜스 F와 함께 제출하고 호스트에게 F가 시그널 상태가 될때까지 기다리게 합니다.
-이렇게 하면 호스트는 명령 버퍼가 실행을 끝낼 때까지 블러킹 상태가 됩니다.
-따라서 안전하게 메모리 전송이 끝난 뒤 디스크에 파일을 저장할 수 있습니다.
+구체적인 예시로 스크린샷을 찍는 예시가 있습니다. 예를들어 GPU에서 필요한 작업을 이미 수행했다고 합시다. 이제 이미지를 GPU에서부터 호스트로 전송하고 메모리를 파일로 저장해야 합니다. 전송을 위한 명령 버퍼 A와 펜스 F가 있다고 합시다. 명령 버퍼 A를 펜스 F와 함께 제출하고 호스트에게 F가 시그널 상태가 될때까지 기다리게 합니다. 이렇게 하면 호스트는 명령 버퍼가 실행을 끝낼 때까지 블러킹 상태가 됩니다. 따라서 안전하게 메모리 전송이 끝난 뒤 디스크에 파일을 저장할 수 있습니다.
 
 방금 설명한 내용을 의사 코드로 표현하자면 다음과 같습니다:
 ```
@@ -118,35 +89,22 @@ vkWaitForFence(F) // A의 실행이 끝날때 까지 실행 중단(블럭)
 save_screenshot_to_disk() // 전송이 끝날 때까지 싱행이 불가능
 ```
 
-세마포어 예시와는 달리 이 예시는 호스트의 실행을 *블럭*합니다.
-즉 모든 연산이 끝날때까지 호스트는 아무것도 하지 않는다는 뜻입니다.
-이 경우에는 스크린샷을 디스크에 저장하기 전까지 전송이 끝나야만 한다는 것을 보장해야 하기 때문입니다.
+세마포어 예시와는 달리 이 예시는 호스트의 실행을 *블럭*합니다. 즉 모든 연산이 끝날때까지 호스트는 아무것도 하지 않는다는 뜻입니다. 이 경우에는 스크린샷을 디스크에 저장하기 전까지 전송이 끝나야만 한다는 것을 보장해야 하기 때문입니다.
 
-일반적으로 꼭 필요한 경우가 아니라면 호스트를 블럭하지 않는것이 좋습니다.
-GPU에 전달하고 난 뒤 호스트는 다른 유용한 작업을 하는 것이 좋습니다.
-펜스가 시그널 상태가 되는 것을 기다리는 것은 유용한 작업이 아니죠.
-따라서 작업의 동기화를 위해서는 세마포어를 사용하거나, 아직 다루지 않은 다른 동기화 방법을 사용해야 합니다.
+일반적으로 꼭 필요한 경우가 아니라면 호스트를 블럭하지 않는것이 좋습니다. GPU에 전달하고 난 뒤 호스트는 다른 유용한 작업을 하는 것이 좋습니다. 펜스가 시그널 상태가 되는 것을 기다리는 것은 유용한 작업이 아니죠. 따라서 작업의 동기화를 위해서는 세마포어를 사용하거나, 아직 다루지 않은 다른 동기화 방법을 사용해야 합니다.
 
-펜스의 경우 시그널이 아닌 상태로 되돌리는 작업은 매뉴얼하게 수행해 주어야 합니다.
-펜스는 호스트의 실행을 제어하기 위해 사용되는 것이고 호스트가 펜스를 되돌리는 시점을 결정하게 됩니다.
-이와는 달리 세마포어는 호스트와 상관없이 GPU 내의 작업 순서를 결정하기 위해 사용됩니다.
+펜스의 경우 시그널이 아닌 상태로 되돌리는 작업은 매뉴얼하게 수행해 주어야 합니다. 펜스는 호스트의 실행을 제어하기 위해 사용되는 것이고 호스트가 펜스를 되돌리는 시점을 결정하게 됩니다. 이와는 달리 세마포어는 호스트와 상관없이 GPU 내의 작업 순서를 결정하기 위해 사용됩니다.
 
 정리하자면, 세마포어는 GPU에서의 실행 순서를 명시하기 위해 사용하고 펜스는 CPU와 GPU간의 동기화를 위해 사용한다는 것입니다.
 
 ### 어떻게 결정해야 하나요?
 
 사용할 수 있는 두 종류의 동기화 요소가 있고 마침 동기화가 필요한 두 과정이 존재합니다.
-스왑체인 연산과 이전 프레임이 끝나기를 기다리는 과정입니다.
-스왑체인 연산에 대해서는 세마포어를 사용할 것인데 이는 GPU에서 수행되는 작업이고, 호스트가 그 동안 기다리는 것을 원치 않기 때문입니다.
-이전 프레임이 끝나기를 기다리는 것은 반대의 이유로 펜스를 사용할 것인데 호스트가 기다려야 하기 때문입니다.
-그리고 기다려야 하는 이유는 한번에 하나 이상의 프레임이 그려지는 것을 원치 않기 때문입니다.
-매 프레임마다 명령 버퍼를 다시 기록하기 때문에 다음 프레임을 위한 작업을 현재 프레임의 실행이 끝나기 전에 기록할 수 없습니다.
-만일 그렇게 하게 되면 GPU가 명령을 실행하는 동안 명령 버퍼가 덮어쓰여지게 됩니다.
+스왑체인 연산과 이전 프레임이 끝나기를 기다리는 과정입니다. 스왑체인 연산에 대해서는 세마포어를 사용할 것인데 이는 GPU에서 수행되는 작업이고, 호스트가 그 동안 기다리는 것을 원치 않기 때문입니다. 이전 프레임이 끝나기를 기다리는 것은 반대의 이유로 펜스를 사용할 것인데 호스트가 기다려야 하기 때문입니다. 그리고 기다려야 하는 이유는 한번에 하나 이상의 프레임이 그려지는 것을 원치 않기 때문입니다. 매 프레임마다 명령 버퍼를 다시 기록하기 때문에 다음 프레임을 위한 작업을 현재 프레임의 실행이 끝나기 전에 기록할 수 없습니다. 만일 그렇게 하게 되면 GPU가 명령을 실행하는 동안 명령 버퍼가 덮어쓰여지게 됩니다.
 
 ## 동기화 객체의 생성
 
-스왑체인으로부터 이미지가 획득되어 렌더링할 준비가 되었는지에 대한 세마포어 하나와 렌더링이 끝나서 표시할 준비가 되었다는 세마포어 하나가 필요합니다.
-또한 한 번에 하나의 프레임만 렌더링하기 위한 펜스 하나가 필요합니다.
+스왑체인으로부터 이미지가 획득되어 렌더링할 준비가 되었는지에 대한 세마포어 하나와 렌더링이 끝나서 표시할 준비가 되었다는 세마포어 하나가 필요합니다. 또한 한 번에 하나의 프레임만 렌더링하기 위한 펜스 하나가 필요합니다.
 
 이 세마포어와 펜스 객체를 저장하기 위한 클래스 멤버를 만듭니다:
 
@@ -223,8 +181,7 @@ void cleanup() {
 
 ## 이전 프레임 기다리기
 
-프레임 시작 시점에 이전 프레임이 끝나기를 기다려야 하므로 명령 버퍼와 세마포어(*역주: 펜스일 듯*)가 사용 가능해야 합니다.
-이를 위해 `vkWaitForFences`를 호출합니다.
+프레임 시작 시점에 이전 프레임이 끝나기를 기다려야 하므로 명령 버퍼와 세마포어(*역주: 펜스일 듯*)가 사용 가능해야 합니다. 이를 위해 `vkWaitForFences`를 호출합니다.
 
 ```c++
 void drawFrame() {
@@ -232,10 +189,7 @@ void drawFrame() {
 }
 ```
 
-`vkWaitForFences` 함수는 펜스 배열을 받아서, 몇 개 또는 전체 펜스들이 시그널인 상태를 반환할 때까지 호스트를 대기하도록 합니다.
-인자로 넘긴 `VK_TRUE`는 모든 펜스를 기다리겠다는 의미인데 지금은 하나의 펜스만 있으므로 크게 의미는 없습니다.
-이 함수는 또한 타임아웃(timeout) 매개변수를 갖는데 `UINT64_MAX`를 통해 64비트 부호없는 정수의 최대값으로 설정했습니다.
-즉 타임아웃을 사용하지 않겠다는 의미입니다.
+`vkWaitForFences` 함수는 펜스 배열을 받아서, 몇 개 또는 전체 펜스들이 시그널인 상태를 반환할 때까지 호스트를 대기하도록 합니다. 인자로 넘긴 `VK_TRUE`는 모든 펜스를 기다리겠다는 의미인데 지금은 하나의 펜스만 있으므로 크게 의미는 없습니다. 이 함수는 또한 타임아웃(timeout) 매개변수를 갖는데 `UINT64_MAX`를 통해 64비트 부호없는 정수의 최대값으로 설정했습니다. 즉 타임아웃을 사용하지 않겠다는 의미입니다.
 
 대기 후에 `vkResetFences` 호출을 통해 펜스를 시그널이 아닌 상태로 리셋해 주어야 합니다:
 
@@ -243,13 +197,9 @@ void drawFrame() {
     vkResetFences(device, 1, &inFlightFence);
 ```
 
-더 진행하기 전에 현재 설계에 약간의 문제가 있습니다.
-첫 프레임에 `drawFrame()`를 호출하기 때문에 바로 `inFlightFence`가 시그널 상태가 되도록 기다립니다.
-`inFlightFence`는 프레임 렌더링이 끝나야 시그널 상태가 되는데 지금은 첫 번째 프레임이므로 펜스를 시그널 상태로 만들어줄 이전 프레임이 없습니다!
-따라서 `vkWaitForFences()`가 프로세스를 무한정 블럭해서 아무 일도 일어나지 않을 것입니다.
+더 진행하기 전에 현재 설계에 약간의 문제가 있습니다. 첫 프레임에 `drawFrame()`를 호출하기 때문에 바로 `inFlightFence`가 시그널 상태가 되도록 기다립니다. `inFlightFence`는 프레임 렌더링이 끝나야 시그널 상태가 되는데 지금은 첫 번째 프레임이므로 펜스를 시그널 상태로 만들어줄 이전 프레임이 없습니다! 따라서 `vkWaitForFences()`가 프로세스를 무한정 블럭해서 아무 일도 일어나지 않을 것입니다.
 
-이 문제를 해결하는 많은 방법 중 API에 들어있는 똑똑한 해결책이 하나 있습니다.
-펜스를 시그널인 상태로 생성해서 `vkWaitForFences()`의 첫 호출이 바로 반환되도록 하는 것입니다.
+이 문제를 해결하는 많은 방법 중 API에 들어있는 똑똑한 해결책이 하나 있습니다. 펜스를 시그널인 상태로 생성해서 `vkWaitForFences()`의 첫 호출이 바로 반환되도록 하는 것입니다.
 
 이렇게 하기 위해서 `VK_FENCE_CREATE_SIGNALED_BIT` 플래그를 `VkFenceCreateInfo`에 추가합니다:
 
@@ -267,8 +217,7 @@ void createSyncObjects() {
 
 ## 스왑 체인에서 이미지 얻어오기
 
-다음으로 `drawFrame` 함수에서 할 일은 스왑 체인으로부터 이미지를 얻어오는 것입니다.
-스왑 체인은 확장 기능이므로 `vk*KHR` 네이밍으로 되어 있는 함수를 사용해야 하는 것을 잊지 마세요.
+다음으로 `drawFrame` 함수에서 할 일은 스왑 체인으로부터 이미지를 얻어오는 것입니다. 스왑 체인은 확장 기능이므로 `vk*KHR` 네이밍으로 되어 있는 함수를 사용해야 하는 것을 잊지 마세요.
 
 ```c++
 void drawFrame() {
@@ -279,30 +228,22 @@ void drawFrame() {
 }
 ```
 
-`vkAcquireNextImageKHR`의 첫 두 매개변수는 논리적 장치와 이미지를 얻어오려고 하는 스왑 체인입니다.
-세 번째 매개변수는 이미지가 가용할때까지의 나노초 단위 타임아웃 시간입니다.
-64비트의 부호없는 정주의 최대값을 사용했고, 그 의미는 타임아웃을 적용하지 않겠다는 의미입니다.
+`vkAcquireNextImageKHR`의 첫 두 매개변수는 논리적 장치와 이미지를 얻어오려고 하는 스왑 체인입니다. 세 번째 매개변수는 이미지가 가용할때까지의 나노초 단위 타임아웃 시간입니다. 64비트의 부호없는 정주의 최대값을 사용했고, 그 의미는 타임아웃을 적용하지 않겠다는 의미입니다.
 
-다음 두 매개변수는 표시 엔진이 이미지 사용이 끝나면 시그널 상태로 변환될 동기화 객체들을 명시합니다.
-그 시점이 바로 우리가 새로운 프레임을 드로우 할 시점입니다.
-세마포어나 펜스, 또는 그 둘 다를 명시할 수 있습니다.
-여기서는 `imageAvailableSemaphore`를 사용할 것입니다.
+다음 두 매개변수는 표시 엔진이 이미지 사용이 끝나면 시그널 상태로 변환될 동기화 객체들을 명시합니다. 그 시점이 바로 우리가 새로운 프레임을 드로우 할 시점입니다.
+세마포어나 펜스, 또는 그 둘 다를 명시할 수 있습니다. 여기서는 `imageAvailableSemaphore`를 사용할 것입니다.
 
-마지막 매개변수는 사용이 가능해진 스왑 체인 이미지의 인덱스를 출력할 변수입니다.
-이 인덱스는 `swapChainImages` 배열의 `VkImage`의 인덱스입니다.
-이 인덱스를 사용해 `VkFrameBuffer`를 선택할 것입니다.
+마지막 매개변수는 사용이 가능해진 스왑 체인 이미지의 인덱스를 출력할 변수입니다. 이 인덱스는 `swapChainImages` 배열의 `VkImage`의 인덱스입니다. 이 인덱스를 사용해 `VkFrameBuffer`를 선택할 것입니다.
 
 ## 명령 버퍼 기록
 
-사용할 스왑 체인 이미지의 imageIndex를 얻었으면 이제 명령 버퍼를 기록할 수 있습니다.
-먼저, `vkResetCommandBuffer`를 호출해 명령 버퍼가 기록이 가능한 상태가 되도록 합니다.
+사용할 스왑 체인 이미지의 imageIndex를 얻었으면 이제 명령 버퍼를 기록할 수 있습니다. 먼저, `vkResetCommandBuffer`를 호출해 명령 버퍼가 기록이 가능한 상태가 되도록 합니다.
 
 ```c++
 vkResetCommandBuffer(commandBuffer, 0);
 ```
 
-`vkResetCommandBuffer`의 두 번째 매개변수는 `VkCommandBufferResetFlagBits` 플래그입니다.
-특별히 무언가 작업을 하지는 않을 것이므로 0으로 두겠습니다.
+`vkResetCommandBuffer`의 두 번째 매개변수는 `VkCommandBufferResetFlagBits` 플래그입니다. 특별히 무언가 작업을 하지는 않을 것이므로 0으로 두겠습니다.
 
 이제 `recordCommandBuffer`를 호출하여 원하는 명령을 기록합니다.
 
@@ -327,10 +268,7 @@ submitInfo.pWaitSemaphores = waitSemaphores;
 submitInfo.pWaitDstStageMask = waitStages;
 ```
 
-첫 세 개의 매개변수는 실행이 시작되기 전 대기할 세마포어, 그리고 파이프라인의 어떤 스테이지(stage)에서 대기할지를 명시합니다.
-우리의 경우 색상 값을 이미지에 기록하는 동안 대기할 것이므로 색상 어태치먼트에 쓰기를 수행하는 스테이지를 명시하였습니다.
-즉 이론적으로는 우리의 구현이 정점 셰이더 등등을 이미지가 가용하지 않은 상태에서 실행될 수 있다는 뜻입니다.
-`waitStages` 배열의 각 요소가 `pWaitSemaphores`의 동일한 인덱스와 대응됩니다.
+첫 세 개의 매개변수는 실행이 시작되기 전 대기할 세마포어, 그리고 파이프라인의 어떤 스테이지(stage)에서 대기할지를 명시합니다. 우리의 경우 색상 값을 이미지에 기록하는 동안 대기할 것이므로 색상 어태치먼트에 쓰기를 수행하는 스테이지를 명시하였습니다. 즉 이론적으로는 우리의 구현이 정점 셰이더 등등을 이미지가 가용하지 않은 상태에서 실행될 수 있다는 뜻입니다. `waitStages` 배열의 각 요소가 `pWaitSemaphores`의 동일한 인덱스와 대응됩니다.
 
 ```c++
 submitInfo.commandBufferCount = 1;
@@ -346,8 +284,7 @@ submitInfo.signalSemaphoreCount = 1;
 submitInfo.pSignalSemaphores = signalSemaphores;
 ```
 
-`signalSemaphoreCount`와 `pSignalSemaphores` 매개변수는 명령 버퍼 실행이 끝나면 시그널 상태가 될 세마포어를 명시합니다.
-우리의 경우 `renderFinishedSemaphore`를 사용합니다.
+`signalSemaphoreCount`와 `pSignalSemaphores` 매개변수는 명령 버퍼 실행이 끝나면 시그널 상태가 될 세마포어를 명시합니다. 우리의 경우 `renderFinishedSemaphore`를 사용합니다.
 
 ```c++
 if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
@@ -355,27 +292,15 @@ if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
 }
 ```
 
-이제 `vkQueueSubmit`를 사용해 명령 버퍼를 그래픽스 큐에 제출합니다.
-이 함수는 `VkSubmitInfo` 구조체 배열을 받을 수 있는데 작업량이 많을 때는 이 방식이 효율적입니다.
-마지막 매개변수는 펜스로 명령 버퍼 실행이 끝나면 시그널 상태가 됩니다.
-이렇게 하면 언제 안전하게 명령 버퍼를 다시 사용할 수 있는 상태가 되는지를 알 수 있으므로 `inFlightFence`를 사용합니다.
-이제 다음 프레임이 되면, CPU는 이번 명령 버퍼의 실행이 끝날때까지 대기하다가 새로룽 명령들을 기록하게 됩니다.
+이제 `vkQueueSubmit`를 사용해 명령 버퍼를 그래픽스 큐에 제출합니다. 이 함수는 `VkSubmitInfo` 구조체 배열을 받을 수 있는데 작업량이 많을 때는 이 방식이 효율적입니다. 마지막 매개변수는 펜스로 명령 버퍼 실행이 끝나면 시그널 상태가 됩니다. 이렇게 하면 언제 안전하게 명령 버퍼를 다시 사용할 수 있는 상태가 되는지를 알 수 있으므로 `inFlightFence`를 사용합니다. 이제 다음 프레임이 되면, CPU는 이번 명령 버퍼의 실행이 끝날때까지 대기하다가 새로룽 명령들을 기록하게 됩니다.
 
 ## 서브패스 종속성(dependencies)
 
-렌더패스의 서브패스는 이미지 레이아웃의 전환을 자동적으로 처리해 준다는 사실을 기억하십시오.
-이러한 전환은 *서브패스 종속성*에 의해 제어되는데, 서브패스간의 메모리와 실행 종속성을 명시합니다.
-지금은 하나의 서브패스만 있는 상태지만 이 서브패스의 바로 이전과 이후의 연산 또한 암시적으로 "서브패스"로 간주됩니다.
+렌더패스의 서브패스는 이미지 레이아웃의 전환을 자동적으로 처리해 준다는 사실을 기억하십시오. 이러한 전환은 *서브패스 종속성*에 의해 제어되는데, 서브패스간의 메모리와 실행 종속성을 명시합니다. 지금은 하나의 서브패스만 있는 상태지만 이 서브패스의 바로 이전과 이후의 연산 또한 암시적으로 "서브패스"로 간주됩니다.
 
-렌더패스의 시작 시점과 끝 시점에 전환을 처리해주는 내장된 종속성이 있긴 합니다만, 시작 시점의 경우 올바른 시점에 제어되지 않습니다.
-이는 전환이 파이프라인의 시작 시점에 일어난다고 가정하고 설계되었는데 우리의 경우 파이프라인 시작 시점에 이미지를 획득하지는 않은 상태입니다.
-이러한 문제를 해결하기 위한 방법이 두 가지가 있습니다.
-`imageAvailableSemaphore`의 to `waitStages`를 `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT`으로 바꾸어 이미지가 가용할 때까지 렌더패스를 시작하지 않는 방법이 있고,
-렌더패스가 `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` 스테이지를 대기하도록 하는 방법이 있습니다.
-저는 여기서 두 번째 방법을 선택했는데, 서브패스 종속성과 그 동작 방식을 살펴보는 데 좋기 때문입니다.
+렌더패스의 시작 시점과 끝 시점에 전환을 처리해주는 내장된 종속성이 있긴 합니다만, 시작 시점의 경우 올바른 시점에 제어되지 않습니다. 이는 전환이 파이프라인의 시작 시점에 일어난다고 가정하고 설계되었는데 우리의 경우 파이프라인 시작 시점에 이미지를 획득하지는 않은 상태입니다. 이러한 문제를 해결하기 위한 방법이 두 가지가 있습니다. `imageAvailableSemaphore`의 to `waitStages`를 `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT`으로 바꾸어 이미지가 가용할 때까지 렌더패스를 시작하지 않는 방법이 있고, 렌더패스가 `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` 스테이지를 대기하도록 하는 방법이 있습니다. 저는 여기서 두 번째 방법을 선택했는데, 서브패스 종속성과 그 동작 방식을 살펴보는 데 좋기 때문입니다.
 
-서브패스 종속성은 `VkSubpassDependency` 구조체에 명시됩니다.
-`createRenderPass` 함수에 라애 코드를 추가합니다:
+서브패스 종속성은 `VkSubpassDependency` 구조체에 명시됩니다. `createRenderPass` 함수에 라애 코드를 추가합니다:
 
 ```c++
 VkSubpassDependency dependency{};
@@ -383,27 +308,21 @@ dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
 dependency.dstSubpass = 0;
 ```
 
-첫 두 필드는 의존(dependency)하는 서브패스와 종속(dependent)되는 서브패스를 명시합니다.(*역주: 의존=선행되어야 하는 서브패스, 종속=후행해야 하는 서브패스*)
-특수한 값인 `VK_SUBPASS_EXTERNAL`은 `srcSubpass` 또는 `dstSubpass` 중 어디에 설정되었느냐에 따라 서브패스의 앞과 뒤에 오는 암시적인 서브패스를 명시하는 값입니다.
-`0` 인덱스는 우리의 첫 번째(그리고 유일한) 서브패스를 의미하는 인덱스입니다.
-종속성 그래프의 사이클을 방지하기 위해 `dstSubpass`는 항상 `srcSubpass`보다 커야 합니다(둘 중 하나가 `VK_SUBPASS_EXTERNAL`가 아닌 경우에 해당).
+첫 두 필드는 의존(dependency)하는 서브패스와 종속(dependent)되는 서브패스를 명시합니다.(*역주: 의존=선행되어야 하는 서브패스, 종속=후행해야 하는 서브패스*) 특수한 값인 `VK_SUBPASS_EXTERNAL`은 `srcSubpass` 또는 `dstSubpass` 중 어디에 설정되었느냐에 따라 서브패스의 앞과 뒤에 오는 암시적인 서브패스를 명시하는 값입니다. `0` 인덱스는 우리의 첫 번째(그리고 유일한) 서브패스를 의미하는 인덱스입니다. 종속성 그래프의 사이클을 방지하기 위해 `dstSubpass`는 항상 `srcSubpass`보다 커야 합니다(둘 중 하나가 `VK_SUBPASS_EXTERNAL`가 아닌 경우에 해당).
 
 ```c++
 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
 dependency.srcAccessMask = 0;
 ```
 
-다음 두 개의 필드는 대기할 연산과 그 연산이 일어날 스테이지를 명시합니다.
-접근하기 전에, 스왑 체인이 이미지를 읽기를 마칠 때까지 기다려야 합니다.
-따라서 색상 어태치먼트 출력 자체를 기다리도록 하면 됩니다.
+다음 두 개의 필드는 대기할 연산과 그 연산이 일어날 스테이지를 명시합니다. 접근하기 전에, 스왑 체인이 이미지를 읽기를 마칠 때까지 기다려야 합니다. 따라서 색상 어태치먼트 출력 자체를 기다리도록 하면 됩니다.
 
 ```c++
 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
 ```
 
-이 서브패스를 기다려야 하는 연산은 컬러 어태치먼트 스테이지에 있고 컬러 어태치먼트에 값을 쓰는 연산과 관련되어 있습니다.
-이렇게 설정하면 실제로 필요하고 허용되기 전까지는 이미지의 전환이 발생하지 않습니다.: when we want to start writing colors to it.
+이 서브패스를 기다려야 하는 연산은 컬러 어태치먼트 스테이지에 있고 컬러 어태치먼트에 값을 쓰는 연산과 관련되어 있습니다. 이렇게 설정하면 실제로 필요하고 허용되기 전까지는 이미지의 전환이 발생하지 않습니다.: when we want to start writing colors to it.
 
 ```c++
 renderPassInfo.dependencyCount = 1;
@@ -414,8 +333,7 @@ renderPassInfo.pDependencies = &dependency;
 
 ## 표시
 
-프레임을 그리는 마지막 단계는 결과를 스왑체인에 다시 제출해서 화면에 표시되도록 하는 단계입니다.
-표시는 `drawFrame` 함수 마지막에 `VkPresentInfoKHR` 구조체를 통해 설정됩니다.
+프레임을 그리는 마지막 단계는 결과를 스왑체인에 다시 제출해서 화면에 표시되도록 하는 단계입니다. 표시는 `drawFrame` 함수 마지막에 `VkPresentInfoKHR` 구조체를 통해 설정됩니다.
 
 ```c++
 VkPresentInfoKHR presentInfo{};
@@ -425,8 +343,7 @@ presentInfo.waitSemaphoreCount = 1;
 presentInfo.pWaitSemaphores = signalSemaphores;
 ```
 
-첫 두 매개변수는 `VkSubmitInfo`처럼, 표시를 수행하기 전 어떤 세마포어를 기다릴지를 명시합니다.
-우리는 명령 버퍼 실행이 끝나서 삼각형이 그려질 때까지 대기해야 하므로 그 때 시그널 상태가 되는 `signalSemaphores`를 사용합니다.
+첫 두 매개변수는 `VkSubmitInfo`처럼, 표시를 수행하기 전 어떤 세마포어를 기다릴지를 명시합니다. 우리는 명령 버퍼 실행이 끝나서 삼각형이 그려질 때까지 대기해야 하므로 그 때 시그널 상태가 되는 `signalSemaphores`를 사용합니다.
 
 ```c++
 VkSwapchainKHR swapChains[] = {swapChain};
@@ -435,24 +352,19 @@ presentInfo.pSwapchains = swapChains;
 presentInfo.pImageIndices = &imageIndex;
 ```
 
-다음 두 매개변수는 이미지를 표시할 스왑 체인과 각 스왑 체인의 이미지 인덱스를 명시합니다.
-이는 거의 항상 한 개만 사용합니다.
+다음 두 매개변수는 이미지를 표시할 스왑 체인과 각 스왑 체인의 이미지 인덱스를 명시합니다. 이는 거의 항상 한 개만 사용합니다.
 
 ```c++
 presentInfo.pResults = nullptr; // Optional
 ```
 
-마지막으로 추가적인 매개변수로 `pResults`가 있습니다.
-여기에는 `VkResult`의 배열을 명시해서 각각의 스왑 체인에서 표시가 성공적으로 이루어졌는지를 체크합니다.
-하나의 스왑 체인만 사용하는 경우 그냥 표시 함수의 반환값으로 확인하면 되기 때문에 현재는 사용하지 않습니다.
+마지막으로 추가적인 매개변수로 `pResults`가 있습니다. 여기에는 `VkResult`의 배열을 명시해서 각각의 스왑 체인에서 표시가 성공적으로 이루어졌는지를 체크합니다. 하나의 스왑 체인만 사용하는 경우 그냥 표시 함수의 반환값으로 확인하면 되기 때문에 현재는 사용하지 않습니다.
 
 ```c++
 vkQueuePresentKHR(presentQueue, &presentInfo);
 ```
 
-`vkQueuePresentKHR` 함수는 이미지를 표시하라는 요청을 스왑 체인에 제출합니다.
-다음 챕터에서 `vkAcquireNextImageKHR`와 `vkQueuePresentKHR`에 대한 오류 처리를 추가할 것입니다.
-왜냐하면 지금까지와는 다르게 이 떄의 오류는 프로그램을 종료할 정도의 오류는 아닐 수 있기 때문입니다.
+`vkQueuePresentKHR` 함수는 이미지를 표시하라는 요청을 스왑 체인에 제출합니다. 다음 챕터에서 `vkAcquireNextImageKHR`와 `vkQueuePresentKHR`에 대한 오류 처리를 추가할 것입니다. 왜냐하면 지금까지와는 다르게 이 떄의 오류는 프로그램을 종료할 정도의 오류는 아닐 수 있기 때문입니다.
 
 여기까지 모든 단계를 제대로 수행했다면 이제 프로그램을 실행하면 아래와 비슷한 장면을 보시게 될겁니다:
 
@@ -460,14 +372,11 @@ vkQueuePresentKHR(presentQueue, &presentInfo);
 
 >이 삼각형은 여러분들이 그래픽스 관련한 튜토리얼에서 보던 삼각형과 다를 수 있습니다. 왜냐하면 이 튜토리얼에서는 셰이더가 선형 색상 공간(linear color space)에서 보간을 수행한 뒤 sRGB 색상 공간으로 변환을 수행하기 때문입니다. 이러한 차이점에 대해서는 [이 블로그](https://medium.com/@heypete/hello-triangle-meet-swift-and-wide-color-6f9e246616d9)를 참고하세요.
 
-짝짝짝! 하지만 안타깝게도 검증 레이어가 활성화 된 상태라면, 프로그램이 종료될 때 오류가 발생하는 것을 보실 수 있습니다.
-`debugCallback`에 의해 출력되는 메시지가 그 이유를 알려줍니다:
+짝짝짝! 하지만 안타깝게도 검증 레이어가 활성화 된 상태라면, 프로그램이 종료될 때 오류가 발생하는 것을 보실 수 있습니다. `debugCallback`에 의해 출력되는 메시지가 그 이유를 알려줍니다:
 
 ![](/images/semaphore_in_use.png)
 
-`drawFrame`의 모든 연산이 비동기적이라는 것을 기억하십시오.
-즉 `mainLoop`에서 루프를 종료했을 때도 그리기와 표시 연산이 계속 진행되고 있다는 뜻입니다.
-연산이 진행되는 도중에 리소스를 정리하는 것은 좋지 않습니다.
+`drawFrame`의 모든 연산이 비동기적이라는 것을 기억하십시오. 즉 `mainLoop`에서 루프를 종료했을 때도 그리기와 표시 연산이 계속 진행되고 있다는 뜻입니다. 연산이 진행되는 도중에 리소스를 정리하는 것은 좋지 않습니다.
 
 이 문제를 해결하기 위해 `mainLoop`를 끝내고 윈도우를 소멸하기 이전에 논리적 장치가 연산을 끝내기를 기다려야 합니다.
 
@@ -483,15 +392,11 @@ void mainLoop() {
 ```
 
 `vkQueueWaitIdle`를 통해 특정 명령 큐의 연산이 끝나기를 기다리도록 할 수도 있습니다.
-이 함수는 동기화를 위한 아주 기초적인 방법으로 사용될 수도 있습니다.
-이제 윈도우를 닫아도 문제 없이 프로그램이 종료되는 것을 보실 수 있습니다.
+이 함수는 동기화를 위한 아주 기초적인 방법으로 사용될 수도 있습니다. 이제 윈도우를 닫아도 문제 없이 프로그램이 종료되는 것을 보실 수 있습니다.
 
 ## 결론
 
-900줄이 좀 넘는 코드로 드디어 화면에 뭔가를 표시할 수 있었습니다.
-Vulkan 프로그램을 부트스트래핑(bootstrapping)하는 것은 많은 노력이 필요하지만, 명시성으로 인해 우리에게 엄청난 양의 제어권을 제공한다는 것을 알 수 있었습니다.
-제가 권장하는 것은 이제 시간을 갖고 코드를 다시 읽어보면서 모든 Vulkan 객체들의 목적과 그들이 각각 어떻게 관련되어 있는지에 대한 개념을 복습해 보시라는 것입니다.
-이러한 지식을 가진 상태에서 이제 프로그램의 기능을 확장해 나가 볼 것입니다.
+900줄이 좀 넘는 코드로 드디어 화면에 뭔가를 표시할 수 있었습니다. Vulkan 프로그램을 부트스트래핑(bootstrapping)하는 것은 많은 노력이 필요하지만, 명시성으로 인해 우리에게 엄청난 양의 제어권을 제공한다는 것을 알 수 있었습니다. 제가 권장하는 것은 이제 시간을 갖고 코드를 다시 읽어보면서 모든 Vulkan 객체들의 목적과 그들이 각각 어떻게 관련되어 있는지에 대한 개념을 복습해 보시라는 것입니다. 이러한 지식을 가진 상태에서 이제 프로그램의 기능을 확장해 나가 볼 것입니다.
 
 다음 챕터에서는 렌더링 루프를 확장하여 여러 프레임을 사용하도록 할 것입니다.
 
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
index fb83a694..304b6f51 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
@@ -1,14 +1,10 @@
 ## 여러 프레임 사용하기(frames in-flight)
 
-지금 우리 렌더링 루프에 눈에 띄는 문제가 하나 있습니다.
-다음 프레임을 렌더링 하기 전에 이전 프레임을 기다려야만 하고 이로 인해 호스트는 불필요한 대기(ideling) 시간을 갖게 됩니다.
+지금 우리 렌더링 루프에 눈에 띄는 문제가 하나 있습니다. 다음 프레임을 렌더링 하기 전에 이전 프레임을 기다려야만 하고 이로 인해 호스트는 불필요한 대기(ideling) 시간을 갖게 됩니다.
 
 <!-- insert diagram showing our current render loop and the 'multi frame in flight' render loop -->
 
-이를 수정하는 방법은 여러 프레임을 동시에 사용하는 것입니다.
-즉, 하나의 프레임에 렌더링을 수행하는 것과 다음 프레임의 기록 과정을 서로 간섭이 없도록 할 것입니다.
-어떻게 해야 할까요? 일단 렌더링에 필요한 모든 접근과 수정이 필요한 자원들이 모두 복제되어야만 합니다. 즉, 여러 개의 명령 버퍼, 세마포어와 펜스들이 있어야 합니다.
-나중 챕터에서는 다른 리소스들에 대한 다중 인스턴스를 추가할 것이고, 그 챕터에서 이러한 개념을 다시 보게될 것입니다.
+이를 수정하는 방법은 여러 프레임을 동시에 사용하는 것입니다. 즉, 하나의 프레임에 렌더링을 수행하는 것과 다음 프레임의 기록 과정을 서로 간섭이 없도록 할 것입니다. 어떻게 해야 할까요? 일단 렌더링에 필요한 모든 접근과 수정이 필요한 자원들이 모두 복제되어야만 합니다. 즉, 여러 개의 명령 버퍼, 세마포어와 펜스들이 있어야 합니다. 나중 챕터에서는 다른 리소스들에 대한 다중 인스턴스를 추가할 것이고, 그 챕터에서 이러한 개념을 다시 보게될 것입니다.
 
 프로그램 상단에 얼마나 많은 프레임을 동시에 처리할 것인지 정의하는 상수를 먼저 추가합니다:
 
@@ -16,14 +12,9 @@
 const int MAX_FRAMES_IN_FLIGHT = 2;
 ```
 
-우리는 CPU가 GPU보다 *너무* 앞서나가는 것을 원하지는 않으므로 2로 설정하였습니다.
-두 개의 프레임을 동시에 사용하면 CPU와 GPU는 각자의 작업을 동시에 수행할 수 있습니다.
-CPU가 먼저 끝나면, 작업을 더 제출할기 전에 GPU가 렌더링을 끝내길 기다릴 것입니다.
-세 개 이상의 프레임을 동시에 사용하면 CPU가 GPU보다 앞서나가 지연되는 프레임이 발생할 수 있습니다.
-일반적으로, 이러한 지연은 좋지 않습니다. 하지만 이렇게 사용되는 프레임의 개수를 조정하는 것 또한 Vulkan의 명시성의 한 예가 될 것입니다.
+우리는 CPU가 GPU보다 *너무* 앞서나가는 것을 원하지는 않으므로 2로 설정하였습니다. 두 개의 프레임을 동시에 사용하면 CPU와 GPU는 각자의 작업을 동시에 수행할 수 있습니다. CPU가 먼저 끝나면, 작업을 더 제출할기 전에 GPU가 렌더링을 끝내길 기다릴 것입니다. 세 개 이상의 프레임을 동시에 사용하면 CPU가 GPU보다 앞서나가 지연되는 프레임이 발생할 수 있습니다. 일반적으로, 이러한 지연은 좋지 않습니다. 하지만 이렇게 사용되는 프레임의 개수를 조정하는 것 또한 Vulkan의 명시성의 한 예가 될 것입니다.
 
-각 프레임은 각자의 명령 버퍼, 세마포어 집합과 펜스를 가져야 합니다.
-이들을 `std::vector`들로 바꾸고 이름도 변경합니다.
+각 프레임은 각자의 명령 버퍼, 세마포어 집합과 펜스를 가져야 합니다. 이들을 `std::vector`들로 바꾸고 이름도 변경합니다.
 
 ```c++
 std::vector<VkCommandBuffer> commandBuffers;
@@ -35,9 +26,7 @@ std::vector<VkSemaphore> renderFinishedSemaphores;
 std::vector<VkFence> inFlightFences;
 ```
 
-이제 여러 개의 명령 버퍼를 생성해야 합니다.
-`createCommandBuffer`를 `createCommandBuffers`로 이름을 변경하고 명령 버퍼 벡터의 크기를 `MAX_FRAMES_IN_FLIGHT`로 수정합니다.
-`VkCommandBufferAllocateInfo`를 수정하여 명령 버퍼의 숫자를 받고록 하고 새로운 명령 버퍼 벡터의 위치를 넘겨줍니다:
+이제 여러 개의 명령 버퍼를 생성해야 합니다. `createCommandBuffer`를 `createCommandBuffers`로 이름을 변경하고 명령 버퍼 벡터의 크기를 `MAX_FRAMES_IN_FLIGHT`로 수정합니다. `VkCommandBufferAllocateInfo`를 수정하여 명령 버퍼의 숫자를 받고록 하고 새로운 명령 버퍼 벡터의 위치를 넘겨줍니다:
 
 ```c++
 void createCommandBuffers() {
@@ -147,9 +136,7 @@ void drawFrame() {
 <!-- Possibly use swapchain-image-count for renderFinished semaphores, as it can't
 be known with a fence whether the semaphore is ready for re-use. -->
 
-이제 동기화화 관련한 모든 구현을 마쳐서 `MAX_FRAMES_IN_FLIGHT`개 이상의 프레임 작업이 동시에 큐에 들어가지 않도록 하였으며 이러한 프레임들이 서로 겹치지도 않게 되었습니다.
-정리 부분과 같은 나머지 부분은 `vkDeviceWaitIdle` 처럼 보다 기초적인 동기화 방법에 의존하고 있습니다.
-어떠한 접근법을 사용할지는 성능 요구사항에 따라서 여러분이 선택하셔야 합니다.
+이제 동기화화 관련한 모든 구현을 마쳐서 `MAX_FRAMES_IN_FLIGHT`개 이상의 프레임 작업이 동시에 큐에 들어가지 않도록 하였으며 이러한 프레임들이 서로 겹치지도 않게 되었습니다. 정리 부분과 같은 나머지 부분은 `vkDeviceWaitIdle` 처럼 보다 기초적인 동기화 방법에 의존하고 있습니다. 어떠한 접근법을 사용할지는 성능 요구사항에 따라서 여러분이 선택하셔야 합니다.
 
 동기화에 대해 예를 통해 더 알고 싶으시면 Khronos에서 제공하는 [이 개요 문서](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present)를 살펴보세요.
 
diff --git a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
index 619f1c9a..e7d7b236 100644
--- a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
+++ b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
@@ -1,9 +1,6 @@
 ## 개요
 
-지금까지 만든 프로그램으로 성공적으로 삼각형을 그렸지만 아직 잘 처리하지 못하는 상황이 있습니다.
-윈도우 표면이 변경되어 스왑 체인이 더이상 호환되지 않을 때 입니다.
-이러한 상황이 발생하는 이유 중 하나로 윈도우의 크기가 변하는 경우가 있습니다.
-이러한 이벤트를 탐지하여 스왑 체인을 새로 만들어야만 합니다.
+지금까지 만든 프로그램으로 성공적으로 삼각형을 그렸지만 아직 잘 처리하지 못하는 상황이 있습니다. 윈도우 표면이 변경되어 스왑 체인이 더이상 호환되지 않을 때 입니다. 이러한 상황이 발생하는 이유 중 하나로 윈도우의 크기가 변하는 경우가 있습니다. 이러한 이벤트를 탐지하여 스왑 체인을 새로 만들어야만 합니다.
 
 ## 스왑 체인 재생성
 
@@ -19,13 +16,9 @@ void recreateSwapChain() {
 }
 ```
 
-먼저 `vkDeviceWaitIdle`를 호출하는데 이전 장에서처럼 이미 사용 중인 자원을 건드리면 안되기 때문입니다.
-그리고 당연히 스왑 체인은 새로 만들어야 하고요.
-이미지 뷰는 스왑 체인의 이미지와 직접적으로 관련되어 있기 때문에 다시 만들어야 합니다.
-하지막으로 프레임버퍼도 스왑 체인 이미지와 직접적으로 관련되어 있으니 역시나 마찬가지로 다시 만들어 주어야 합니다.
+먼저 `vkDeviceWaitIdle`를 호출하는데 이전 장에서처럼 이미 사용 중인 자원을 건드리면 안되기 때문입니다. 그리고 당연히 스왑 체인은 새로 만들어야 하고요. 이미지 뷰는 스왑 체인의 이미지와 직접적으로 관련되어 있기 때문에 다시 만들어야 합니다. 하지막으로 프레임버퍼도 스왑 체인 이미지와 직접적으로 관련되어 있으니 역시나 마찬가지로 다시 만들어 주어야 합니다.
 
-이러한 객체들의 이전 버전은 모두 재생성 되기 전에 정리되어야 하는데, 이를 확실히 하기 위해 정리 코드의 몇 부분을 변도의 함수로 만들어 `recreateSwapChain` 함수에서 호출 가능하도록 할 것입니다.
-이 함수는 `cleanupSwapChain`로 명명합시다:
+이러한 객체들의 이전 버전은 모두 재생성 되기 전에 정리되어야 하는데, 이를 확실히 하기 위해 정리 코드의 몇 부분을 변도의 함수로 만들어 `recreateSwapChain` 함수에서 호출 가능하도록 할 것입니다. 이 함수는 `cleanupSwapChain`로 명명합시다:
 
 ```c++
 void cleanupSwapChain() {
@@ -43,10 +36,7 @@ void recreateSwapChain() {
 }
 ```
 
-여기서는 간략화 해서 렌더패스는 재생성하지 않았습니다.
-이론적으로는 응용 프로그램의 실행 동안 스왑 체인 이미지의 포맷도 바뀔 수 있습니다.
-예를 들어 윈도우를 일반적인 모니터에서 HDR 모니터로 이동한다거나 하는 등을 생각해 볼 수 있습니다.
-이러한 경우 응용 프로램에서 HDR로의 변경이 적절히 적용되도록 렌더패스 재생성도 필요할 수 있습니다.
+여기서는 간략화 해서 렌더패스는 재생성하지 않았습니다. 이론적으로는 응용 프로그램의 실행 동안 스왑 체인 이미지의 포맷도 바뀔 수 있습니다. 예를 들어 윈도우를 일반적인 모니터에서 HDR 모니터로 이동한다거나 하는 등을 생각해 볼 수 있습니다. 이러한 경우 응용 프로램에서 HDR로의 변경이 적절히 적용되도록 렌더패스 재생성도 필요할 수 있습니다.
 
 새로 만들어진 객체들의 정리 코드는 `cleanup`에서 `cleanupSwapChain`로 옮깁니다:
 
@@ -94,19 +84,13 @@ void cleanup() {
 }
 ```
 
-`chooseSwapExtent`에서 이미 새로운 윈도우의 해상도를 질의해서 스왑 페인 이미지가 (새로운) 윈도우에 적합한 크기가 되도록 했다는 것에 주목하십시오.
-따라서 `chooseSwapExtent`를 수정할 필요는 없습니다(`glfwGetFramebufferSize`를 사용해서 스왑 체인 생성 시점에 픽셀 단위의 표면 해상도를 얻어왔다는 것을 기억하세요).
+`chooseSwapExtent`에서 이미 새로운 윈도우의 해상도를 질의해서 스왑 페인 이미지가 (새로운) 윈도우에 적합한 크기가 되도록 했다는 것에 주목하십시오. 따라서 `chooseSwapExtent`를 수정할 필요는 없습니다(`glfwGetFramebufferSize`를 사용해서 스왑 체인 생성 시점에 픽셀 단위의 표면 해상도를 얻어왔다는 것을 기억하세요).
 
-이로써 스왑 체인을 재생성하는 부분은 끝입니다!
-하지만 이러한 접근법의 단점은 새로운 스왑 체인이 생성될 때까지 모든 렌더링이 중단된다는 것입니다.
-이전 스왑 체인이 사용되는 동안에 그리기가 수행되는 동안에 대 스왑 체인을 만드는 것도 가능합니다.
-그러려면 `VkSwapchainCreateInfoKHR` 구조체의 `oldSwapChain` 필드에 이전 스왑 체인을 전달하고 사용이 끝난 뒤 소멸시키면 됩니다.
+이로써 스왑 체인을 재생성하는 부분은 끝입니다! 하지만 이러한 접근법의 단점은 새로운 스왑 체인이 생성될 때까지 모든 렌더링이 중단된다는 것입니다. 이전 스왑 체인이 사용되는 동안에 그리기가 수행되는 동안에 대 스왑 체인을 만드는 것도 가능합니다. 그러려면 `VkSwapchainCreateInfoKHR` 구조체의 `oldSwapChain` 필드에 이전 스왑 체인을 전달하고 사용이 끝난 뒤 소멸시키면 됩니다.
 
 ## 최적화되지 않았거나 부적합한 스왑 체인
 
-이제 언제 스왑 체인 재생성이 필요한지 알아내서 `recreateSwapChain` 함수를 호출하면 됩니다.
-다행히 Vulkan은 대개 표시 단계에서 현재 스왑 체인이 적합하지 않게 된 시점에 이러한 것을 알려 줍니다.
-`vkAcquireNextImageKHR`와 `vkQueuePresentKHR` 함수는 아래와 같은 특정한 값으로 이러한 상황을 알려줍니다.
+이제 언제 스왑 체인 재생성이 필요한지 알아내서 `recreateSwapChain` 함수를 호출하면 됩니다. 다행히 Vulkan은 대개 표시 단계에서 현재 스왑 체인이 적합하지 않게 된 시점에 이러한 것을 알려 줍니다. `vkAcquireNextImageKHR`와 `vkQueuePresentKHR` 함수는 아래와 같은 특정한 값으로 이러한 상황을 알려줍니다.
 
 * `VK_ERROR_OUT_OF_DATE_KHR`: 스왑 체인이 표면과 호환이 불가능하여 렌더링이 불가능하게 되었음. 일반적으로 윈도우의 크기가 변했을 때 발생
 * `VK_SUBOPTIMAL_KHR`: 스왑 체인이 표면을 표현하는 데 여전히 사용 가능하지만 표면 속성이 정확히 일치하지는 않음
@@ -122,11 +106,9 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR) {
 }
 ```
 
-이미지를 획득하려 할 때 스왑체인이 부적합하다고 판단되면 그 이미지는 표현에 활용할 수 없습니다.
-따라서 즉시 스왑 체인을 재생성하고 다음 `drawFrame`을 다시 호출해야 합니다.
+이미지를 획득하려 할 때 스왑체인이 부적합하다고 판단되면 그 이미지는 표현에 활용할 수 없습니다. 따라서 즉시 스왑 체인을 재생성하고 다음 `drawFrame`을 다시 호출해야 합니다.
 
-스왑 체인이 최적화되지 않은 경우에도 이렇게 하도록 할 수도 있지만 저는 이 경우에는 어쨌든 이미지를 이미 획득했기 때문에 그냥 진행하기로 했습니다.
-`VK_SUCCESS`와 `VK_SUBOPTIMAL_KHR`는 모두 "성공" 반환 코드로 취급합니다.
+스왑 체인이 최적화되지 않은 경우에도 이렇게 하도록 할 수도 있지만 저는 이 경우에는 어쨌든 이미지를 이미 획득했기 때문에 그냥 진행하기로 했습니다. `VK_SUCCESS`와 `VK_SUBOPTIMAL_KHR`는 모두 "성공" 반환 코드로 취급합니다.
 
 ```c++
 result = vkQueuePresentKHR(presentQueue, &presentInfo);
@@ -140,20 +122,13 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
 currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
 ```
 
-`vkQueuePresentKHR` 함수는 위와 같은 의미를 가진 같은 값들을 반환합니다.
-이 경우에는 최적화되지 않은 경우에도 스왑 체인을 재생성하는데 가능한 좋은 결과를 얻고 싶기 때문입니다.
+`vkQueuePresentKHR` 함수는 위와 같은 의미를 가진 같은 값들을 반환합니다. 이 경우에는 최적화되지 않은 경우에도 스왑 체인을 재생성하는데 가능한 좋은 결과를 얻고 싶기 때문입니다.
 
 ## 데드락(deadlock) 해소
 
-지금 시점에서 코드를 실행하면 데드락이 발생할 수 있습니다.
-코드를 디버깅해보면 `vkWaitForFences`에는 도달하지만 여기에서 더 이상 진행하지 못하는 것을 볼 수 있습니다.
-이는 `vkAcquireNextImageKHR`이 `VK_ERROR_OUT_OF_DATE_KHR`을 반환하면 스왑체인을 재생성하고 `drawFrame`로 돌아가게 했기 때문입니다.
-하지만 그러한 처리는 현재 프레임의 펜스가 기다리는 상태에서 일어날 수 있습니다.
-바로 반환되는 바람에 아무런 작업도 제출되지 않았고 펜스는 시그널 상태가 될 수 없어서 `vkWaitForFences`에서 멈춘 상태가 됩니다.
+지금 시점에서 코드를 실행하면 데드락이 발생할 수 있습니다. 코드를 디버깅해보면 `vkWaitForFences`에는 도달하지만 여기에서 더 이상 진행하지 못하는 것을 볼 수 있습니다. 이는 `vkAcquireNextImageKHR`이 `VK_ERROR_OUT_OF_DATE_KHR`을 반환하면 스왑체인을 재생성하고 `drawFrame`로 돌아가게 했기 때문입니다. 하지만 그러한 처리는 현재 프레임의 펜스가 기다리는 상태에서 일어날 수 있습니다. 바로 반환되는 바람에 아무런 작업도 제출되지 않았고 펜스는 시그널 상태가 될 수 없어서 `vkWaitForFences`에서 멈춘 상태가 됩니다.
 
-다행히 손쉬운 해결법이 있습니다.
-작업을 다시 제출할 것이 확실한 시점까지 펜스를 리셋하는 것을 미루는 것입니다.
-이렇게 되면 빠른 반환이 일어났을 때 펜스는 여전히 시그널 상태이고 `vkWaitForFences`는 다음 프레임에서 데드락이 발생하지 않을 것입니다.
+다행히 손쉬운 해결법이 있습니다. 작업을 다시 제출할 것이 확실한 시점까지 펜스를 리셋하는 것을 미루는 것입니다. 이렇게 되면 빠른 반환이 일어났을 때 펜스는 여전히 시그널 상태이고 `vkWaitForFences`는 다음 프레임에서 데드락이 발생하지 않을 것입니다.
 
 `drawFrame`의 시작 부분으 다음과 같이 되어야 합니다
 :
@@ -174,11 +149,9 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR) {
 vkResetFences(device, 1, &inFlightFences[currentFrame]);
 ```
 
-## 크기 변환을 명시적으로 처리하기
+## 크기 변환의 명시적 처리
 
-윈도우 크기 변환에 대해 많은 드라이버와 플랫폼이 `VK_ERROR_OUT_OF_DATE_KHR`를 자동으로 반환해주지만, 이러한 동작이 보장된 것은 아닙니다.
-따라서 추가적인 코드를 통해 크기 변환을 명시적으로 처리해 주도록 하겠습니다.
-먼저 크기 변환이 일어났을 때를 위한 플래그를 멤버 변수로 추가합니다:
+윈도우 크기 변환에 대해 많은 드라이버와 플랫폼이 `VK_ERROR_OUT_OF_DATE_KHR`를 자동으로 반환해주지만, 이러한 동작이 보장된 것은 아닙니다. 따라서 추가적인 코드를 통해 크기 변환을 명시적으로 처리해 주도록 하겠습니다. 먼저 크기 변환이 일어났을 때를 위한 플래그를 멤버 변수로 추가합니다:
 
 ```c++
 std::vector<VkFence> inFlightFences;
@@ -197,9 +170,7 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebu
 }
 ```
 
-이러한 작업을 `vkQueuePresentKHR` 뒤에 진행해서 세마포어가 적합상 상태에 있도록 하는 것이 중요합니다.
-그렇지 않으면 시그널 상태인 세마포어가 제대로 대기를 하지 못할 수 있습니다.
-이제 실제 크기 변경을 탐지하기 위해 GLFW 프레임워크의 `glfwSetFramebufferSizeCallback` 함수를 사용하여 콜백을 설정합니다:
+이러한 작업을 `vkQueuePresentKHR` 뒤에 진행해서 세마포어가 적합상 상태에 있도록 하는 것이 중요합니다. 그렇지 않으면 시그널 상태인 세마포어가 제대로 대기를 하지 못할 수 있습니다. 이제 실제 크기 변경을 탐지하기 위해 GLFW 프레임워크의 `glfwSetFramebufferSizeCallback` 함수를 사용하여 콜백을 설정합니다:
 
 ```c++
 void initWindow() {
@@ -237,12 +208,9 @@ static void framebufferResizeCallback(GLFWwindow* window, int width, int height)
 
 이제 프로그램을 실행하고 윈도우 크기를 조정하여 프레임버퍼가 윈도우 크기에 맞게 조정되는지 살펴 보세요.
 
-## 최소화 처리하기
+## 최소화 처리
 
-스왑 체인이 부적합하게 되는 다른 또다른 경우로 특수한 윈도우 크기 변경 사례가 있습니다. 바로 윈도우 최소화 입니다.
-이 경우가 특수한 이유는 프레임버퍼 크기가 `0`이 되기 떄문입니다.
-이 튜토리얼에서는 이러한 경우에 대해 윈도우가 다시 활성화가 될때까지 정지하는 방식으로 처리할 것입니다.
-`recreateSwapChain` 함수를 사용합니다:
+스왑 체인이 부적합하게 되는 다른 또다른 경우로 특수한 윈도우 크기 변경 사례가 있습니다. 바로 윈도우 최소화 입니다. 이 경우가 특수한 이유는 프레임버퍼 크기가 `0`이 되기 떄문입니다. 이 튜토리얼에서는 이러한 경우에 대해 윈도우가 다시 활성화가 될때까지 정지하는 방식으로 처리할 것입니다. `recreateSwapChain` 함수를 사용합니다:
 
 ```c++
 void recreateSwapChain() {
@@ -261,8 +229,7 @@ void recreateSwapChain() {
 
 처음의 `glfwGetFramebufferSize` 호출은 올바른 크기일 경우에 대한 것으로 이 경우 `glfwWaitEvents`는 기다릴 것이 없습니다.
 
-축하합니다! 이제 올바로 동작하는 것 Vulkan 프로그램을 완성했습니다!
-다음 챕터에서는 정점 셰이더에 하드코딩된 정점을 제거하고 정점 버퍼(vertex buffer)를 사용해 볼 것입니다.
+축하합니다! 이제 올바로 동작하는 것 Vulkan 프로그램을 완성했습니다! 다음 챕터에서는 정점 셰이더에 하드코딩된 정점을 제거하고 정점 버퍼(vertex buffer)를 사용해 볼 것입니다.
 
 [C++ code](/code/17_swap_chain_recreation.cpp) /
 [Vertex shader](/code/09_shader_base.vert) /
diff --git a/kr/04_Vertex_buffers/00_Vertex_input_description.md b/kr/04_Vertex_buffers/00_Vertex_input_description.md
index a235a9de..28e00be0 100644
--- a/kr/04_Vertex_buffers/00_Vertex_input_description.md
+++ b/kr/04_Vertex_buffers/00_Vertex_input_description.md
@@ -1,12 +1,10 @@
 ## 개요
 
-다음 몇 챕터동안 정점 셰이더에 하드코딩된 정점 데이터를 메모리의 정점 버퍼(vertex buffer)로 바꾸어 보겠습니다.
-먼저 가장 손쉬운 방법인 CPU에서 보이는(visible) 버퍼를 만든 뒤 `memcpy`를 통해 정점 데이터를 직접 복사하는 방법을 알아볼 것이고, 이후에 스테이징 버퍼(staging buffer)를 사용해 정점 데이터를 고성능 메모리에 복사하는 방법을 알아볼 것입니다.
+다음 몇 챕터동안 정점 셰이더에 하드코딩된 정점 데이터를 메모리의 정점 버퍼(vertex buffer)로 바꾸어 보겠습니다. 먼저 가장 손쉬운 방법인 CPU에서 보이는(visible) 버퍼를 만든 뒤 `memcpy`를 통해 정점 데이터를 직접 복사하는 방법을 알아볼 것이고, 이후에 스테이징 버퍼(staging buffer)를 사용해 정점 데이터를 고성능 메모리에 복사하는 방법을 알아볼 것입니다.
 
 ## 정점 셰이더
 
-먼저 정점 셰이더가 정점 데이터를 코드로 포함하지 않도록 수정할 것입니다.
-정점 셰이더는 `in` 키워드로 정점 버퍼에서 입력을 받을 것입니다.
+먼저 정점 셰이더가 정점 데이터를 코드로 포함하지 않도록 수정할 것입니다. 정점 셰이더는 `in` 키워드로 정점 버퍼에서 입력을 받을 것입니다.
 
 ```glsl
 #version 450
@@ -22,13 +20,9 @@ void main() {
 }
 ```
 
-`inPosition`와 `inColor` 변수는 *정점 어트리뷰트(vertex attribute)*입니다.
-이는 정점 버퍼에 명시된 정점별 속성이며, 기존처럼 위치와 속성 데이터 입니다.
-정점 셰이더를 수정한 뒤 다시 컴파일하는 것을 잊지 마세요!
+`inPosition`와 `inColor` 변수는 *정점 어트리뷰트(vertex attribute)*입니다. 이는 정점 버퍼에 명시된 정점별 속성이며, 기존처럼 위치와 속성 데이터 입니다. 정점 셰이더를 수정한 뒤 다시 컴파일하는 것을 잊지 마세요!
 
-`fragColor`처럼, `layout(location = x)`는 입력에 대해 나중에 참조하기 위한 인덱스를 할당하는 것입니다.
-예를들어 `dvec3`와 같은 64비트 벡터는 여러 *슬롯(slot)*을 사용한다는 사실을 중요하게 알아두셔야 합니다.
-이러한 경우 그 다음으로 오는 인덱스는 2 이상 큰 인덱스여야 합니다:
+`fragColor`처럼, `layout(location = x)`는 입력에 대해 나중에 참조하기 위한 인덱스를 할당하는 것입니다. 예를들어 `dvec3`와 같은 64비트 벡터는 여러 *슬롯(slot)*을 사용한다는 사실을 중요하게 알아두셔야 합니다. 이러한 경우 그 다음으로 오는 인덱스는 2 이상 큰 인덱스여야 합니다:
 
 ```glsl
 layout(location = 0) in dvec3 inPosition;
@@ -39,9 +33,7 @@ layout(location = 2) in vec3 inColor;
 
 ## 정점 데이터
 
-정점 데이터를 셰이더 코드에서 우리 프로그램의 배열로 옮길 예정입니다.
-먼저 벡터와 행렬 같은 선형대수 관련 자료형을 제공해 주는 GLM 라이브러를 include 하는 것 부터 시작합니다.
-이 자료형들을 사용해 위치와 색상 벡터를 명시할 것입니다.
+정점 데이터를 셰이더 코드에서 우리 프로그램의 배열로 옮길 예정입니다. 먼저 벡터와 행렬 같은 선형대수 관련 자료형을 제공해 주는 GLM 라이브러를 include 하는 것 부터 시작합니다. 이 자료형들을 사용해 위치와 색상 벡터를 명시할 것입니다.
 
 ```c++
 #include <glm/glm.hpp>
@@ -66,14 +58,11 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-이제 `Vertex` 구조체를 사용해 정점 데이터를 명시합니다.
-이전과 완전히 동일한 위치와 색상값을 사용하지만 이제는 정점에 대한 배열 하나에 모두 포함해 두었습니다.
-이러한 방식을 정점 어트리뷰트의 *interleving*이라고 합니다.
+이제 `Vertex` 구조체를 사용해 정점 데이터를 명시합니다. 이전과 완전히 동일한 위치와 색상값을 사용하지만 이제는 정점에 대한 배열 하나에 모두 포함해 두었습니다. 이러한 방식을 정점 어트리뷰트의 *interleving*이라고 합니다.
 
-## 바인딩 명세(Binding descriptions)
+## 바인딩 기술자(Binding descriptions)
 
-다음 단계는 GPU 메모리에 업로드된 데이터를 정점 셰이더로 어떻게 전달할지를 Vulkan에 알려주는 것입니다.
-이러한 정보를 전달하기 위한 두 종류의 구조체가 필요합니다.
+다음 단계는 GPU 메모리에 업로드된 데이터를 정점 셰이더로 어떻게 전달할지를 Vulkan에 알려주는 것입니다. 이러한 정보를 전달하기 위한 두 종류의 구조체가 필요합니다.
 
 첫 구조체는 `VkVertexInputBindingDescription`이고 `Vertex` 구조체에 멤버 함수를 추가하여 적절한 데이터를 생성할 수 있도록 합니다.
 
@@ -90,8 +79,7 @@ struct Vertex {
 };
 ```
 
-정점 바인딩은 정점에 대해 얼만큼의 데이터를 메모리로부터 로드할 것인지를 명시합니다.
-각 데이터별 바이트의 크기, 그리고 각 정점에 대해 다음 데이터로 넘어갈지, 아니면 다음 인스턴스에서 널어갈지를 포함합니다.
+정점 바인딩은 정점에 대해 얼만큼의 데이터를 메모리로부터 로드할 것인지를 명시합니다. 각 데이터별 바이트의 크기, 그리고 각 정점에 대해 다음 데이터로 넘어갈지, 아니면 다음 인스턴스에서 널어갈지를 포함합니다.
 
 ```c++
 VkVertexInputBindingDescription bindingDescription{};
@@ -100,20 +88,16 @@ bindingDescription.stride = sizeof(Vertex);
 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
 ```
 
-우리의 정점별 데이터는 하나의 배열에 포장되어(packed) 있으니 바인딩은 하나만 있으면 됩니다.
-`binding` 매개변수는 바인딩 배열의 바인딩할 인덱스를 명시합니다.
-`stride` 매개변수는 한 요소와 다음 요소 사이의 바이트 크기입니다.
-`inputRate` 매개변수는 아래와 같은 값 중 하나를 가집니다:
+우리의 정점별 데이터는 하나의 배열에 포장되어(packed) 있으니 바인딩은 하나만 있으면 됩니다. `binding` 매개변수는 바인딩 배열의 바인딩할 인덱스를 명시합니다. `stride` 매개변수는 한 요소와 다음 요소 사이의 바이트 크기입니다. `inputRate` 매개변수는 아래와 같은 값 중 하나를 가집니다:
 
 * `VK_VERTEX_INPUT_RATE_VERTEX`: 각 정점에 대해 다음 데이터 요소로 이동함
 * `VK_VERTEX_INPUT_RATE_INSTANCE`: 각 인스턴스에 대해 다음 데이터 요소로 넘어감
 
 인스턴스 렌더링을 한 것은 아니므로 정점별 데이터로 해 두겠습니다.
 
-## 어트리뷰트 명세
+## 어트리뷰트 기술자
 
-정점 입력을 처리하는 방법을 설명하기 위한 두 번째 구조체는 `VkVertexInputAttributeDescription`입니다. 
-이 구조체를 채우기 위해 또 다른 헬퍼 함수를 `Vertex`에 추가하겠습니다.
+정점 입력을 처리하는 방법을 설명하기 위한 두 번째 구조체는 `VkVertexInputAttributeDescription`입니다. 이 구조체를 채우기 위해 또 다른 헬퍼 함수를 `Vertex`에 추가하겠습니다.
 
 ```c++
 #include <array>
@@ -127,9 +111,7 @@ static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions
 }
 ```
 
-함수 프로토타입에서 알 수 있듯, 두 개의 구조체를 사용할 것입니다.
-어트리뷰트 명세를 위한 구조체는 바인딩 명세를 활용해 얻어진 정점 데이터 덩어리로부터 정점 어트리뷰트를 어떻게 추출할지를 알려줍니다.
-우리는 위치와 색상 두 개의 어트리뷰트가 있으니 두 개의 어트리뷰트 명세 구조체가 필요합니다.
+함수 프로토타입에서 알 수 있듯, 두 개의 구조체를 사용할 것입니다. 어트리뷰트 기술자를 위한 구조체는 바인딩 기술자를 활용해 얻어진 정점 데이터 덩어리로부터 정점 어트리뷰트를 어떻게 추출할지를 알려줍니다. 우리는 위치와 색상 두 개의 어트리뷰트가 있으니 두 개의 어트리뷰트 기술자 구조체가 필요합니다.
 
 ```c++
 attributeDescriptions[0].binding = 0;
@@ -138,32 +120,22 @@ attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
 attributeDescriptions[0].offset = offsetof(Vertex, pos);
 ```
 
-`binding` 매개변수는 어떤 바인딩에서 정점별 데이터를 얻어올 것인지를 Vulkan에 알려줍니다.
-`location` 매개변수는 정점 셰이더의 `location` 지시자에 대한 참조입니다.
-정점 셰이더의 location `0`에 대한 입력이 위치값에 해당하고, 이는 두 개의 32비트 부동소수점으로 이루어져 있습니다.
+`binding` 매개변수는 어떤 바인딩에서 정점별 데이터를 얻어올 것인지를 Vulkan에 알려줍니다. `location` 매개변수는 정점 셰이더의 `location` 지시자에 대한 참조입니다.정점 셰이더의 location `0`에 대한 입력이 위치값에 해당하고, 이는 두 개의 32비트 부동소수점으로 이루어져 있습니다.
 
-`format` 매개변수는 어트리뷰트의 데이터 자료형을 알려줍니다.
-약간 헷갈리는 점은 이러한 포맷이 색상 포맷과 동일한 열거자로 명시된다는 점입니다.
-아래와 같은 셰이더 자료형에 따르는 포맷이 사용됩니다:
+`format` 매개변수는 어트리뷰트의 데이터 자료형을 알려줍니다. 약간 헷갈리는 점은 이러한 포맷이 색상 포맷과 동일한 열거자로 명시된다는 점입니다. 아래와 같은 셰이더 자료형에 따르는 포맷이 사용됩니다:
 
 * `float`: `VK_FORMAT_R32_SFLOAT`
 * `vec2`: `VK_FORMAT_R32G32_SFLOAT`
 * `vec3`: `VK_FORMAT_R32G32B32_SFLOAT`
 * `vec4`: `VK_FORMAT_R32G32B32A32_SFLOAT`
 
-보다시피 색상 채널의 수와 일치하는 요소 숫자를 갖는 셰이더 자료형의 포맷을 사용해야 합니다.
-셰이더의 요소 숫자보다 더 많은 채널을 사용하는 것도 허용되지만 남는 값은 무시됩니다.
-요소 숫자보다 채널 수가 적으면 BGA 요소의 기본값인 `(0, 0, 1)`가 사용됩니다. 
-색상 타입인 (`SFLOAT`, `UINT`, `SINT`)와 비트 너비 또한 셰이더 입력의 자료형과 일치해야 합니다.
-예시는 다음과 같습니다:
+보다시피 색상 채널의 수와 일치하는 요소 숫자를 갖는 셰이더 자료형의 포맷을 사용해야 합니다. 셰이더의 요소 숫자보다 더 많은 채널을 사용하는 것도 허용되지만 남는 값은 무시됩니다. 요소 숫자보다 채널 수가 적으면 BGA 요소의 기본값인 `(0, 0, 1)`가 사용됩니다. 색상 타입인 (`SFLOAT`, `UINT`, `SINT`)와 비트 너비 또한 셰이더 입력의 자료형과 일치해야 합니다. 예시는 다음과 같습니다:
 
 * `ivec2`: `VK_FORMAT_R32G32_SINT`, 32비트 부호 있는 정수 2개 요소를 갖는 벡터
 * `uvec4`: `VK_FORMAT_R32G32B32A32_UINT`, 32비트 부호 없는 정수 4개의 요소를 갖는 벡터
 * `double`: `VK_FORMAT_R64_SFLOAT`, 64비트 double 부동소수점
 
-`format` 매개변수는 어트리뷰트 데이터의 바이트 크기를 암시적으로 정의하며 `offset` 매개변수는 정점별 데이터를 읽어올 시작 바이트를 명시합니다.
-바인딩은 한 번에 하나의 `Vertex`를 읽어오며 위치 어트리뷰트(`pos`)는 `0` 바이트, 즉 처음부터 읽어옵니다. 
-`offsetof` 매크로를 사용하면 자동으로 계산됩니다.
+`format` 매개변수는 어트리뷰트 데이터의 바이트 크기를 암시적으로 정의하며 `offset` 매개변수는 정점별 데이터를 읽어올 시작 바이트를 명시합니다. 바인딩은 한 번에 하나의 `Vertex`를 읽어오며 위치 어트리뷰트(`pos`)는 `0` 바이트, 즉 처음부터 읽어옵니다.  `offsetof` 매크로를 사용하면 자동으로 계산됩니다.
 
 ```c++
 attributeDescriptions[1].binding = 0;
@@ -176,8 +148,7 @@ attributeDescriptions[1].offset = offsetof(Vertex, color);
 
 ## 파이프라인 정점 입력
 
-이제 `createGraphicsPipeline` 안의 구조체를 참조하여 정점 데이터를 위와 같은 포맷으로 받아들이도록 그래픽스 파이프라인을 설정해야 합니다.
-`vertexInputInfo` 구조체를 찾아 두 명세를 참조하도록 수정합니다:
+이제 `createGraphicsPipeline` 안의 구조체를 참조하여 정점 데이터를 위와 같은 포맷으로 받아들이도록 그래픽스 파이프라인을 설정해야 합니다. `vertexInputInfo` 구조체를 찾아 두 기술자를 참조하도록 수정합니다:
 
 ```c++
 auto bindingDescription = Vertex::getBindingDescription();
@@ -189,9 +160,7 @@ vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
 ```
 
-이제 이 파이프라인은 `vertices` 컨테이너의 정점 데이터를 받아들이고 정점 셰이더로 넘길 준비가 되었습니다.
-검증 레이어를 활성화 한 상태에서 프로그램을 실행하면 바인딩된 정점 버퍼가 없다는 오류 메시지를 보시게 될겁니다.
-다음 단계는 정점 버퍼를 만들고 정점 데이터를 버퍼에 넘겨 GPU가 접근할 수 있도록 하는 것입니다.
+이제 이 파이프라인은 `vertices` 컨테이너의 정점 데이터를 받아들이고 정점 셰이더로 넘길 준비가 되었습니다. 검증 레이어를 활성화 한 상태에서 프로그램을 실행하면 바인딩된 정점 버퍼가 없다는 오류 메시지를 보시게 될겁니다. 다음 단계는 정점 버퍼를 만들고 정점 데이터를 버퍼에 넘겨 GPU가 접근할 수 있도록 하는 것입니다.
 
 [C++ code](/code/18_vertex_input.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /
diff --git a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
index 62071837..19ba00b2 100644
--- a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
+++ b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
@@ -1,9 +1,6 @@
 ## 개요
 
-Vulkan에서 버퍼는 그래픽 카드가 읽을 수 있는, 임의의 데이터를 저장하는 메모리 영역을 의미합니다.
-이 챕터에서의 예시처럼 정점 데이터를 저장하는 데 사용될 수도 있지만 나중 챕터에서 살펴볼 것인데 다른 용도로도 자주 사용됩니다.
-지금까지 살펴본 Vulkan 객체와는 다르게 버퍼는 스스로 메모리를 할당하지 않습니다. 
-지금까지 살펴본 것처럼 Vulkan API는 프로그래머에게 거의 모든 제어권을 주는데, 메모리 관리 또한 이에 포함됩니다.
+Vulkan에서 버퍼는 그래픽 카드가 읽을 수 있는, 임의의 데이터를 저장하는 메모리 영역을 의미합니다. 이 챕터에서의 예시처럼 정점 데이터를 저장하는 데 사용될 수도 있지만 나중 챕터에서 살펴볼 것인데 다른 용도로도 자주 사용됩니다. 지금까지 살펴본 Vulkan 객체와는 다르게 버퍼는 스스로 메모리를 할당하지 않습니다. 지금까지 살펴본 것처럼 Vulkan API는 프로그래머에게 거의 모든 제어권을 주는데, 메모리 관리 또한 이에 포함됩니다.
 
 ## 버퍼 생성
 
@@ -42,29 +39,23 @@ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
 bufferInfo.size = sizeof(vertices[0]) * vertices.size();
 ```
 
-첫 번째 필드는 `size`이고, 버퍼의 바이트 단위 크기를 명시합니다. 
-정점 데이터의 바이트 단위 크기를 계산하는 것은 `sizeof`를 사용하면 됩니다.
+첫 번째 필드는 `size`이고, 버퍼의 바이트 단위 크기를 명시합니다. 정점 데이터의 바이트 단위 크기를 계산하는 것은 `sizeof`를 사용하면 됩니다.
 
 ```c++
 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
 ```
 
-두 번째 필드는 `usage`인데 버퍼의 데이터가 어떤 목적으로 사용될지를 알려줍니다. 
-bitwise OR를 사용해 목적을 여러개 명기하는것도 가능합니다. 
-우리의 사용 목적은 정점 버퍼이며 다른 타입에 대해서는 다른 챕터에서 알아보겠습니다.
+두 번째 필드는 `usage`인데 버퍼의 데이터가 어떤 목적으로 사용될지를 알려줍니다. bitwise OR를 사용해 목적을 여러개 명기하는것도 가능합니다. 우리의 사용 목적은 정점 버퍼이며 다른 타입에 대해서는 다른 챕터에서 알아보겠습니다.
 
 ```c++
 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
 ```
 
-스왑 체인의 이미지처럼 버퍼는 특정 큐 패밀리에 의해 소유되거나 여러 큐 패밀리에서 공유될 수 있습니다.
-버퍼는 그래픽스 큐에서만 활용될 예정이므로 독점(exclusive) 접근으로 두겠습니다.
+스왑 체인의 이미지처럼 버퍼는 특정 큐 패밀리에 의해 소유되거나 여러 큐 패밀리에서 공유될 수 있습니다. 버퍼는 그래픽스 큐에서만 활용될 예정이므로 독점(exclusive) 접근으로 두겠습니다.
 
-`flag` 매개변수는 sparse한 버퍼 메모리를 설정하기 위해 사용되는데, 지금은 사용하지 않습니다. 
-기본값인 `0`으로 둘 것입니다.
+`flag` 매개변수는 sparse한 버퍼 메모리를 설정하기 위해 사용되는데, 지금은 사용하지 않습니다. 기본값인 `0`으로 둘 것입니다.
 
-이제 `vkCreateBuffer`로 버퍼를 만들 수 있습니다. 
-버퍼 핸들을 저장할 `vertexBuffer`를 클래스의 멤버로 정의합니다.
+이제 `vkCreateBuffer`로 버퍼를 만들 수 있습니다. 버퍼 핸들을 저장할 `vertexBuffer`를 클래스의 멤버로 정의합니다.
 
 ```c++
 VkBuffer vertexBuffer;
@@ -98,8 +89,7 @@ void cleanup() {
 
 ## 메모리 요구사항
 
-버퍼가 생성되었지만 아직 실제로 메모리가 할당된 것은 아닙니다. 
-버퍼의 메모리 할당을 위한 첫 단계는 `vkGetBufferMemoryRequirements`라는 이름의 함수로 메모리 요구사항을 질의하는 것입니다.
+버퍼가 생성되었지만 아직 실제로 메모리가 할당된 것은 아닙니다. 버퍼의 메모리 할당을 위한 첫 단계는 `vkGetBufferMemoryRequirements`라는 이름의 함수로 메모리 요구사항을 질의하는 것입니다.
 
 ```c++
 VkMemoryRequirements memRequirements;
@@ -112,10 +102,7 @@ vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
 * `alignment`: `bufferInfo.usage`와 `bufferInfo.flags`에 의해 결정되는, 메모리 영역에서 버퍼가 시작되는 바이트 오프셋(offset)
 * `memoryTypeBits`: 버퍼에 적합한 메모리 타입의 비트 필드
 
-그래픽 카드는 할당할 수 있는 서로 다른 종류의 메모리가 있습니다. 
-각 메모리 타입은 허용 가능한 연산과 성능 특성이 다릅니다. 
-버퍼의 요구사항과 우리 응용 프로그램의 요구사항을 결합하여 적합한 메모리 타입을 결정해야 합니다. 
-이러한 목적을 위해서 `findMemoryType` 함수를 새로 만듭시다.
+그래픽 카드는 할당할 수 있는 서로 다른 종류의 메모리가 있습니다. 각 메모리 타입은 허용 가능한 연산과 성능 특성이 다릅니다. 버퍼의 요구사항과 우리 응용 프로그램의 요구사항을 결합하여 적합한 메모리 타입을 결정해야 합니다. 이러한 목적을 위해서 `findMemoryType` 함수를 새로 만듭시다.
 
 ```c++
 uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
@@ -130,9 +117,7 @@ VkPhysicalDeviceMemoryProperties memProperties;
 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
 ```
 
-`VkPhysicalDeviceMemoryProperties` 구조체는 `memoryTypes`와 `memoryHeaps` 배열을 가지고 있습니다. 
-메모리 힙은 VRAM, 그리고 VRAM이 부족할 때 사용하는 RAM의 스왑 공간 같은 메모리 자원입니다. 이 힙 안에 여러 메모리 타입이 존재하게 됩니다. 
-지금은 메모리 타입만 사용하고 그 메모리가 어떤 힙에 존재하는 것인지 신경쓰지 않을 것이지만 그에 따라 성능에 영향이 있을 수 있다는 것은 예상하실 수 있을겁니다.
+`VkPhysicalDeviceMemoryProperties` 구조체는 `memoryTypes`와 `memoryHeaps` 배열을 가지고 있습니다. 메모리 힙은 VRAM, 그리고 VRAM이 부족할 때 사용하는 RAM의 스왑 공간 같은 메모리 자원입니다. 이 힙 안에 여러 메모리 타입이 존재하게 됩니다. 지금은 메모리 타입만 사용하고 그 메모리가 어떤 힙에 존재하는 것인지 신경쓰지 않을 것이지만 그에 따라 성능에 영향이 있을 수 있다는 것은 예상하실 수 있을겁니다.
 
 먼저 버퍼에 적합한 메모리 타입을 찾습니다:
 
@@ -146,15 +131,9 @@ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
 throw std::runtime_error("failed to find suitable memory type!");
 ```
 
-`typeFilter` 매개변수는 적합한 메모리 타입을 명시하기 위한 비트 필드입니다. 
-즉 적합한 메모리 타입에 대한 인덱스는 그냥 반복문을 돌면서 해당 비트가 1인지를 확인하여 얻을 수 있습니다.
+`typeFilter` 매개변수는 적합한 메모리 타입을 명시하기 위한 비트 필드입니다. 즉 적합한 메모리 타입에 대한 인덱스는 그냥 반복문을 돌면서 해당 비트가 1인지를 확인하여 얻을 수 있습니다.
 
-하지만 우리는 정점 버퍼를 위한 적합한 메모리 타입에만 관심이 있는 것이 아닙니다. 
-정점 데이터를 해당 메모리에 쓸 수 있어야 합니다. 
-`memoryTypes` 배열은 힙과 각 메모리 타입의 속성을 명시하는 `VkMemoryType` 구조체의 배열입니다. 
-속성은 메모리의 특수 기능을 정의하는데 예를 들자면 CPU에서 값을 쓸 수 있도록 맵핑(map)할 수 있는지 여부와 같은 것입니다. 
-이 속성은 `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`로 명시되는데, 추가적으로 `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` 속성도 필요합니다. 
-그 이유에 대해서는 메모리 맵핑을 할 때 알게 될 겁니다.
+하지만 우리는 정점 버퍼를 위한 적합한 메모리 타입에만 관심이 있는 것이 아닙니다. 정점 데이터를 해당 메모리에 쓸 수 있어야 합니다. `memoryTypes` 배열은 힙과 각 메모리 타입의 속성을 명시하는 `VkMemoryType` 구조체의 배열입니다. 속성은 메모리의 특수 기능을 정의하는데 예를 들자면 CPU에서 값을 쓸 수 있도록 맵핑(map)할 수 있는지 여부와 같은 것입니다. 이 속성은 `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`로 명시되는데, 추가적으로 `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` 속성도 필요합니다. 그 이유에 대해서는 메모리 맵핑을 할 때 알게 될 겁니다.
 
 이제 반복문을 수정해 이러한 속성에 대한 지원 여부를 확인합니다:
 
@@ -166,8 +145,7 @@ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
 }
 ```
 
-필요한 속성이 하나 이상일 수 있으므로 bitwise AND의 결과가 0이 아닌 것만을 확인하면 안되고 해당하는 비트 필드가 필요한 속성과 동일한지 확인해야 합니다. 
-버퍼에 적합한 메모리 타입이 있고 이러한 속성들을 가지고 있으면 해당 인덱스를 반환하고, 아니면 예외를 발생시키도록 합니다.
+필요한 속성이 하나 이상일 수 있으므로 bitwise AND의 결과가 0이 아닌 것만을 확인하면 안되고 해당하는 비트 필드가 필요한 속성과 동일한지 확인해야 합니다. 버퍼에 적합한 메모리 타입이 있고 이러한 속성들을 가지고 있으면 해당 인덱스를 반환하고, 아니면 예외를 발생시키도록 합니다.
 
 ## 메모리 할당
 
@@ -180,8 +158,7 @@ allocInfo.allocationSize = memRequirements.size;
 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
 ```
 
-이제 단지 크기와 타입을 명시하면 되는데, 모두 정점 버퍼의 메모리 요구사항과 원하는 속성으로부터 도출되는 값입니다. 
-메모리에 대한 핸들을 저장하기 위한 클래스 멤버를 만들고 `vkAllocateMemory`를 통해 메모리를 할당받습니다.
+이제 단지 크기와 타입을 명시하면 되는데, 모두 정점 버퍼의 메모리 요구사항과 원하는 속성으로부터 도출되는 값입니다. 메모리에 대한 핸들을 저장하기 위한 클래스 멤버를 만들고 `vkAllocateMemory`를 통해 메모리를 할당받습니다.
 
 ```c++
 VkBuffer vertexBuffer;
@@ -200,8 +177,7 @@ if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUC
 vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
 ```
 
-첫 세 매개변수는 특별히 설명할 것이 없고, 네 번째 매개변수는 메모리 영역에서의 오프셋입니다. 
-지금 메모리는 정점 버퍼만을 위해 할당받은 것이므로 오프셋은 `0`입니다. 
+첫 세 매개변수는 특별히 설명할 것이 없고, 네 번째 매개변수는 메모리 영역에서의 오프셋입니다. 지금 메모리는 정점 버퍼만을 위해 할당받은 것이므로 오프셋은 `0`입니다. 
 오프셋이 0이 아니면, `memRequirements.alignment`를 사용해 분할 가능해야만 합니다.
 
 물론 C++에서의 동적 메모리 할당처럼 이 메모리는 어떤 시점에 해제되어야만 합니다. 
@@ -217,19 +193,14 @@ void cleanup() {
 
 ## 정점 버퍼 채우기
 
-이제 정점 데이터를 버퍼에 복사할 시간입니다. 
-이는 CPU가 접근 가능한 메모리에 `vkMapMemory`로 [버퍼 메모리 맵핑](https://en.wikipedia.org/wiki/Memory-mapped_I/O)을 함으로써 수행합니다.
+이제 정점 데이터를 버퍼에 복사할 시간입니다. 이는 CPU가 접근 가능한 메모리에 `vkMapMemory`로 [버퍼 메모리 맵핑](https://en.wikipedia.org/wiki/Memory-mapped_I/O)을 함으로써 수행합니다.
 
 ```c++
 void* data;
 vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
 ```
 
-이 함수는 오프셋과 크기로 명시된 특정 메모리 리소스 영역에 접근이 가능하도록 해 줍니다. 
-여기서 오프셋과 크기는 각각 `0`과 `bufferInfo.size`입니다. 
-`VK_WHOLE_SIZE`와 같은 특수한 값으로 전체 메모리를 맵핑하는 것도 가능합니다. 
-끝에서 두 번째 매개변수는 플래그를 명시하기 위해 사용될 수도 있지만 현재 API에는 아직 사용 가능한 것이 없습니다. 따라서 값은 `0`이어야만 합니다. 
-마지막 매개변수는 맵핑된 메모리에 대한 포인터 출력입니다.
+이 함수는 오프셋과 크기로 명시된 특정 메모리 리소스 영역에 접근이 가능하도록 해 줍니다. 여기서 오프셋과 크기는 각각 `0`과 `bufferInfo.size`입니다. `VK_WHOLE_SIZE`와 같은 특수한 값으로 전체 메모리를 맵핑하는 것도 가능합니다. 끝에서 두 번째 매개변수는 플래그를 명시하기 위해 사용될 수도 있지만 현재 API에는 아직 사용 가능한 것이 없습니다. 따라서 값은 `0`이어야만 합니다. 마지막 매개변수는 맵핑된 메모리에 대한 포인터 출력입니다.
 
 ```c++
 void* data;
@@ -238,23 +209,18 @@ vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
 vkUnmapMemory(device, vertexBufferMemory);
 ```
 
-이제 `memcpy`로 정점 데이터를 맵핑된 메모리에 복사하고 `vkUnmapMemory`를 사용해 다시 언맵핑합니다. 
-안타깝게도 드라이버가 즉시 버퍼 메모리에 복사를 수행하지 못할 수도 있습니다. 예를 들어 캐싱(chching) 떄문에요. 
-또한 버퍼에의 쓰기 작업이 아직 맵핑된 메모리에 보이지 않을 수도 있습니다. 
-이러한 문제를 처리하기 위한 두 가지 방법이 있습니다:
+이제 `memcpy`로 정점 데이터를 맵핑된 메모리에 복사하고 `vkUnmapMemory`를 사용해 다시 언맵핑합니다. 안타깝게도 드라이버가 즉시 버퍼 메모리에 복사를 수행하지 못할 수도 있습니다. 예를 들어 캐싱(chching) 때문에요. 또한 버퍼에의 쓰기 작업이 아직 맵핑된 메모리에 보이지 않을 수도 있습니다. 이러한 문제를 처리하기 위한 두 가지 방법이 있습니다:
 
 * `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`로 명시된 호스트에 일관성(coherent) 메모리 힙을 사용함
 * 맵핑된 메모리에 쓰기를 수행한 후 `vkFlushMappedMemoryRanges`를 호출하고, 맵핑된 메모리를 읽기 전에 `vkInvalidateMappedMemoryRanges` 를 호출함
 
-우리는 첫 번째 방법을 사용했는데 이렇게 하면 맵핑된 메모리의 내용이 할당된 메모리와 항상 동일한 것이 보장됩니다. 
-이러한 방식은 명시적인 플러싱(flushing)에 비해 약간의 성능 손해가 있다는 것을 아셔야 하지만, 크게 상관 없습니다. 왜 그런지는 다음 챕터에서 살펴보도록 하겠습니다.
+우리는 첫 번째 방법을 사용했는데 이렇게 하면 맵핑된 메모리의 내용이 할당된 메모리와 항상 동일한 것이 보장됩니다. 이러한 방식은 명시적인 플러싱(flushing)에 비해 약간의 성능 손해가 있다는 것을 아셔야 하지만, 크게 상관 없습니다. 왜 그런지는 다음 챕터에서 살펴보도록 하겠습니다.
 
 메모리 영역을 플러싱하거나 일관성 메모리 힙을 사용한다는 이야기는 드라이버가 버퍼에 대한 쓰기 의도를 파악하게 된다는 것이지만 아직 실제로 GPU가 그 메모리 영역을 볼 수 있다는 이야기는 아닙니다. 실제로 데이터가 GPU로 전송되는 것은 백그라운드에서 진행되며 [명세](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-submission-host-writes)에서는 단순히 다음 `vkQueueSubmit` 호출 이전에 완료가 보장된다는 것만 정의하고 있습니다.
 
 ## 정점 버퍼 바인딩
 
-이제 남은 것은 렌더링 연산에서 정점 버퍼를 바인딩 하는 것입니다.
-`recordCommandBuffer` 함수를 확장하여 이러한 작업을 수행하도록 합니다.
+이제 남은 것은 렌더링 연산에서 정점 버퍼를 바인딩 하는 것입니다. `recordCommandBuffer` 함수를 확장하여 이러한 작업을 수행하도록 합니다.
 
 ```c++
 vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
@@ -266,10 +232,7 @@ vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
 vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0);
 ```
 
-`vkCmdBindVertexBuffers` 함수는 정점 버퍼를 이전 챕터에서 설정한 바인딩에 바인딩합니다. 
-명령버퍼를 제외한 첫 두 매개변수는 오프셋과 정점 버퍼의 바인딩 숫자를 명시합니다. 
-마지막 두 매개변수는 바인딩할 정점 버퍼의 배열과 정점 데이터를 읽기 시작할 바이트 오프셋을 명시합니다. 
-또한 `vkCmdDraw` 호출에서도 하드코딩된 숫자 `3`을 사용하는 대신 버퍼의 정점 개수를 넘겨주도록 수정합니다.
+`vkCmdBindVertexBuffers` 함수는 정점 버퍼를 이전 챕터에서 설정한 바인딩에 바인딩합니다. 명령버퍼를 제외한 첫 두 매개변수는 오프셋과 정점 버퍼의 바인딩 숫자를 명시합니다. 마지막 두 매개변수는 바인딩할 정점 버퍼의 배열과 정점 데이터를 읽기 시작할 바이트 오프셋을 명시합니다. 또한 `vkCmdDraw` 호출에서도 하드코딩된 숫자 `3`을 사용하는 대신 버퍼의 정점 개수를 넘겨주도록 수정합니다.
 
 이제 프로그램을 실행하면 익숙한 삼각형을 다시 볼 수 있습니다:
 
diff --git a/kr/04_Vertex_buffers/02_Staging_buffer.md b/kr/04_Vertex_buffers/02_Staging_buffer.md
index bb82f470..0205f658 100644
--- a/kr/04_Vertex_buffers/02_Staging_buffer.md
+++ b/kr/04_Vertex_buffers/02_Staging_buffer.md
@@ -1,19 +1,12 @@
 ## 개요
 
-지금 만든 정점 버퍼는 잘 동작하지만 CPU에서 접근이 가능하도록 선택한 메모리 타입이 그래픽 카드에서 읽기에는 최적화된 메모리 타입은 아닐 수 있습니다. 
-가장 적합한 메모리는 `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` 플래그를 가지고 있고 대개는 대상 그래픽 카드에 대해 CPU에서는 접근이 불가능합니다. 
-이 챕터에서는 두 정점 버퍼를 만들 것입니다.
-하나는 *스테이징 버퍼(staging buffer)*로 CPU에서 접근 가능하여 정점 배열을 넣을 수 있으며 다른 하나는 장치의 로컬 메모리에 있는 정점 버퍼입니다. 
-그러고 나서 버퍼 복사 명령을 사용해 스테이징 버퍼에서 실제 정점 버퍼로 데이터를 옮길 것입니다.
+지금 만든 정점 버퍼는 잘 동작하지만 CPU에서 접근이 가능하도록 선택한 메모리 타입이 그래픽 카드에서 읽기에는 최적화된 메모리 타입은 아닐 수 있습니다. 가장 적합한 메모리는 `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` 플래그를 가지고 있고 대개는 대상 그래픽 카드에 대해 CPU에서는 접근이 불가능합니다. 이 챕터에서는 두 정점 버퍼를 만들 것입니다. 하나는 *스테이징 버퍼(staging buffer)*로 CPU에서 접근 가능하여 정점 배열을 넣을 수 있으며 다른 하나는 장치의 로컬 메모리에 있는 정점 버퍼입니다. 그러고 나서 버퍼 복사 명령을 사용해 스테이징 버퍼에서 실제 정점 버퍼로 데이터를 옮길 것입니다.
 
 ## 전송 큐(Transfer queue)
 
-버퍼 복사 맹령은 전송 연산을 지원하는 큐 패밀리가 필요하고 이는 `VK_QUEUE_TRANSFER_BIT`로 표기됩니다. 
-좋은 소식은 `VK_QUEUE_GRAPHICS_BIT`이나 `VK_QUEUE_COMPUTE_BIT` 기능이 있는 큐 패밀리는 암시적으로 `VK_QUEUE_TRANSFER_BIT` 연산을 지원한다는 것입니다. 
-이러한 경우 `queueFlags`에 이를 명시적으로 표시하도록 구현되는 것이 강제되지는 않습니다.
+버퍼 복사 맹령은 전송 연산을 지원하는 큐 패밀리가 필요하고 이는 `VK_QUEUE_TRANSFER_BIT`로 표기됩니다. 좋은 소식은 `VK_QUEUE_GRAPHICS_BIT`이나 `VK_QUEUE_COMPUTE_BIT` 기능이 있는 큐 패밀리는 암시적으로 `VK_QUEUE_TRANSFER_BIT` 연산을 지원한다는 것입니다. 이러한 경우 `queueFlags`에 이를 명시적으로 표시하도록 구현되는 것이 강제되지는 않습니다.
 
-도전을 원하신다면 전송 연산을 위해 또 다른 큐 패밀리를 사용하도록 해 보십시오. 
-이렇게 하려면 다음과 같은 추가적인 수정이 필요합니다:
+도전을 원하신다면 전송 연산을 위해 또 다른 큐 패밀리를 사용하도록 해 보십시오. 이렇게 하려면 다음과 같은 추가적인 수정이 필요합니다:
 
 * `QueueFamilyIndices`와 `findQueueFamilies`를 수정하여 `VK_QUEUE_TRANSFER_BIT` 비트를 갖지만 `VK_QUEUE_GRAPHICS_BIT`는 갖지 않는 큐 패밀리를 명시적으로 탐색합니다.
 * `createLogicalDevice`를 수정하여 전송 큐에 대한 핸들을 요청하도록 합니다.
@@ -25,8 +18,7 @@
 
 ## 버퍼 생성 추상화
 
-이 장에서는 여러 버퍼를 생성할 것이므로, 버퍼 생성에 관한 헬퍼 함수를 만드는 것이 좋을 것 같습니다.
-새로운 함수인 `createBuffer`를 만들고 `createVertexBuffer`의 (맵핑을 제외한) 코드를 옮겨옵니다.
+이 장에서는 여러 버퍼를 생성할 것이므로, 버퍼 생성에 관한 헬퍼 함수를 만드는 것이 좋을 것 같습니다. 새로운 함수인 `createBuffer`를 만들고 `createVertexBuffer`의 (맵핑을 제외한) 코드를 옮겨옵니다.
 
 ```c++
 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
@@ -56,8 +48,7 @@ void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyF
 }
 ```
 
-버퍼의 크기와 메모리 속성, 사용 목적에 대한 매개변수를 만들어서 다른 종류의 버퍼를 만들 때도 사용 가능하도록 함수를 정의하는 것을 잊지 마세요. 
-마지막 두 매개변수는 핸들을 저장할 출력 변수입니다.
+버퍼의 크기와 메모리 속성, 사용 목적에 대한 매개변수를 만들어서 다른 종류의 버퍼를 만들 때도 사용 가능하도록 함수를 정의하는 것을 잊지 마세요. 마지막 두 매개변수는 핸들을 저장할 출력 변수입니다.
 
 이제 버퍼 생성과 메모리 할당 코드를 `createVertexBuffer`에서 제거하고 대신 `createBuffer`를 호출하면 됩니다:
 
@@ -101,9 +92,7 @@ void createVertexBuffer() {
 * `VK_BUFFER_USAGE_TRANSFER_SRC_BIT`: 메모리 전송 연산에서 소스(source)로 사용되는 버퍼
 * `VK_BUFFER_USAGE_TRANSFER_DST_BIT`: 메모리 전송 연산에서 목적지(destination)로 사용되는 버퍼
 
-`vertexBuffer`는 이제 장치 로컬인 메모리 타입에서 할당되고, 그로 인해 일반적으로 `vkMapMemory`는 사용할 수 없게 됩니다. 
-하지만 `stagingBuffer`에서 `vertexBuffer`로 데이터를 복사할 수는 있습니다. 
-우리가 이렇게 하려고 한다는 것을 `stagingBuffer`에는 전송 소스 플래그를, `vertexBuffer`에는 전송 목적지 플래그와 정점 버퍼 플래그를 사용해 알려주어야 합니다.
+`vertexBuffer`는 이제 장치 로컬인 메모리 타입에서 할당되고, 그로 인해 일반적으로 `vkMapMemory`는 사용할 수 없게 됩니다. 하지만 `stagingBuffer`에서 `vertexBuffer`로 데이터를 복사할 수는 있습니다. 우리가 이렇게 하려고 한다는 것을 `stagingBuffer`에는 전송 소스 플래그를, `vertexBuffer`에는 전송 목적지 플래그와 정점 버퍼 플래그를 사용해 알려주어야 합니다.
 
 이제 한 버퍼에서 다른 버퍼로 복사를 하는 `copyBuffer` 함수를 만듭니다.
 
@@ -113,10 +102,7 @@ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
 }
 ```
 
-메모리 전송 연산은 그리기와 마찬가지로 명령 버퍼를 통해 실행됩니다. 
-그러므로 먼저 임시 명령 버퍼를 할당해야 합니다.
-이렇게 임시 사용되는 버퍼에 대한 별도의 명령 풀을 만들면 메모리 할당 최적화가 수행될 수 있습니다. 
-지금의 경우 명령 풀 생성시에 `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` 플래그를 사용해야 합니다.
+메모리 전송 연산은 그리기와 마찬가지로 명령 버퍼를 통해 실행됩니다. 그러므로 먼저 임시 명령 버퍼를 할당해야 합니다. 이렇게 임시 사용되는 버퍼에 대한 별도의 명령 풀을 만들면 메모리 할당 최적화가 수행될 수 있습니다. 지금의 경우 명령 풀 생성시에 `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` 플래그를 사용해야 합니다.
 
 ```c++
 void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
@@ -141,8 +127,7 @@ beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
 vkBeginCommandBuffer(commandBuffer, &beginInfo);
 ```
 
-명령 버퍼를 한 번만 사용하고 복사 연산 실행이 끝나서 함수가 반환될 때까지 대기하도록 할 것입니다. 
-이러한 의도를 `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`를 사용해 드라이버에게 알려주는 것이 좋습니다.
+명령 버퍼를 한 번만 사용하고 복사 연산 실행이 끝나서 함수가 반환될 때까지 대기하도록 할 것입니다. 이러한 의도를 `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`를 사용해 드라이버에게 알려주는 것이 좋습니다.
 
 ```c++
 VkBufferCopy copyRegion{};
@@ -152,17 +137,13 @@ copyRegion.size = size;
 vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
 ```
 
-버퍼 내용은 `vkCmdCopyBuffer` 명령을 통해 전송됩니다. 
-소스와 목적지 버퍼, 그리고 복사할 영역에 대한 배열을 인자로 받습니다. 
-영역은 `VkBufferCopy`로 정의되며 소스 버퍼의 오프셋, 목적지 버퍼의 오프셋, 크기로 구성됩니다. 
-`vkMapMemory` 명령과는 달리 여기서는 `VK_WHOLE_SIZE`로 명시하는 것은 불가능합니다.
+버퍼 내용은 `vkCmdCopyBuffer` 명령을 통해 전송됩니다. 소스와 목적지 버퍼, 그리고 복사할 영역에 대한 배열을 인자로 받습니다. 영역은 `VkBufferCopy`로 정의되며 소스 버퍼의 오프셋, 목적지 버퍼의 오프셋, 크기로 구성됩니다. `vkMapMemory` 명령과는 달리 여기서는 `VK_WHOLE_SIZE`로 명시하는 것은 불가능합니다.
 
 ```c++
 vkEndCommandBuffer(commandBuffer);
 ```
 
-이 명령 버퍼는 복사 명령만을 포함하므로 바로 기록을 중단하면 됩니다. 
-이제 명령 버퍼를 실행하여 전송을 완료합니다:
+이 명령 버퍼는 복사 명령만을 포함하므로 바로 기록을 중단하면 됩니다. 이제 명령 버퍼를 실행하여 전송을 완료합니다:
 
 ```c++
 VkSubmitInfo submitInfo{};
@@ -174,12 +155,7 @@ vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
 vkQueueWaitIdle(graphicsQueue);
 ```
 
-그리기 명령과는 다르게 여기서를 기다려야 할 이벤트가 없습니다. 
-그냥 버퍼에 대한 전송 명령을 바로 실행하기를 원합니다. 
-여기서도 마찬가지로 이러한 전송이 완료되는 것을 기다리는 두 가지 방법이 있습니다. 
-`vkWaitForFences`로 펜스를 사용해 대기하거나, 전송 큐가 아이들(idle) 상태가 될때까지 대기하도록 `vkQueueWaitIdle`를 사용하는 것입니다. 
-하나씩 실행하는 것이 아니라 여러 개의 전송 명령을 동시에 계획하고 전체가 끝날때까지 대기하는 경우에 펜스를 사용하면 됩니다. 
-이렇게 하면 드라이버가 최적화 하기 더 좋습니다.
+그리기 명령과는 다르게 여기서를 기다려야 할 이벤트가 없습니다. 그냥 버퍼에 대한 전송 명령을 바로 실행하기를 원합니다. 여기서도 마찬가지로 이러한 전송이 완료되는 것을 기다리는 두 가지 방법이 있습니다. `vkWaitForFences`로 펜스를 사용해 대기하거나, 전송 큐가 아이들(idle) 상태가 될때까지 대기하도록 `vkQueueWaitIdle`를 사용하는 것입니다. 하나씩 실행하는 것이 아니라 여러 개의 전송 명령을 동시에 계획하고 전체가 끝날때까지 대기하는 경우에 펜스를 사용하면 됩니다. 이렇게 하면 드라이버가 최적화 하기 더 좋습니다.
 
 ```c++
 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
@@ -207,18 +183,13 @@ copyBuffer(stagingBuffer, vertexBuffer, bufferSize);
 }
 ```
 
-프로그램을 실행하여 삼각형이 잘 보이는지 확인하세요. 
-개선점이 바로 눈에 보이지는 않지만 이제 정점 데이터는 고성능 메모리로부터 로드(load)됩니다. 
-보다 복잡한 형상을 렌더링 할 때에는 이러한 사실이 중요해집니다.
+프로그램을 실행하여 삼각형이 잘 보이는지 확인하세요. 개선점이 바로 눈에 보이지는 않지만 이제 정점 데이터는 고성능 메모리로부터 로드(load)됩니다. 보다 복잡한 형상을 렌더링 할 때에는 이러한 사실이 중요해집니다.
 
 ## 결론
 
-실제 응용 프로그램에서는 개별 버퍼마다 `vkAllocateMemory`를 호출하지 않는것이 좋다는 점을 주의하십시오. 
-동시에 수행 가능한 메모리 할당은 물리적 장치의 `maxMemoryAllocationCount`에 의해 제한되며 NVIDIA GTX 1080와 같은 고성능 장치에서도 `4096`정도밖에 안됩니다. 
-많은 객체를 위한 메모리 할당을 한꺼번에 수행하는 적정한 방법은 여러 객체들에 대해 `offset` 매개변수를 사용해 한 번에 할당을 수행하는 별도의 할당자(allocator)를 만드는 것입니다.
+실제 응용 프로그램에서는 개별 버퍼마다 `vkAllocateMemory`를 호출하지 않는것이 좋다는 점을 주의하십시오. 동시에 수행 가능한 메모리 할당은 물리적 장치의 `maxMemoryAllocationCount`에 의해 제한되며 NVIDIA GTX 1080와 같은 고성능 장치에서도 `4096`정도밖에 안됩니다. 많은 객체를 위한 메모리 할당을 한꺼번에 수행하는 적정한 방법은 여러 객체들에 대해 `offset` 매개변수를 사용해 한 번에 할당을 수행하는 별도의 할당자(allocator)를 만드는 것입니다.
 
-이러한 할당자를 직접 구현해도 되고, GPUOpen 이니셔티브에서 제공하는 [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) 라이브러리를 사용해도 됩니다. 
-하지만 이 튜토리얼에서는 각 리소스에 대해 별도의 할당을 수행해도 상관없는데 지금은 위와 같은 제한에 걸릴 만큼 복잡한 작업은 하지 않기 떄문입니다.
+이러한 할당자를 직접 구현해도 되고, GPUOpen 이니셔티브에서 제공하는 [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) 라이브러리를 사용해도 됩니다. 하지만 이 튜토리얼에서는 각 리소스에 대해 별도의 할당을 수행해도 상관없는데 지금은 위와 같은 제한에 걸릴 만큼 복잡한 작업은 하지 않기 떄문입니다.
 
 [C++ code](/code/20_staging_buffer.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /
diff --git a/kr/04_Vertex_buffers/03_Index_buffer.md b/kr/04_Vertex_buffers/03_Index_buffer.md
index e39ad9cd..5cbb0ce1 100644
--- a/kr/04_Vertex_buffers/03_Index_buffer.md
+++ b/kr/04_Vertex_buffers/03_Index_buffer.md
@@ -1,24 +1,16 @@
 ## 개요
 
-실제 응용 프로그램에서 렌더링할 3D 메쉬는 여러 삼각형에서 정점을 공유하는 경우가 많습니다. 
-이는 아래와 같은 간단한 사각형을 렌더링 할 때도 적용됩니다:
+실제 응용 프로그램에서 렌더링할 3D 메쉬는 여러 삼각형에서 정점을 공유하는 경우가 많습니다. 이는 아래와 같은 간단한 사각형을 렌더링 할 때도 적용됩니다:
 
 ![](/images/vertex_vs_index.svg)
 
-사각형을 렌더링하려면 삼각형 두 개가 필요하기 때문에 정점 6개를 갖는 정점 버퍼가 필요합니다. 
-문제는 두 개의 정점은 동일한 데이터이기 때문에 50%의 중복이 발생한다는 것입니다. 
-메쉬가 복잡해지면 평균적으로 정점 한개당 3개의 삼각형에서 재사용되어 상황은 더 나빠집니다. 
-이 문제를 해결하는 방법은 *인덱스 버퍼(index buffer)*를 사용하는 것입니다.
+사각형을 렌더링하려면 삼각형 두 개가 필요하기 때문에 정점 6개를 갖는 정점 버퍼가 필요합니다. 문제는 두 개의 정점은 동일한 데이터이기 때문에 50%의 중복이 발생한다는 것입니다. 메쉬가 복잡해지면 평균적으로 정점 한개당 3개의 삼각형에서 재사용되어 상황은 더 나빠집니다. 이 문제를 해결하는 방법은 *인덱스 버퍼(index buffer)*를 사용하는 것입니다.
 
-인덱스 버퍼는 정점 버퍼에 대한 포인터 배열과 같습니다. 
-정점 데이터의 순서를 바꾸고 이미 존재하는 데이터는 여러 정점으로 활용할 수 있게 해줍니다. 
-위 그림에서는 정점 버퍼가 네 개의 정점을 가지고 있을 때 인덱스 버퍼를 사용해 사각형을 표현하는 예시를 보여줍니다. 
-첫 세 개의 인덱스가 위 오른쪽 삼각형을 정의하며 마지막 세 개의 인덱스가 왼쪽 아래 삼각형을 정의합니다.
+인덱스 버퍼는 정점 버퍼에 대한 포인터 배열과 같습니다. 정점 데이터의 순서를 바꾸고 이미 존재하는 데이터는 여러 정점으로 활용할 수 있게 해줍니다. 위 그림에서는 정점 버퍼가 네 개의 정점을 가지고 있을 때 인덱스 버퍼를 사용해 사각형을 표현하는 예시를 보여줍니다. 첫 세 개의 인덱스가 위 오른쪽 삼각형을 정의하며 마지막 세 개의 인덱스가 왼쪽 아래 삼각형을 정의합니다.
 
 ## 인덱스 버퍼 생성
 
-이 챕터에서 우리는 정점 데이터를 수정하고 인덱스 데이터를 추가하여 위 그림과 같은 사각형을 그려 볼 것입니다. 
-네 개의 꼭지점을 표현하도록 정점 데이터를 수정합니다:
+이 챕터에서 우리는 정점 데이터를 수정하고 인덱스 데이터를 추가하여 위 그림과 같은 사각형을 그려 볼 것입니다. 네 개의 꼭지점을 표현하도록 정점 데이터를 수정합니다:
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -29,9 +21,7 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-왼쪽 위 꼭지점은 빨간색, 오른쪽 위는 초록색, 오른쪽 아래는 파란색, 왼쪽 아래는 흰색입니다. 
-인덱스 버퍼의 내용은 새로운 `indices`를 추가하여 정의합니다. 
-오른쪽 위 삼각형과 왼쪽 아래 삼각형을 위해 그림에서와 같이 인덱스를 정의합니다.
+왼쪽 위 꼭지점은 빨간색, 오른쪽 위는 초록색, 오른쪽 아래는 파란색, 왼쪽 아래는 흰색입니다. 인덱스 버퍼의 내용은 새로운 `indices`를 추가하여 정의합니다. 오른쪽 위 삼각형과 왼쪽 아래 삼각형을 위해 그림에서와 같이 인덱스를 정의합니다.
 
 ```c++
 const std::vector<uint16_t> indices = {
@@ -39,11 +29,9 @@ const std::vector<uint16_t> indices = {
 };
 ```
 
-`vertices`요소 개수에 따라 인덱스 버퍼로 `uint16_t`나 `uint32_t`를 사용하는 것이 모두 가능합니다. 
-지금은 65535개보다는 정점이 적으므로 `uint16_t`를 사용합니다.
+`vertices`요소 개수에 따라 인덱스 버퍼로 `uint16_t`나 `uint32_t`를 사용하는 것이 모두 가능합니다. 지금은 65535개보다는 정점이 적으므로 `uint16_t`를 사용합니다.
 
-정점 데이터와 마찬가지로 인덱스도 `VkBuffer`를 통해 GPU로 전달되어 접근 가능하게 만들어야만 합니다.
-인덱스 버퍼 리소스를 저장할 두 개의 새로운 클래스 멤버를 정의합니다:
+정점 데이터와 마찬가지로 인덱스도 `VkBuffer`를 통해 GPU로 전달되어 접근 가능하게 만들어야만 합니다. 인덱스 버퍼 리소스를 저장할 두 개의 새로운 클래스 멤버를 정의합니다:
 
 ```c++
 VkBuffer vertexBuffer;
@@ -83,11 +71,7 @@ void createIndexBuffer() {
 }
 ```
 
-눈에 띄는 차이점은 두 가지입니다. 
-`bufferSize`는 인덱스 자료형인 `uint16_t` 또는 `uint32_t`의 크기 곱하기 인덱스의 개수입니다. 
-`indexBuffer`의 사용법은 당연히 `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`가 아닌 `VK_BUFFER_USAGE_INDEX_BUFFER_BIT` 입니다.
-이 외에는 모든 것이 같습니다. 
-`indices`의 내용을 장치의 로컬 인덱스 버퍼에 복사하기 위해 스테이징 버퍼를 만들어 사용합니다.
+눈에 띄는 차이점은 두 가지입니다. `bufferSize`는 인덱스 자료형인 `uint16_t` 또는 `uint32_t`의 크기 곱하기 인덱스의 개수입니다. `indexBuffer`의 사용법은 당연히 `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`가 아닌 `VK_BUFFER_USAGE_INDEX_BUFFER_BIT` 입니다. 이 외에는 모든 것이 같습니다. `indices`의 내용을 장치의 로컬 인덱스 버퍼에 복사하기 위해 스테이징 버퍼를 만들어 사용합니다.
 
 인덱스 버퍼 정점 버퍼처럼 프로그램 종료 시점에 정리되어야 합니다:
 
@@ -107,10 +91,7 @@ void cleanup() {
 
 ## 인덱스 버퍼 사용
 
-그리기를 위해 인덱스 버퍼를 사용하기 위해서는 `recordCommandBuffer`에 두 가지 변화가 필요합니다. 
-우선 정범 버퍼터럼 인덱스 버퍼도 바인딩 해야 합니다. 
-차이점은 하나의 인덱스 버퍼만 가질 수 있다는 것입니다. 
-각 정점 어트리뷰트에 대해 안타깝게도 여러 개의 인덱스를 사용할 수는 없으니 하나의 어트리뷰트만 바뀌어도 전체 정점 데이터를 복사해야 합니다.
+그리기를 위해 인덱스 버퍼를 사용하기 위해서는 `recordCommandBuffer`에 두 가지 변화가 필요합니다. 우선 정범 버퍼터럼 인덱스 버퍼도 바인딩 해야 합니다. 차이점은 하나의 인덱스 버퍼만 가질 수 있다는 것입니다. 각 정점 어트리뷰트에 대해 안타깝게도 여러 개의 인덱스를 사용할 수는 없으니 하나의 어트리뷰트만 바뀌어도 전체 정점 데이터를 복사해야 합니다.
 
 ```c++
 vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
@@ -118,38 +99,23 @@ vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
 vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
 ```
 
-인덱스 버퍼의 바인딩은 `vkCmdBindIndexBuffer`로 수행되며 이 함수는 인덱스 버퍼, 바이트 오프셋, 인덱스 데이터의 타입을 매개변수로 받습니다. 
-앞서 이야기한 것처럼 타입은 `VK_INDEX_TYPE_UINT16` 또는 `VK_INDEX_TYPE_UINT32` 입니다.
+인덱스 버퍼의 바인딩은 `vkCmdBindIndexBuffer`로 수행되며 이 함수는 인덱스 버퍼, 바이트 오프셋, 인덱스 데이터의 타입을 매개변수로 받습니다. 앞서 이야기한 것처럼 타입은 `VK_INDEX_TYPE_UINT16` 또는 `VK_INDEX_TYPE_UINT32` 입니다.
 
-인덱스 버퍼를 바인딩한 것만으로 아직 바뀐 것은 없습니다. 
-Vulkan에 인덱스 버퍼를 사용하도록 그리기 명령 또한 수정해야 합니다. 
-`vkCmdDraw` 라인을 `vkCmdDrawIndexed`로 바꿉니다:
+인덱스 버퍼를 바인딩한 것만으로 아직 바뀐 것은 없습니다. Vulkan에 인덱스 버퍼를 사용하도록 그리기 명령 또한 수정해야 합니다. `vkCmdDraw` 라인을 `vkCmdDrawIndexed`로 바꿉니다:
 
 ```c++
 vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
 ```
 
-`vkCmdDraw` 호출과 매우 유사합니다. 
-첫 두 매개변수는 인덱스의 개수와 인스턴스 개수를 명시합니다. 
-인스턴싱을 하지 않으므로 `1`로 두었습니다. 
-인덱스의 개수는 정점 셰이더에 넘겨질 정점의 개수를 의미합니다. 
-다음 매개변수는 인덱스 버터의 오프셋이고 `1`을 사용하면 두 번째 인덱스부터 렌더링을 시작합니다. 
-마지막에서 두 번째 매개변수는 인덱스 버퍼에 추가할 인덱스의 오프셋을 의미합니다. 
-마지막 매개변수는 인스턴싱을 위한 오프셋이고, 여기서는 사용하지 않습니다.
+`vkCmdDraw` 호출과 매우 유사합니다. 첫 두 매개변수는 인덱스의 개수와 인스턴스 개수를 명시합니다. 인스턴싱을 하지 않으므로 `1`로 두었습니다. 인덱스의 개수는 정점 셰이더에 넘겨질 정점의 개수를 의미합니다. 다음 매개변수는 인덱스 버터의 오프셋이고 `1`을 사용하면 두 번째 인덱스부터 렌더링을 시작합니다. 마지막에서 두 번째 매개변수는 인덱스 버퍼에 추가할 인덱스의 오프셋을 의미합니다. 마지막 매개변수는 인스턴싱을 위한 오프셋이고, 여기서는 사용하지 않습니다.
 
 이제 프로그램을 실행하면 아래와 같은 화면이 보입니다:
 
 ![](/images/indexed_rectangle.png)
 
-이제 인덱스 버퍼를 사용해 정점을 재사용하여 메모리를 아끼는 법을 배웠습니다. 
-나중에 복잡한 3D 모델을 로딩할 챕터에서는 이러한 기능이 특히 중요합니다.
+이제 인덱스 버퍼를 사용해 정점을 재사용하여 메모리를 아끼는 법을 배웠습니다. 나중에 복잡한 3D 모델을 로딩할 챕터에서는 이러한 기능이 특히 중요합니다.
 
-이전 챕터에서 버퍼와 같은 다중 리소스들을 한 번의 메모리 할당으로 진행해야 한다고 언급했지만 사실 그 이상으로 해야 할 일들이 있습니다. 
-[드라이버 개발자가 추천하길](https://developer.nvidia.com/vulkan-memory-management) 정점과 인덱스 버퍼와 같은 다중 버퍼를 하나의 `VkBuffer`에 저장하고 `vkCmdBindVertexBuffers`와 같은 명령에서 오프셋을 사용하라고 합니다. 
-이렇게 하면 데이터가 함께 존재하기 때문에 더 캐시 친화적입니다. 
-또한 같은 렌더링 연산에 사용되는 것이 아니라면, 메모리 덩어리(chunk)를 여러 리소스에서 재사용 하는 것도 가능합니다.
-이는 *앨리어싱(aliasing)*이라고 불리며 몇몇 Vulkan 함수는 이러한 동작을 수행하려 한다는 것을 알려주기 위한 명시적 플래그도 존재합니다.
-이렇게 하면 
+이전 챕터에서 버퍼와 같은 다중 리소스들을 한 번의 메모리 할당으로 진행해야 한다고 언급했지만 사실 그 이상으로 해야 할 일들이 있습니다. [드라이버 개발자가 추천하길](https://developer.nvidia.com/vulkan-memory-management) 정점과 인덱스 버퍼와 같은 다중 버퍼를 하나의 `VkBuffer`에 저장하고 `vkCmdBindVertexBuffers`와 같은 명령에서 오프셋을 사용하라고 합니다. 이렇게 하면 데이터가 함께 존재하기 때문에 더 캐시 친화적입니다. 또한 같은 렌더링 연산에 사용되는 것이 아니라면, 메모리 덩어리(chunk)를 여러 리소스에서 재사용 하는 것도 가능합니다. 이는 *앨리어싱(aliasing)*이라고 불리며 몇몇 Vulkan 함수는 이러한 동작을 수행하려 한다는 것을 알려주기 위한 명시적 플래그도 존재합니다.
 
 [C++ code](/code/21_index_buffer.cpp) /
 [Vertex shader](/code/18_shader_vertexbuffer.vert) /

From 5d456ab48fecb1cc93eeb4ffbf5684d42909db5f Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 15 Feb 2024 15:25:20 +0900
Subject: [PATCH 35/47] kr translate add Korean glossary, fix terms

---
 kr/01_Overview.md                             | 20 ++--
 .../01_Presentation/01_Swap_chain.md          | 12 +--
 .../01_Shader_modules.md                      |  6 +-
 .../04_Conclusion.md                          |  2 +-
 .../02_Rendering_and_presentation.md          |  2 +-
 .../03_Drawing/03_Frames_in_flight.md         |  4 +-
 .../04_Swap_chain_recreation.md               |  2 +-
 .../00_Descriptor_layout_and_buffer.md        |  2 +-
 kr/kr_glossary.md                             | 93 +++++++++++++++++++
 9 files changed, 118 insertions(+), 25 deletions(-)
 create mode 100644 kr/kr_glossary.md

diff --git a/kr/01_Overview.md b/kr/01_Overview.md
index 6208a36e..e8984509 100644
--- a/kr/01_Overview.md
+++ b/kr/01_Overview.md
@@ -6,7 +6,7 @@
 
 그래픽 카드의 아키텍처가 발전하면서 점점 더 많은 프로그램가능한(programmable) 기능을 제공하기 시작했습니다. 이러한 새로운 기능은 어떻게든 기존 API와 통합되어야 했습니다. 이에 따라 이상적인 추상화와는 멀어지기 시작했고, 프로그래머의 의도와 현대적인 그래픽스 아키텍처간의 맵핑을 위해 그래픽 드라이버 내부에 대한 어림짐작들이 포함되기 시작했습니다. 이것이 게임의 성능 향상을 위해 많은 드라이버 업데이트가 필요한 이유고, 때때로 인로 인해 많은 성능 향상이 일어나기도 합니다. 드라이버의 복잡성 때문에 응용 프로그램 개발자들은 벤더(vendor) 사이의 불일치를 다루어야 하기도 하는데, 예를 들어 어떤 [셰이더](https://en.wikipedia.org/wiki/Shader) 문법이 받아들여지는지 아닌지와 같은 것입니다. 이러한 새로운 기능 이외에도, 지난 십 년 동안 새로 시작에 편입된 강력한 그래픽 하드웨어를 가진 모바일 기기가 있습니다. 이 모바일 GPU는 전력과 공간 요구사항으로 인해 또다른 아키텍처를 가지게 되었습니다. 한 예로 [tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering)이 있는데, 이 기능에 대해 프로그래머에게 보다 많은 제어 권한을 부여함으로써 성능이 향상될 수 있었습니다. 이러한 API의 설계 시기에 기인한 또 다른 문제로 멀티쓰레딩의 제약이 있습니다. 이로 인해 CPU 단에서의 병목이 발생하기도 합니다.
 
-Vulkan은 처음부터 현대적인 그래픽스 아키텍처에 기반한 설계를 통해 이러한 문제를 해결합니다. 보다 장황한 API를 통해 프로그래머가 의도를 명확하게 기술할 수 있게 함으로써 드라이버의 오버헤드(overhead)를 줄이고, 멀티쓰레드로 커맨드(command)을 병렬적으로 생성 및 제출(submit)할 수 있습니다. 단일 컴파일러, 표준화된 바이트 코드로 변경함으로써 셰이더 컴파일의 불일치 문제도 해결합니다. 마지막으로 그래픽스와 계산 기능을 단일 API로 병합하여 현대 그래픽 카드의 범용 계산(general purpose) 기능을 공식적으로 지원합니다.
+Vulkan은 처음부터 현대적인 그래픽스 아키텍처에 기반한 설계를 통해 이러한 문제를 해결합니다. 보다 장황한 API를 통해 프로그래머가 의도를 명확하게 기술할 수 있게 함으로써 드라이버의 오버헤드(overhead)를 줄이고, 멀티쓰레드로 명령(command)을 병렬적으로 생성 및 제출(submit)할 수 있습니다. 단일 컴파일러, 표준화된 바이트 코드로 변경함으로써 셰이더 컴파일의 불일치 문제도 해결합니다. 마지막으로 그래픽스와 계산 기능을 단일 API로 병합하여 현대 그래픽 카드의 범용 계산(general purpose) 기능을 공식적으로 지원합니다.
 
 ## 삼각형을 그리기 위해 필요한 것들
 
@@ -18,7 +18,7 @@ Vulkan 응용 프로그램은 `VkInstance`를 통해 Vulkan API를 설정함으
 
 ### 2단계 - 논리적 장치(logical device)와 큐 패밀리(queue family)
 
-사용할 적절한 하드웨어를 선택한 뒤에는, 논리적 장치인 VkDevice를 만들어야 합니다. 이를 통해 좀 더 상세하게 다중 뷰포트 렌더링을 할 것인지, 64 bit float을 사용할지와 같은 상세 VkPhysicalDeviceFeatures를 기술합니다. 또한 어떤 큐 패밀리를 사용할지 명시해야 합니다. 그리기 커맨드나 메모리 연산과 같은 대부분의 연산들은 Vulkan을 통해 수행되는데 이는 이러한 작업들을 VkQueue에 제출한 후 비동기적으로 실행됩니다. 큐는 큐 패밀리에 할당되는데, 각 큐 패밀리는 큐에 있는 특정 연산의 집합을 지원합니다. 예를 들어, 그래픽스를 위한 큐 패밀리, 계산을 위한 큐 패밀리, 메모리 전송을 위한 큐 패밀리가 있을 수 있습니다. 큐 패밀리의 가용 여부는 물리적 장치 선택의 구분 기준으로도 활용될 수 있습니다. Vulkan을 지원하지만 그래픽스 연산 기능을 전혀 지원하지 않는 장치가 있을 수도 있습니다. 하지만 오늘날 Vulkan을 지원하는 모든 그래픽 카드들은 일반적으로 우리가 필요로 하는 모든 큐 연산을 지원하고 있습니다.
+사용할 적절한 하드웨어를 선택한 뒤에는, 논리적 장치인 VkDevice를 만들어야 합니다. 이를 통해 좀 더 상세하게 다중 뷰포트 렌더링을 할 것인지, 64 bit float을 사용할지와 같은 상세 VkPhysicalDeviceFeatures를 기술합니다. 또한 어떤 큐 패밀리를 사용할지 명시해야 합니다. 그리기 명령이나 메모리 연산과 같은 대부분의 연산들은 Vulkan을 통해 수행되는데 이는 이러한 작업들을 VkQueue에 제출한 후 비동기적으로 실행됩니다. 큐는 큐 패밀리에 할당되는데, 각 큐 패밀리는 큐에 있는 특정 연산의 집합을 지원합니다. 예를 들어, 그래픽스를 위한 큐 패밀리, 계산을 위한 큐 패밀리, 메모리 전송을 위한 큐 패밀리가 있을 수 있습니다. 큐 패밀리의 가용 여부는 물리적 장치 선택의 구분 기준으로도 활용될 수 있습니다. Vulkan을 지원하지만 그래픽스 연산 기능을 전혀 지원하지 않는 장치가 있을 수도 있습니다. 하지만 오늘날 Vulkan을 지원하는 모든 그래픽 카드들은 일반적으로 우리가 필요로 하는 모든 큐 연산을 지원하고 있습니다.
 
 ### 3단계 - 윈도우 표면(window surface)과 스왑 체인(swap chain)
 
@@ -46,26 +46,26 @@ Vulkan과 기존 API간의 가장 큰 차이점은 거의 모든 그래픽스 
 
 좋은 소식은 이러한 작업들이 ahead-of-time 컴파일과 just-in-time 컴파일의 차이 같은 것이라, 드라이버가 미리 최적화 할 수 있는 여지가 많고, 런타임 성능이 보다 예측하기 쉽다는 것입니다. 왜냐하면 다른 그래픽스 파이프라인으로의 변경과 같은 큰 상태 변화가 매우 명시적으로 표현되기 때문이지요.
 
-### 7단계 - 커맨드 풀(pool)과 커맨드 버퍼
+### 7단계 - 명령 풀(pool)과 명령 버퍼
 
-앞서 이야기한 것처럼, 우리가 실행하고자 하는, 예를 들자면 그리기와 같은 Vulkan의 많은 연산들은 큐에 제출되어야 합니다. 이러한 연산들은 제출되기 전에 VkCommandBuffer에 먼저 기록되어야 합니다. 이러한 커맨드 버퍼는 `VkCommandPool`로부터 할당되고 이는 특정한 큐 패밀리와 연관(associate)되어 있습니다. 간단한 삼각형을 그리기 위해서는 아래와 같은 연산들을 커맨드 버퍼에 기록해야 합니다.
+앞서 이야기한 것처럼, 우리가 실행하고자 하는, 예를 들자면 그리기와 같은 Vulkan의 많은 연산들은 큐에 제출되어야 합니다. 이러한 연산들은 제출되기 전에 VkCommandBuffer에 먼저 기록되어야 합니다. 이러한 명령 버퍼는 `VkCommandPool`로부터 할당되고 이는 특정한 큐 패밀리와 연관(associate)되어 있습니다. 간단한 삼각형을 그리기 위해서는 아래와 같은 연산들을 명령 버퍼에 기록해야 합니다.
 
 - 렌더 패스 시작
 - 그래픽스 파이프라인 바인딩
 - 3개 정점 그리기
 - 렌더 패스 종료
 
-프레임버퍼의 이미지는 스왑 체인이 우리에게 전달해준 이미지에 의존하기 때문에, 커맨드 버퍼에 기록할 명령어는 모든 가능한 이미지에 대해 기록되어야 하고, 그리기 시점에 올바른 것이 선택되어야 합니다. 다른 방법으로는 매 프레임마다 커맨드 버퍼를 기록하는 것인데, 그리 효율적이지 않습니다.
+프레임버퍼의 이미지는 스왑 체인이 우리에게 전달해준 이미지에 의존하기 때문에, 명령 버퍼에 기록할 명령어는 모든 가능한 이미지에 대해 기록되어야 하고, 그리기 시점에 올바른 것이 선택되어야 합니다. 다른 방법으로는 매 프레임마다 명령 버퍼를 기록하는 것인데, 그리 효율적이지 않습니다.
 
 ### 8단계 - 메인 루프(main loop)
 
-그리기 커맨드가 커맨드 버퍼에 기록되었으므로 메인 루프는 직관적입니다. 먼저 스왑 체인으로부터 vkAcquireNextImageKHR를 통해 이미지를 얻습니다. 그리고 해당 이미지에 적합한 커맨드 버퍼를 선택하고 vkQueueSubmit를 통해 실행합니다. 마지막으로 vkQueuePresentKHR를 통해 이미지를 스왑 체인에 반환하여 화면에 표시되게 합니다.
+그리기 명령이 명령 버퍼에 기록되었으므로 메인 루프는 직관적입니다. 먼저 스왑 체인으로부터 vkAcquireNextImageKHR를 통해 이미지를 얻습니다. 그리고 해당 이미지에 적합한 명령 버퍼를 선택하고 vkQueueSubmit를 통해 실행합니다. 마지막으로 vkQueuePresentKHR를 통해 이미지를 스왑 체인에 반환하여 화면에 표시되게 합니다.
 
-큐에 제출된 연산들은 비동기적으로 실행됩니다. 따라서 세마포어와 같은 동기 객체를 사용하여 실행이 올바른 순서로 이루어지도록 해야 합니다. 그리기 커맨드 버퍼의 실행은 이미지 획득 이후에 되도록 해야 합니다. 그렇지 않으면 화면에 표시하기 위해 읽고 있는 이미지에 렌더링이 수행될 수도 있습니다. vkQueuePresentKHR의 호출은 렌더링이 끝날 때까지 기다려야 하는데 이를 위해서는 렌더링이 끝나면 신호를 보내는 두 번째 세마포어를 사용해야 할 것입니다.
+큐에 제출된 연산들은 비동기적으로 실행됩니다. 따라서 세마포어와 같은 동기화 객체를 사용하여 실행이 올바른 순서로 이루어지도록 해야 합니다. 그리기 명령 버퍼의 실행은 이미지 획득 이후에 되도록 해야 합니다. 그렇지 않으면 화면에 표시하기 위해 읽고 있는 이미지에 렌더링이 수행될 수도 있습니다. vkQueuePresentKHR의 호출은 렌더링이 끝날 때까지 기다려야 하는데 이를 위해서는 렌더링이 끝나면 신호를 보내는 두 번째 세마포어를 사용해야 할 것입니다.
 
 ### 요약
 
-이 정신없는 소개를 통해 삼각형을 그리기 앞서 알아야 할 것들에 대한 기본적인 이해가 되었길 바랍니다. 실제 프로그램은 몇 가지 단계가 더 필요한데, 정점 버퍼를 할당한다던지, 유니폼(uniform) 버퍼를 만들고 텍스처 이미지를 업로드한다던다 하는 것이고, 이어지는 챕터에서 소개할 것입니다. 우선은 간단히 시작할 것인데 Vulkan의 학습 곡선이 이미 충분히 가파르기 때문입니다. 주의하실 것은 초기에 우리는 정점의 좌표를 정점 버퍼를 사용하는 대신 정점 셰이더에 직접 하드코딩 하는 편법을 쓸 것입니다. 이는 정점 버퍼를 사용하기 위해서는 우선 커맨드 버퍼에 익숙해져야 하기 때문입니다.
+이 정신없는 소개를 통해 삼각형을 그리기 앞서 알아야 할 것들에 대한 기본적인 이해가 되었길 바랍니다. 실제 프로그램은 몇 가지 단계가 더 필요한데, 정점 버퍼를 할당한다던지, 유니폼(uniform) 버퍼를 만들고 텍스처 이미지를 업로드한다던다 하는 것이고, 이어지는 챕터에서 소개할 것입니다. 우선은 간단히 시작할 것인데 Vulkan의 학습 곡선이 이미 충분히 가파르기 때문입니다. 주의하실 것은 초기에 우리는 정점의 좌표를 정점 버퍼를 사용하는 대신 정점 셰이더에 직접 하드코딩 하는 편법을 쓸 것입니다. 이는 정점 버퍼를 사용하기 위해서는 우선 명령 버퍼에 익숙해져야 하기 때문입니다.
 
 요약하자면, 삼각형을 그리기 위해서 우리는:
 
@@ -77,8 +77,8 @@ Vulkan과 기존 API간의 가장 큰 차이점은 거의 모든 그래픽스 
 - 렌더 타겟과 사용법을 명시하는 렌더 패스 생성
 - 렌더 패스를 위한 프레임버퍼 생성
 - 그래픽스 파이프라인 설정
-- 모든 후보 스왑 체인 이미지에 대해 커맨드 버퍼를 할당하고 그리기 커맨드를 기록
-- 이미지를 획득하고, 올바른 그리기 커맨드를 제출하고, 이미지를 스왑 체인에 반환하여 프레임 그리기
+- 모든 후보 스왑 체인 이미지에 대해 명령 버퍼를 할당하고 그리기 명령을 기록
+- 이미지를 획득하고, 올바른 그리기 명령을 제출하고, 이미지를 스왑 체인에 반환하여 프레임 그리기
 
 단계가 많지만 개별 단계의 목적에 대해서는 이후 챕터에서 명확하고 단순하게 설명할 것입니다. 개별 단계와 전체 프로그램의 관계에 대해 헷갈리시면 이 챕터로 다시 돌아 오십시오.
 
diff --git a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
index 43555845..ee56817d 100644
--- a/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
+++ b/kr/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md
@@ -1,6 +1,6 @@
 Vulkan은 "기본 프레임버퍼(default framebuffer)"의 개념이 없습니다. 따라서 렌더링을 수행할 버퍼를 소유한 하부 구조(infrastructure)를 만들어서 화면에 그려지게 해야 합니다. 이 하부 구조는 *스왑 체인(swap chain)*이라고 하며 Vulkan의 경우 명시적으로 생성되어야 합니다. 스왑 체인은 기본적으로 화면에 표시되길 기다리는 이미지들의 큐입니다. 우리 응용 프로그램에서는 이러한 이미지를 만들고 큐에 반환할 것입니다. 큐가 어떻게 동작하고 어떤 조건에서 큐의 이미지가 표시될 것인지와 같은 사항들은 스왑 체인의 설정에 따라 달라집니다. 하지만 일반적으로 스왑 체인의 역할은 화면의 주사율(refresh rate)과 이미지의 표시를 동기화(synchronize)하는 것입니다.
 
-## 스왑 체인 지원 확인하기
+## 스왑 체인 지원 확인
 
 모든 그래픽 카드가 이미지를 곧바로 화면에 표시하는 기능을 지원하는 것은 아닙니다. 예를 들어 서버를 위해 설계된 그래픽 카드는 디스플레이 출력이 없을 수 있습니다. 또한, 이미지의 표현은 윈도우 시스템, 그 윈도우와 연관된 표면(surface)과 밀접하게 관련되어 있기 때문에 Vulkan 코어(core)에는 포함되어 있지 않습니다. 지원하는지를 확인한 후에 `VK_KHR_swapchain` 장치 확장을 활성화시켜줘야만 합니다.
 
@@ -127,7 +127,7 @@ if (presentModeCount != 0) {
 }
 ```
 
-이제 모든 세부 사항이 구조체 안에 있으니 `isDeviceSuitable`를 한번 더 확장하여 적절하게 스왑 체인이 지원되고 있는지 확인하도록 해 봅시다. 이 튜토리얼에서의 스왑 체인 지원은 우리가 가진 윈도우 표면에 대해 최소 하나의 이미지 포맷과 표면 모드를 지원하는 것이면 충분합니다.
+이제 모든 세부 사항이 구조체 안에 있으니 `isDeviceSuitable`를 한번 더 수정하여 적절하게 스왑 체인이 지원되고 있는지 확인하도록 해 봅시다. 이 튜토리얼에서의 스왑 체인 지원은 우리가 가진 윈도우 표면에 대해 최소 하나의 이미지 포맷과 표면 모드를 지원하는 것이면 충분합니다.
 
 ```c++
 bool swapChainAdequate = false;
@@ -143,7 +143,7 @@ if (extensionsSupported) {
 return indices.isComplete() && extensionsSupported && swapChainAdequate;
 ```
 
-## 스왑 체인에 대한 적절한 설정 선택하기
+## 스왑 체인에 대한 적절한 설정 선택
 
 우리가 얻은 `swapChainAdequate` 조건이 만족되었다면 충분하지만, 서로 다른 최적화 요구사항에 대한 모드들이 여전히 존재합니다. 이제는 최적의 스왑 체인 설정을 찾기 위한 몇 가지 함수를 작성해 볼 것입니다. 결정해야 할 설정은 세 가지입니다:
 
@@ -222,7 +222,7 @@ VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& avai
 }
 ```
 
-### 스왑 크기(extent)
+### 스왑 범위(extent)
 
 이제 주요 속성 하나만 남았고, 마지막 함수로 추가할 것입니다:
 
@@ -232,7 +232,7 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 }
 ```
 
-스왑 크기는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 *픽셀 단위에서* 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)로 해상도를 명시해야 합니다.
+스왑 범위는 스왑 체인 이미지의 해상도이고 거의 대부분의 경우에 *픽셀 단위에서* 이미지를 그리고자 하는 윈도의 해상도와 동일한 값을 가집니다(보다 상세한 내용은 곧 살펴볼 것입니다). 가능한 해상도의 범위는 `VkSurfaceCapabilitiesKHR` 구조체에 정의되어 있습니다. Vulkan은 `currentExtent` 멤버의 너비와 높이를 설정하여 윈도우의 해상도와 맞추도록 하고 있습니다. 하지만 어떤 윈도우 매니저의 경우 `currentExtent`의 너비와 높이 값을 특수한 값(`uint32_t`의 최대값)으로 설정하여 이 두 값을 다르게 할 수 있습니다. 이러한 경우 윈도우에 가장 적절한 해상도를 `minImageExtent`와 `maxImageExtent` 사이 범위에서 선택하게 됩니다. 하지만 올바른 단위(unit)로 해상도를 명시해야 합니다.
 
 GLFW는 크기를 측정하는 두 단위가 있고 이는 픽셀과 [스크린 좌표계](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems) 입니다. 예를 들어 우리가 이전에 윈도우를 생성할 때 명시한 `{WIDTH, HEIGHT}` 해상도는 스크린 좌표계 기준으로 측정한 값입니다. 하지만 Vulkan은 픽셀 단위로 동작하기 때문에, 스왑 체인의 크기도 픽셀 단위로 명시해 주어야만 합니다. 안타깝게도 여러분이 (애플의 레티나 디스플레이와 같은) 고DPI 디스플레이를 사용하는 경우, 스크린 좌표계가 픽셀 단위와 달라집니다. 높은 픽셀 밀도로 인해 픽셀 단위의 윈도우 해상도는 스크린 좌표계 단위의 윈도우 해상도보다 커집니다. Vulkan이 스왑 크기에 관한 것을 수정해 주지 않는 한, 그냥 `{WIDTH, HEIGHT}`를 사용할 수는 없습니다. 대신에 `glfwGetFramebufferSize`를 사용해서 윈도우의 해상도를 최대 및 최소 이미지 크기와 맞추기 전에 픽셀 단위로 받아와야만 합니다.
 
@@ -265,7 +265,7 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
 
 여기서 `clamp` 함수는 `width`와 `height` 값을 허용 가능한 최대와 최소 크기로 제한하기 위해 사용되었습니다.
 
-## 스왑 체인 생성하기
+## 스왑 체인 생성
 
 이제 런타임 선택을 위해 필요한 모든 헬퍼 함수들이 준비되었으니 동작하는 스왑 체인을 만들기 위한 모든 정보를 얻을 수 있습니다.
 
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
index 688c3ea2..07da4582 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
@@ -96,7 +96,7 @@ void main() {
 
 입력 변수의 이름이 (정점 셰이더의 출력과) 같은 이름일 필요는 없습니다. 이들은 `location` 지시어에 의해 명시된 인덱스를 기반으로 연결됩니다. `main`함수는 알파값과 함께 색상을 출력하도록 수정되었습니다. 위쪽 이미지에서 본 것처럼, `fragColor`의 값은 자동으로 세 정점 사이에서 보간되어 연속적인 값을 보여줍니다.
 
-## 셰이더 컴파일하기
+## 셰이더 컴파일
 
 프로젝트의 루트(root) 디렉토리에 `shaders`라는 이름의 디렉토리를 만들고 정점 셰이더는 `shader.vert` 파일에, 프래그먼트 셰이더는 `shader.frag`파일에 작성하고 해당 디렉토리에 넣으세요. GLSL 셰이더를 위한 공식적인 확장자는 없지만, 이러한 방식이 그 둘을 구분하기 위해 일반적으로 사용하는 방법입니다.
 
@@ -228,7 +228,7 @@ void createGraphicsPipeline() {
 
 셰이더가 제대로 로드 되었는지를 버퍼의 크기를 출력하여 파일의 실제 바이트 사이와 일치하는를 통해 확인하세요. 바이너리 코드이기 때문에 널 종료(null terminate)여야 할 필요가 없고 나중에는 이러한 크기를 명시적으로 확인할 것입니다.
 
-## 셰이더 모듈 생성하기
+## 셰이더 모듈 생성
 
 코드를 파이프라인에 넘기기 전에, `VkShaderModule` 객체로 이들을 감싸야 합니다. 이를 위한 `createShaderModule` 헬퍼 함수를 만듭시다.
 
@@ -284,7 +284,7 @@ void createGraphicsPipeline() {
 }
 ```
 
-## 셰이더 단계(stage) 생성하기
+## 셰이더 단계(stage) 생성
 
 셰이더를 실제로 사용하기 위해서는 이들을 `VkPipelineShaderStageCreateInfo` 구조체를 활용하여 파이프라인의 특정 단계에 할당해야 하고, 이 역시 파이프라인 생성 과정의 한 부분입니다.
 
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
index 0fe70c20..5e1494cb 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md
@@ -2,7 +2,7 @@
 
 * 셰이더 단계: 그래픽스 파이프라인 내의 프로그램 가능한 단계들의 기능을 정의하는 셰이더 모듈
 * 고정 함수 상태: 파이프라인의 고정함수 단계들을 정의하는 구조체들. 여기에는 입력 조립기, 래스터화, 뷰포트와 컬러 블렌딩이 포함됨
-* 파이프라인 레이아웃: 셰이더가 참조하는, 그리기 시점에 갱신될 수 있는 uniform과 push 값들
+* 파이프라인 레이아웃: 셰이더가 참조하는, 그리기 시점에 갱신될 수 있는 유니폼과 push 값들
 * 렌더 패스: 파이프라인에서 참조하는 어태치먼트들과 그 사용 용도
 
 이 것들이 모여 그래픽스 파이프라인의 기능을 완전히 명시합니다. 이제 우리는 `createGraphicsPipeline` 함수의 마지막 부분에 `VkGraphicsPipelineCreateInfo` 구조체를 만들 수 있습니다. `vkDestroyShaderModule` 보다는 전이어야 하는데 이것들이 생성 과정에서 사용되기 때문입니다.
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
index 3c818cad..0b065f10 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md
@@ -179,7 +179,7 @@ void cleanup() {
 
 메인 그리기 함수로 가 봅시다!
 
-## 이전 프레임 기다리기
+## 이전 프레임 대기
 
 프레임 시작 시점에 이전 프레임이 끝나기를 기다려야 하므로 명령 버퍼와 세마포어(*역주: 펜스일 듯*)가 사용 가능해야 합니다. 이를 위해 `vkWaitForFences`를 호출합니다.
 
diff --git a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
index 304b6f51..74ebb07c 100644
--- a/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
+++ b/kr/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md
@@ -1,6 +1,6 @@
-## 여러 프레임 사용하기(frames in-flight)
+## 여러 프레임의 사용(frames in-flight)
 
-지금 우리 렌더링 루프에 눈에 띄는 문제가 하나 있습니다. 다음 프레임을 렌더링 하기 전에 이전 프레임을 기다려야만 하고 이로 인해 호스트는 불필요한 대기(ideling) 시간을 갖게 됩니다.
+지금 우리 렌더링 루프에 눈에 띄는 문제가 하나 있습니다. 다음 프레임을 렌더링 하기 전에 이전 프레임을 기다려야만 하고 이로 인해 호스트는 불필요한 아이들링(ideling) 시간을 갖게 됩니다.
 
 <!-- insert diagram showing our current render loop and the 'multi frame in flight' render loop -->
 
diff --git a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
index e7d7b236..87b6dcbc 100644
--- a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
+++ b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
@@ -84,7 +84,7 @@ void cleanup() {
 }
 ```
 
-`chooseSwapExtent`에서 이미 새로운 윈도우의 해상도를 질의해서 스왑 페인 이미지가 (새로운) 윈도우에 적합한 크기가 되도록 했다는 것에 주목하십시오. 따라서 `chooseSwapExtent`를 수정할 필요는 없습니다(`glfwGetFramebufferSize`를 사용해서 스왑 체인 생성 시점에 픽셀 단위의 표면 해상도를 얻어왔다는 것을 기억하세요).
+`chooseSwapExtent`에서 이미 새로운 윈도우의 해상도를 질의해서 스왑 체인 이미지가 (새로운) 윈도우에 적합한 크기가 되도록 했다는 것에 주목하십시오. 따라서 `chooseSwapExtent`를 수정할 필요는 없습니다(`glfwGetFramebufferSize`를 사용해서 스왑 체인 생성 시점에 픽셀 단위의 표면 해상도를 얻어왔다는 것을 기억하세요).
 
 이로써 스왑 체인을 재생성하는 부분은 끝입니다! 하지만 이러한 접근법의 단점은 새로운 스왑 체인이 생성될 때까지 모든 렌더링이 중단된다는 것입니다. 이전 스왑 체인이 사용되는 동안에 그리기가 수행되는 동안에 대 스왑 체인을 만드는 것도 가능합니다. 그러려면 `VkSwapchainCreateInfoKHR` 구조체의 `oldSwapChain` 필드에 이전 스왑 체인을 전달하고 사용이 끝난 뒤 소멸시키면 됩니다.
 
diff --git a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
index b522f3bd..0115deea 100644
--- a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
+++ b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
@@ -310,7 +310,7 @@ GLM은 원래 OpenGL을 기반으로 설계되었기 때문에 클립 좌표계
 memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
 ```
 
-UBO를 이런 방식으로 사용하는 것은 자주 바뀌는 값을 셰이더에 전달하기 위한 효율적인 방법은 아닙니다. 작은 버퍼의 데이터를 셰이더에 전달하기 위한 더 효율적인 방법은 *상수 푸쉬(push constant)*입니다. 이에 대해서는 나중 챕터에서 알아보도록 하겠습니다.
+UBO를 이런 방식으로 사용하는 것은 자주 바뀌는 값을 셰이더에 전달하기 위한 효율적인 방법은 아닙니다. 작은 버퍼의 데이터를 셰이더에 전달하기 위한 더 효율적인 방법은 *Push 상수(push constant)*입니다. 이에 대해서는 나중 챕터에서 알아보도록 하겠습니다.
 
 다음 챕터에서는 `VkBuffer`들을 유니폼 버퍼 기술자에 바인딩하는 기술자 집합에 대해 알아볼 것입니다. 이를 통해 셰이더가 이러한 변환 데이터에 접근할 수 있게 될 것입니다.
 
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
new file mode 100644
index 00000000..73d93f1c
--- /dev/null
+++ b/kr/kr_glossary.md
@@ -0,0 +1,93 @@
+# 한글 번역 용어집
+
+- 정점: Vertex
+- 프로그램가능한: Programmable
+- 명령: Command
+- 제출: Submit
+- 확장: Extension
+- 기술/기술자: Describe/Descriptor
+- 장치: Device
+- 큐 패밀리: Queue Family
+- 윈도우 표면: Window Surface
+- 스왑 체인: Swap Chain
+- 렌더 타겟: Render Target
+- 프레임: Frame
+- 표시: Present/Presentation
+- 그리기: Draw
+- 드로우 콜: Draw Call
+- 래핑: Wrap(ping)
+- 스텐실: Stensil
+- 렌더 패스: Render Pass
+- 슬롯: Slot
+- 바인딩: Bind/Bound/Binding
+- 지움/지우기: Clear
+- 혼합/블렌딩: Blend/Blending
+- 풀: Pool
+- 유니폼: Uniform
+- 버퍼: Buffer
+- 소멸: Destroy
+- 할당/할당자: Allocate/Allocator
+- 검증 레이어: Validation Layer
+- 윈도우즈(OS): Windows
+- 오프 스크린: Off-screen
+- 질의: Query
+- 프레임버퍼: Framebuffer
+- 주사율: Refresh Rate
+- 동기/동기화: Synchronize/Synchronization
+- 집합: Set
+- 깊이: Depth
+- 부호 있는/없는: Signed/Unsigned
+- 테어링: Tearing
+- 수직 동기화: Vertical Sync
+- 수직 공백: Vertical Blank
+- 지연시간: Latency
+- 범위: Extent
+- 어태치먼트: Attachment
+- 후처리: Post-processing
+- 동시성: Concurrent/Concurrency
+- 독점: Exclusive
+- 변환: Transform(ation)
+- 뒤집기: Flip
+- 클리핑: Clipping
+- 컨테이너: Container
+- 이미지 뷰: Image View
+- 밉맵: Mipmap
+- 텍스처: Texture
+- 큐브 맵: Cube Map
+- 기본: Default
+- 메쉬: Mesh
+- 입력 조립기: Input Assembler
+- 셰이더: Shader
+- 불변: Immutable
+- 뷰포트: Viewport
+- 행렬: Matrix
+- 외적: Cross Product
+- 프래그먼트: Fragment
+- 클립 좌표: Clip Coordinate
+- 정규화된 장치 좌표: Normalized Device Coordinate
+- 동차 좌표: Homogeneous Coordinate
+- 정렬: Align(ment)
+- 시저: Scissor
+- 래스터화: Rasterization
+- 편향: Bias
+- Push 상수: Push Constant
+- 서브패스: Subpass
+- 세마포어: Semaphore
+- 시그널 상태: Signaled
+- 시그널이 아닌 상태: Unsignaled
+- 블러킹: Blocking
+- 펜스: Fence
+- 종속성/종속/의존: Dependency
+- 아이들(링): Idel(ing)
+- 데드락: Deadlock
+- 스테이징 버퍼: Staging Buffer
+- 어트리뷰트: Attribute
+- 오프셋: Offset
+- 맵핑: Map(ping)
+- 플러싱: Flush(ing)
+- 전송: Transfer
+- 사용법: Usage
+- 소스: Source
+- 목적지: Destination
+- 앨리어싱: Aliasing
+- 투영: Projection
\ No newline at end of file

From 191faaa203b43fbf6279ece0de8b6ed7f01f85d5 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Thu, 15 Feb 2024 20:43:58 +0900
Subject: [PATCH 36/47] kr translate 05-01 descriptor pool and sets

---
 .../01_Descriptor_pool_and_sets.md            | 170 ++++++------------
 kr/kr_glossary.md                             |   3 +-
 2 files changed, 55 insertions(+), 118 deletions(-)

diff --git a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
index 5e373f68..05d0ef78 100644
--- a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
+++ b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
@@ -1,16 +1,10 @@
-## Introduction
+## 개요
 
-The descriptor layout from the previous chapter describes the type of
-descriptors that can be bound. In this chapter we're going to create
-a descriptor set for each `VkBuffer` resource to bind it to the
-uniform buffer descriptor.
+이전 장에서의 기술자 레이아웃은 바인딩 될 수 있는 기술자의 타입을 명시합니다. 이 장에서는 각 `VkBuffer` 리소스를 위한 기술자 집합을 만들어서 유니폼 버퍼 기술자에 바인딩할 것입니다.
 
-## Descriptor pool
+## 기술자 풀
 
-Descriptor sets can't be created directly, they must be allocated from a pool
-like command buffers. The equivalent for descriptor sets is unsurprisingly
-called a *descriptor pool*. We'll write a new function `createDescriptorPool`
-to set it up.
+기술자 집합은 직접 만들 수 없고 명령 버퍼처럼 풀로부터 할당되어야 합니다. 기술자 집합에서 이에 대응하는 것은 당연하게도 *기술자 풀*입니다. 이를 설정하기 위해 `createDescriptorPool` 함수를 새로 작성합니다.
 
 ```c++
 void initVulkan() {
@@ -27,8 +21,7 @@ void createDescriptorPool() {
 }
 ```
 
-We first need to describe which descriptor types our descriptor sets are going
-to contain and how many of them, using `VkDescriptorPoolSize` structures.
+먼저 기술자 집합이 어떤 기술자 타입을 포함할 것인지, 몇 개를 포함할 것인지를 `VkDescriptorPoolSize` 구조체를 통해 명시합니다.
 
 ```c++
 VkDescriptorPoolSize poolSize{};
@@ -36,8 +29,7 @@ poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
 poolSize.descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
 ```
 
-We will allocate one of these descriptors for every frame. This
-pool size structure is referenced by the main `VkDescriptorPoolCreateInfo`:
+이 기술자 중 하나를 매 프레임 할당할 것입니다. 이 풀 크기에 대한 구조체는 `VkDescriptorPoolCreateInfo`에서 참조됩니다: 
 
 ```c++
 VkDescriptorPoolCreateInfo poolInfo{};
@@ -46,19 +38,13 @@ poolInfo.poolSizeCount = 1;
 poolInfo.pPoolSizes = &poolSize;
 ```
 
-Aside from the maximum number of individual descriptors that are available, we
-also need to specify the maximum number of descriptor sets that may be
-allocated:
+가용한 개별 기술자의 최대 숫자와는 별개로 할당될 기술자 집합의 최대 숫자도 명시해야 합니다:
 
 ```c++
 poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
 ```
 
-The structure has an optional flag similar to command pools that determines if
-individual descriptor sets can be freed or not:
-`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. We're not going to touch
-the descriptor set after creating it, so we don't need this flag. You can leave
-`flags` to its default value of `0`.
+명령 풀과 유사하게 이 구조체도 개별 기술자 집합이 해제가 될 수 있을지에 대한 선택적인 플래그로 `VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`가 존재합니다. 기술자 세트는 생성한 이후에 건들지 않을 것이므로 이 플래그를 사용하지는 않을 것입니다. 따라서 `flags`는 기본값인 `0`으로 두면 됩니다.
 
 ```c++
 VkDescriptorPool descriptorPool;
@@ -70,13 +56,11 @@ if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SU
 }
 ```
 
-Add a new class member to store the handle of the descriptor pool and call
-`vkCreateDescriptorPool` to create it.
+기술자 풀의 핸들을 저장하기 위한 클래스 멤버를 추가하고 `vkCreateDescriptorPool`를 호출하여 생성합니다.
 
-## Descriptor set
+## 기술자 집합
 
-We can now allocate the descriptor sets themselves. Add a `createDescriptorSets`
-function for that purpose:
+이제 기술자 집합을 할당할 수 있습니다. 이를 위해 `createDescriptorSets` 함수를 추가합니다:
 
 ```c++
 void initVulkan() {
@@ -93,9 +77,7 @@ void createDescriptorSets() {
 }
 ```
 
-A descriptor set allocation is described with a `VkDescriptorSetAllocateInfo`
-struct. You need to specify the descriptor pool to allocate from, the number of
-descriptor sets to allocate, and the descriptor layout to base them on:
+기술자 집합의 할당은 `VkDescriptorSetAllocateInfo` 구조체를 사용합니다. 어떤 기술자 풀에서 할당할 것인지, 기술자 집합을 몇 개나 할당할 것인지, 기반이 되는 기술자 레이아웃이 무엇인지 등을 명시합니다:
 
 ```c++
 std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout);
@@ -106,11 +88,9 @@ allocInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
 allocInfo.pSetLayouts = layouts.data();
 ```
 
-In our case we will create one descriptor set for each frame in flight, all with the same layout.
-Unfortunately we do need all the copies of the layout because the next function expects an array matching the number of sets.
+우리의 경우 사용 중인 각 프레임마다 하나의 기술자 집합을 생성할 것이고, 레이아웃은 모두 동일합니다. 안타깝게도 이 레이아웃들을 모두 복사해야만 하는데 이 다음 함수에서 집합의 개수와 배열의 개수가 일치되어야 하기 떄문입니다.
 
-Add a class member to hold the descriptor set handles and allocate them with
-`vkAllocateDescriptorSets`:
+기술자 집합 핸들을 저장할 클래스 멤버를 추가하고 `vkAllocateDescriptorSets`를 사용해 할당합니다:
 
 ```c++
 VkDescriptorPool descriptorPool;
@@ -124,10 +104,7 @@ if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SU
 }
 ```
 
-You don't need to explicitly clean up descriptor sets, because they will be
-automatically freed when the descriptor pool is destroyed. The call to
-`vkAllocateDescriptorSets` will allocate descriptor sets, each with one uniform
-buffer descriptor.
+기술자 집합은 기술자 풀이 소멸될 때 자동으로 해제되므로 명시적으로 정리해 줄 필요는 없습니다. `vkAllocateDescriptorSets` 호출은 기술자 집합을 할당하고 각각은 하나의 유니폼 버퍼 기술자를 갖고 있습니다.
 
 ```c++
 void cleanup() {
@@ -139,8 +116,7 @@ void cleanup() {
 }
 ```
 
-The descriptor sets have been allocated now, but the descriptors within still need
-to be configured. We'll now add a loop to populate every descriptor:
+이제 기술자 집합은 할당되었으나 그 안의 기술자에 대한 구성이 남아 있습니다. 이제 각 기술자를 생성하기 위한 반복문을 추가합니다.
 
 ```c++
 for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -148,10 +124,7 @@ for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
 }
 ```
 
-Descriptors that refer to buffers, like our uniform buffer
-descriptor, are configured with a `VkDescriptorBufferInfo` struct. This
-structure specifies the buffer and the region within it that contains the data
-for the descriptor.
+우리 유니폼 버퍼 기술자와 같이, 버퍼를 참조하는 기술자는 `VkDescriptorBufferInfo` 구조체로 설정할 수 있습니다. 이 구조체는 버퍼와 데이터가 들어있는 버퍼의 영역을 명시합니다.
 
 ```c++
 for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -162,8 +135,7 @@ for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
 }
 ```
 
-If you're overwriting the whole buffer, like we are in this case, then it is also possible to use the `VK_WHOLE_SIZE` value for the range. The configuration of descriptors is updated using the `vkUpdateDescriptorSets`
-function, which takes an array of `VkWriteDescriptorSet` structs as parameter.
+지금 우리가 하는 것처럼 전체 버퍼를 덮어쓰는 상황이라면 range에 `VK_WHOLE_SIZE`를 사용해도 됩니다. 기술자의 구성은 `vkUpdateDescriptorSets` 함수를 사용해 갱신되는데 `VkWriteDescriptorSet` 구조체의 배열을 매개변수로 받습니다. 
 
 ```c++
 VkWriteDescriptorSet descriptorWrite{};
@@ -173,20 +145,14 @@ descriptorWrite.dstBinding = 0;
 descriptorWrite.dstArrayElement = 0;
 ```
 
-The first two fields specify the descriptor set to update and the binding. We
-gave our uniform buffer binding index `0`. Remember that descriptors can be
-arrays, so we also need to specify the first index in the array that we want to
-update. We're not using an array, so the index is simply `0`.
+첫 두 필드는 갱신하고 바인딩할 기술자 집합을 명시합니다. 우리는 유니폼 버퍼 바인딩 인덱스로 `0`을 부여했습니다. 기술자는 배열일 수도 있으므로 갱신하고자 하는 첫 인덱스를 명시해 주어야 합니다. 지금은 배열이 아니므로 인덱스로는 `0`을 사용합니다.
 
 ```c++
 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
 descriptorWrite.descriptorCount = 1;
 ```
 
-We need to specify the type of descriptor again. It's possible to update
-multiple descriptors at once in an array, starting at index `dstArrayElement`.
-The `descriptorCount` field specifies how many array elements you want to
-update.
+기술자의 타입을 다시 명시해 주어야 합니다. `dstArrayElement` 인덱스부터 시작해서 배열의 여러 기술자를 한꺼번에 갱신하는 것이 가능합니다. `descriptorCount` 필드가 갱신할 배열의 요소 개수를 명시하게 됩니다.
 
 ```c++
 descriptorWrite.pBufferInfo = &bufferInfo;
@@ -194,66 +160,41 @@ descriptorWrite.pImageInfo = nullptr; // Optional
 descriptorWrite.pTexelBufferView = nullptr; // Optional
 ```
 
-The last field references an array with `descriptorCount` structs that actually
-configure the descriptors. It depends on the type of descriptor which one of the
-three you actually need to use. The `pBufferInfo` field is used for descriptors
-that refer to buffer data, `pImageInfo` is used for descriptors that refer to
-image data, and `pTexelBufferView` is used for descriptors that refer to buffer
-views. Our descriptor is based on buffers, so we're using `pBufferInfo`.
+마지막 필드는 실제 기술자를 구성할 `descriptorCount`개의 구조체 배열을 참조합니다. 셋 중에 실제로 사용할 것이 무엇인지에 따라 달라집니다. `pBufferInfo` 필드는 버퍼 데이터를 참조하는 경우 사용되고, `pImageInfo`는 이미지 데이터를 참조하는 경우, `pTexelBufferView`는 버퍼 뷰를 참조하는 기술자에 대해 사용됩니다. 우리의 경우 버퍼를 참조하므로 `pBufferInfo`를 사용합니다.
 
 ```c++
 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
 ```
 
-The updates are applied using `vkUpdateDescriptorSets`. It accepts two kinds of
-arrays as parameters: an array of `VkWriteDescriptorSet` and an array of
-`VkCopyDescriptorSet`. The latter can be used to copy descriptors to each other,
-as its name implies.
+갱신은 `vkUpdateDescriptorSets`를 사용해 이루어집니다. 두 종류의 배열을 매개변수로 받는데 `VkWriteDescriptorSet`과 `VkCopyDescriptorSet` 입니다. 후자는 이름 그대로 기술자들끼리 복사할 때 사용됩니다.
 
-## Using descriptor sets
+## 기술자 집합 사용
 
-We now need to update the `recordCommandBuffer` function to actually bind the
-right descriptor set for each frame to the descriptors in the shader with `vkCmdBindDescriptorSets`. This needs to be done before the `vkCmdDrawIndexed` call:
+이제 `recordCommandBuffer` 함수를 갱신해서 실제로 각 프레임에 대한 올바른 기술자 세트를 셰이더의 기술자와 `vkCmdBindDescriptorSets`를 통해 바인딩해야 합니다. 이는 `vkCmdDrawIndexed` 호출 전에 이루어져야 합니다:
 
 ```c++
 vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr);
 vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
 ```
 
-Unlike vertex and index buffers, descriptor sets are not unique to graphics
-pipelines. Therefore we need to specify if we want to bind descriptor sets to
-the graphics or compute pipeline. The next parameter is the layout that the
-descriptors are based on. The next three parameters specify the index of the
-first descriptor set, the number of sets to bind, and the array of sets to bind.
-We'll get back to this in a moment. The last two parameters specify an array of
-offsets that are used for dynamic descriptors. We'll look at these in a future
-chapter.
-
-If you run your program now, then you'll notice that unfortunately nothing is
-visible. The problem is that because of the Y-flip we did in the projection
-matrix, the vertices are now being drawn in counter-clockwise order instead of
-clockwise order. This causes backface culling to kick in and prevents
-any geometry from being drawn. Go to the `createGraphicsPipeline` function and
-modify the `frontFace` in `VkPipelineRasterizationStateCreateInfo` to correct
-this:
+정점 버퍼나 인덱스 버퍼와는 다르게, 기술자 집합은 그래픽스 파이프라인에서만 사용되는 것은 아닙니다. 따라서 기술자 집합을 그래픽스 또는 컴퓨트 파이프라인 중 어디에 사용할 것인지를 명시해야 합니다. 다음 매개변수는 기술자가 기반으로 하는 레이아웃입니다. 그 다음 세 개의 매개변수는 첫 기술자 집합의 인덱스와 바인딩할 집합의 개수, 그리고 바인딩할 집합의 배열입니다. 이에 대해선 잠시 뒤에 다시 실펴볼 것입니다. 마지막 두 개의 매개변수는 동적(dynamic) 기술자를 사용할 때를 위한 오프셋의 배열을 명시합니다. 이에 대해서는 나중 챕터에서 알아보겠습니다.
+
+지금 시점에 프로그램을 실행하면 아무것도 보이지 않을 겁니다. 문제는 우리가 투영 행렬에 Y 뒤집기를 수행했기 때문에 정점이 시계방향 순서가 아닌 반시계 방향 순서로 그려진다는 것입니다. 이로 인해 후면 컬링(backface culling)이 동작하여 아무것도 그려지지 않게 됩니다. `createGraphicsPipeline` 함수로 가서  `VkPipelineRasterizationStateCreateInfo`의 `frontFace`를 바로잡아줍니다:
 
 ```c++
 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
 ```
 
-Run your program again and you should now see the following:
+이제 다시 실행해보면 아래와 같이 보일겁니다:
 
 ![](/images/spinning_quad.png)
 
-The rectangle has changed into a square because the projection matrix now
-corrects for aspect ratio. The `updateUniformBuffer` takes care of screen
-resizing, so we don't need to recreate the descriptor set in
-`recreateSwapChain`.
+이제 투영 행렬이 종횡비에 맞게 투영하므로 사각형이 정사각형으로 보입니다. `updateUniformBuffer`가 화면 크기 변경을 처리하므로 `recreateSwapChain`에서 기술자 집합을 다시 생성할 필요는 없습니다.
 
-## Alignment requirements
+## 정렬 요구조건(Alignment requirements)
 
-One thing we've glossed over so far is how exactly the data in the C++ structure should match with the uniform definition in the shader. It seems obvious enough to simply use the same types in both:
+지금까지 대충 넘어갔던 것 중의 하나는 셰이더에서의 유니폼 정의와 C++ 구조체가 어떻게 일치해야 하는가에 관한 것입니다. 양 쪽에 동일한 타입을 사용하는 것이 당연해 보입니다:
 
 ```c++
 struct UniformBufferObject {
@@ -269,7 +210,7 @@ layout(binding = 0) uniform UniformBufferObject {
 } ubo;
 ```
 
-However, that's not all there is to it. For example, try modifying the struct and shader to look like this:
+하지만 그냥 이것으로 끝은 아닙니다. 예를 들어 구조체와 셰이더를 아래와 같이 수정해 봅시다:
 
 ```c++
 struct UniformBufferObject {
@@ -287,19 +228,21 @@ layout(binding = 0) uniform UniformBufferObject {
 } ubo;
 ```
 
-Recompile your shader and your program and run it and you'll find that the colorful square you worked so far has disappeared! That's because we haven't taken into account the *alignment requirements*.
+셰이더를 다시 컴파일하고 프로그램을 실행하면 지금까지 보였던 사각형이 사라진 것을 볼 수 있습니다! 왜냐하면 *정렬 요구조건*을 고려하지 않았기 때문입니다.
+
+Vulkan은 구조체의 데이터가 메모리에 특정한 방식으로 정렬되어 있을 것이라고 예상합니다. 예를 들어:
 
-Vulkan expects the data in your structure to be aligned in memory in a specific way, for example:
+* 스칼라 값은 N으로 정렬 (= 32비트 float의 경우 4바이트)
+* `vec2`는 2N으로 정렬 (= 8바이트)
+* `vec3` 또는 `vec4`는 4N으로 정렬 (= 16바이트)
+* 중접된 구조체는 멤버의 기본 정렬을 16의 배수로 반올림한 것으로 정렬
+* `mat4` 행렬은 `vec4`와 동일한 정렬이어야 함
 
-* Scalars have to be aligned by N (= 4 bytes given 32 bit floats).
-* A `vec2` must be aligned by 2N (= 8 bytes)
-* A `vec3` or `vec4` must be aligned by 4N (= 16 bytes)
-* A nested structure must be aligned by the base alignment of its members rounded up to a multiple of 16.
-* A `mat4` matrix must have the same alignment as a `vec4`.
+정렬 요구조건에 대한 전체 내용은 [해당하는 명세](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout)를 보시면 됩니다.
 
-You can find the full list of alignment requirements in [the specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout).
+원래 우리의 셰이더는 세 개의 `mat4` 필드를 사용하였으므로 항상 정렬 요구조건을 만족하였습니다. 각 `mat4`는 4 x 4 x 4 = 64바이트이고, `model`은 오프셋 `0`, `view`는 오프셋 `64`, `proj`는 오프셋 `128`입니다. 각각이 16의 배수이므로 문제가 없었습니다.
 
-Our original shader with just three `mat4` fields already met the alignment requirements. As each `mat4` is 4 x 4 x 4 = 64 bytes in size, `model` has an offset of `0`, `view` has an offset of 64 and `proj` has an offset of 128. All of these are multiples of 16 and that's why it worked fine.
+8바이트 크기인 `vec2`가 추가된 새 구조체로 인해 모든 오프셋이 맞지 않게 됩니다. 이제 `model`은 오프셋 `8`, `view`는 오프셋 `72`, `proj`는 오프셋 `136`이므로 16의 배수가 아닙니다. 이 문제를 해결하기 위해서는 C++11에서 추가된 [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) 지정자를 사용하게 됩니다.
 
 The new structure starts with a `vec2` which is only 8 bytes in size and therefore throws off all of the offsets. Now `model` has an offset of `8`, `view` an offset of `72` and `proj` an offset of `136`, none of which are multiples of 16. To fix this problem we can use the [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) specifier introduced in C++11:
 
@@ -312,9 +255,7 @@ struct UniformBufferObject {
 };
 ```
 
-If you now compile and run your program again you should see that the shader correctly receives its matrix values once again.
-
-Luckily there is a way to not have to think about these alignment requirements *most* of the time. We can define `GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` right before including GLM:
+이제 컴파일하고 다시 실행해 보면 셰이더가 올바를 행렬값을 얻어오는 것을 볼 수 있습니다. GLM을 include하기 직전에 `GLM_FORCE_DEFAULT_ALIGNED_GENTYPES`를 정의할 수 있습니다:
 
 ```c++
 #define GLM_FORCE_RADIANS
@@ -322,9 +263,9 @@ Luckily there is a way to not have to think about these alignment requirements *
 #include <glm/glm.hpp>
 ```
 
-This will force GLM to use a version of `vec2` and `mat4` that has the alignment requirements already specified for us. If you add this definition then you can remove the `alignas` specifier and your program should still work.
+이렇게 하면 GLM이 정렬 요구사항이 이미 만족된 `vec2`와 `mat4`를 사용하게 됩니다. 이 정의를 추가하면 `alignas` 지정자를 없애도 제대로 동작합니다.
 
-Unfortunately this method can break down if you start using nested structures. Consider the following definition in the C++ code:
+안타깝게도 이 방법은 중첩된 구조체를 사용하면 통하지 않게 됩니다. C++에서 아래와 같은 정의를 생각해 보세요:
 
 ```c++
 struct Foo {
@@ -337,7 +278,7 @@ struct UniformBufferObject {
 };
 ```
 
-And the following shader definition:
+그리고 셰이더에서는 다음과 같이 정의했습니다:
 
 ```c++
 struct Foo {
@@ -350,7 +291,7 @@ layout(binding = 0) uniform UniformBufferObject {
 } ubo;
 ```
 
-In this case `f2` will have an offset of `8` whereas it should have an offset of `16` since it is a nested structure. In this case you must specify the alignment yourself:
+이 경우 `f2`는 오프셋 `8`을 갖게 되는데 실제로는 중첩된 구조체이기 때문에 `16`을 가져야만 합니다. 이러한 경우엔 정렬을 직접 명시해 주어야 합니다:
 
 ```c++
 struct UniformBufferObject {
@@ -359,7 +300,7 @@ struct UniformBufferObject {
 };
 ```
 
-These gotchas are a good reason to always be explicit about alignment. That way you won't be caught offguard by the strange symptoms of alignment errors.
+교훈은, 정렬을 언제나 명시해 주는 것이 좋다는 겁니다. 그렇게 하면 정렬 오류로 인해 생기는 이상한 문제들을 방지할 수 있습니다.
 
 ```c++
 struct UniformBufferObject {
@@ -369,22 +310,17 @@ struct UniformBufferObject {
 };
 ```
 
-Don't forget to recompile your shader after removing the `foo` field.
+`foo`를 삭제한 뒤 셰이더를 다시 컴파일하는 것을 잊지 마세요.
 
-## Multiple descriptor sets
+## 다중 기술자 집합
 
-As some of the structures and function calls hinted at, it is actually possible
-to bind multiple descriptor sets simultaneously. You need to specify a descriptor layout for
-each descriptor set when creating the pipeline layout. Shaders can then
-reference specific descriptor sets like this:
+몇몇 구조체와 함수 호출에서 눈치 채실 수 있듯이, 다중 기술자 집합을 동시에 바인딩 하는 것이 가능합니다. 이 경우 각 기술자 집합에 대해 파이프라인 레이아웃 생성시에 기술자 레이아웃을 생성해야 합니다. 셰이더에서는 특정 기술자 집합을 아래와 같이 참조해야 합니다:
 
 ```c++
 layout(set = 0, binding = 0) uniform UniformBufferObject { ... }
 ```
 
-You can use this feature to put descriptors that vary per-object and descriptors
-that are shared into separate descriptor sets. In that case you avoid rebinding
-most of the descriptors across draw calls which is potentially more efficient.
+객체별로 다른 기술자를 사용하거나 별도의 기술자 집합에서 공유하는 기술자를 사용할 때 이러안 기능을 활용할 수 있습니다. 이 경우 드로우 콜마다 대부분의 기술자를 다시 바인딩하지 않아도 되어서 더 효율적일 수 있습니다.
 
 [C++ code](/code/23_descriptor_sets.cpp) /
 [Vertex shader](/code/22_shader_ubo.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index 73d93f1c..b6e1ee47 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -90,4 +90,5 @@
 - 소스: Source
 - 목적지: Destination
 - 앨리어싱: Aliasing
-- 투영: Projection
\ No newline at end of file
+- 투영: Projection
+- 컬링: Culling
\ No newline at end of file

From 0378f74591ac2d3f00e5eeed4a15505cb26d691d Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Sat, 17 Feb 2024 16:20:30 +0900
Subject: [PATCH 37/47] kr translate 06-00 images (needs improvement)

---
 kr/06_Texture_mapping/00_Images.md | 425 ++++++++---------------------
 kr/kr_glossary.md                  |  13 +-
 2 files changed, 124 insertions(+), 314 deletions(-)

diff --git a/kr/06_Texture_mapping/00_Images.md b/kr/06_Texture_mapping/00_Images.md
index 8c9967f6..e6af931d 100644
--- a/kr/06_Texture_mapping/00_Images.md
+++ b/kr/06_Texture_mapping/00_Images.md
@@ -1,74 +1,41 @@
-## Introduction
-
-The geometry has been colored using per-vertex colors so far, which is a rather
-limited approach. In this part of the tutorial we're going to implement texture
-mapping to make the geometry look more interesting. This will also allow us to
-load and draw basic 3D models in a future chapter.
-
-Adding a texture to our application will involve the following steps:
-
-* Create an image object backed by device memory
-* Fill it with pixels from an image file
-* Create an image sampler
-* Add a combined image sampler descriptor to sample colors from the texture
-
-We've already worked with image objects before, but those were automatically
-created by the swap chain extension. This time we'll have to create one by
-ourselves. Creating an image and filling it with data is similar to vertex
-buffer creation. We'll start by creating a staging resource and filling it with
-pixel data and then we copy this to the final image object that we'll use for
-rendering. Although it is possible to create a staging image for this purpose,
-Vulkan also allows you to copy pixels from a `VkBuffer` to an image and the API
-for this is actually [faster on some hardware](https://developer.nvidia.com/vulkan-memory-management).
-We'll first create this buffer and fill it with pixel values, and then we'll
-create an image to copy the pixels to. Creating an image is not very different
-from creating buffers. It involves querying the memory requirements, allocating
-device memory and binding it, just like we've seen before.
-
-However, there is something extra that we'll have to take care of when working
-with images. Images can have different *layouts* that affect how the pixels are
-organized in memory. Due to the way graphics hardware works, simply storing the
-pixels row by row may not lead to the best performance, for example. When
-performing any operation on images, you must make sure that they have the layout
-that is optimal for use in that operation. We've actually already seen some of
-these layouts when we specified the render pass:
-
-* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: Optimal for presentation
-* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Optimal as attachment for writing
-colors from the fragment shader
-* `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`: Optimal as source in a transfer
-operation, like `vkCmdCopyImageToBuffer`
-* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Optimal as destination in a transfer
-operation, like `vkCmdCopyBufferToImage`
-* `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`: Optimal for sampling from a shader
-
-One of the most common ways to transition the layout of an image is a *pipeline
-barrier*. Pipeline barriers are primarily used for synchronizing access to
-resources, like making sure that an image was written to before it is read, but
-they can also be used to transition layouts. In this chapter we'll see how
-pipeline barriers are used for this purpose. Barriers can additionally be used
-to transfer queue family ownership when using `VK_SHARING_MODE_EXCLUSIVE`.
-
-## Image library
-
-There are many libraries available for loading images, and you can even write
-your own code to load simple formats like BMP and PPM. In this tutorial we'll be
-using the stb_image library from the [stb collection](https://github.com/nothings/stb).
-The advantage of it is that all of the code is in a single file, so it doesn't
-require any tricky build configuration. Download `stb_image.h` and store it in a
-convenient location, like the directory where you saved GLFW and GLM. Add the
-location to your include path.
+## 개요
+
+지금까지 물체는 정점별로 할당된 색깔로 표현이 되었지만 이러한 방법으로는 한계가 있습니다. 이 챕터에서는 좀 더 흥미로운 표현을 위해 텍스처 맵핑을 구현해 보도록 하겠습니다. 이를 통해 나중에는 3D 모델을 로딩하고 그리는 것도 가능하게 될겁니다.
+
+텍스처를 프로그램에 추가기 위해서는 아래와 같은 과정이 필요합니다:
+
+* 장치 메모리가 베이크(baked)한 이미지 객체 생성
+* 이미지 객체를 이미지 파일의 픽셀로 채움
+* 이미지 샘플러(sampler) 생성
+* 텍스처로부터 색상을 샘플링할 결합된(combined) 이미지 샘플러 기술자 추가
+
+전에 이미 이미지 객체를 다뤄본 적 있지만 그 경우는 스왑 체인 확장이 자동적으로 만들어준 경우였습니다. 이번에는 직접 만들어야 합니다. 이미지를 만들고 여기에 데이터를 채우는 것은 정점 버퍼 생성과 비슷합니다. 스테이징 리소스를 먼저 만들고 여기에 픽셀 데이터를 채운 뒤 이를 렌더링에 사용할 최종 이미지 객체에 복사할 것입니다. 이러한 목적으로 스테이징 이미지를 만드는 것도 가능하지만 Vulkan에서는 `VkBuffer`로부터 이미지로 픽셀을 복사하는 것이 가능하고 이러한 목적으로 제공되는 API가 실제로 [어떤 하드웨어에서는 더 빠릅니다](https://developer.nvidia.com/vulkan-memory-management).
+
+먼저 이 버퍼를 만들고 픽셀 값으로 채운 뒤 그 픽셀값을 복사할 이미지를 만듭니다. 이미지를 만드는 것은 버퍼를 만드는 것과 크게 다르지 않습니다. 전과 같이 메모리 요구조건을 질의하고, 장치 메모리를 할당하고 바인딩하면 됩니다.
+
+하지만 이미지를 다룰 때 추가적으로 해 주어야 하는 작업이 있습니다. 이미지마다 다른 *레이아웃*을 가질 수 있는데 이는 픽셀들이 메모리에 어떻게 존재하는지에 영향을 미칩니다. 그래픽 하드웨어가 동작하는 방식 때문에 예를들어 픽셀값을 그냥 행별로 나열하는 것은 성능에 좋지 않을 수 있습니다. 이미지에 대해 어떤 연산을 수행할 때 해당 연산에 최적화된 형태의 레이아웃을 가지고 있는지를 확인해야 합니다. 전에 렌더 패스를 명시할 때 이러한 레이아웃을 이미 살펴본 바 있습니다:
+
+* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`: 표시 목적으로 최적
+* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: 프래그먼트 셰이더에서 색상값을 쓰기 위한 어태치먼트로써 최적
+* `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`: `vkCmdCopyImageToBuffer`에서처럼 전송의 소스로써 최적
+* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: `vkCmdCopyBufferToImage`에서처럼 전송의 목적지로써 최적
+* `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`: 셰이더에서 샘플링하는 목적으로 최적
+
+이미지 레이아웃을 전송하는 가장 흔한 방법은 *파이프라인 배리어(barrier)* 입니다. 파이프라인 배리어는 리소스로의 동기화된 접근을 위해 주로 사용되는데, 예를 들자면 이미지에 대한 쓰기 이후 읽기를 보장하기 위해서와 같은 목적입니다. 하지만 이 방법은 레이아웃을 전송하기 위해서도 사용될 수 있습니다. 이 챕터에서 파이프라인 배리어를 이러한 목적으로 사용하는 방법을 살펴볼 것입니다. 배리어는 `VK_SHARING_MODE_EXCLUSIVE`일 때 큐 패밀리의 소유권(ownership) 이전을 위해서도 사용됩니다.
+
+## 이미지 라이브러리
+
+이미지를 로드하기 위한 다양한 라이브러리가 있고, BMP나 PPM과 같은 간단한 포맷은 직접 코드를 작성해도 됩니다. 이 튜토리얼에서 우리는 [stb collection](https://github.com/nothings/stb)의 stb_image를 사용할 예정입니다. 이 라이브러리의 장점은 모든 코드가 파일 하나에 있어서 빌드 구성이 간단해진다는 것입니다. `stb_image.h`를 다운로드하여 편리한 위치, 예를들자면 GLFW와 GLM이 있는 위치에 두십시오. 그리고 그 위치를 include 경로에 추가하십시오.
 
 **Visual Studio**
 
-Add the directory with `stb_image.h` in it to the `Additional Include
-Directories` paths.
+`stb_image.h`가 있는 디렉토리를 `추가 포함 디렉토리` 경로에 추가하십시오.
 
 ![](/images/include_dirs_stb.png)
 
 **Makefile**
 
-Add the directory with `stb_image.h` to the include directories for GCC:
+`stb_image.h`가 있는 디텍초리는 GCC의 include 디렉토리에 추가하십시오:
 
 ```text
 VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64
@@ -79,9 +46,9 @@ STB_INCLUDE_PATH = /home/user/libraries/stb
 CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH)
 ```
 
-## Loading an image
+## 이미지 로딩
 
-Include the image library like this:
+이미지 라이브러리를 아래와 같이 include합니다:
 
 ```c++
 #define STB_IMAGE_IMPLEMENTATION
@@ -108,20 +75,13 @@ void createTextureImage() {
 }
 ```
 
-Create a new function `createTextureImage` where we'll load an image and upload
-it into a Vulkan image object. We're going to use command buffers, so it should
-be called after `createCommandPool`.
+이미지를 로드하고 Vulkan 이미지 객체에 업로드하기 위한 `createTextureImage` 함수를 새로 만듭니다. 명령 버퍼를 사용할 것이기 때문에 `createCommandPool` 뒤에 호출해야 합니다.
 
-Create a new directory `textures` next to the `shaders` directory to store
-texture images in. We're going to load an image called `texture.jpg` from that
-directory. I've chosen to use the following
-[CC0 licensed image](https://pixabay.com/en/statue-sculpture-fig-historically-1275469/)
-resized to 512 x 512 pixels, but feel free to pick any image you want. The
-library supports most common image file formats, like JPEG, PNG, BMP and GIF.
+`shaders` 디렉토리 옆에 이미지를 저장할 `textures` 디렉토리를 새로 만듭니다. `texture.jpg`라는 이미지를 로딩할 예정입니다. 저는 [CC0 라이센스 이미지](https://pixabay.com/en/statue-sculpture-fig-historically-1275469/)를 512 x 512 픽셀로 리사이징하여 사용하기로 했는데 여러분은 원하는 아무 이미지나 사용하십시오. 라이브러리는 JPEG, PNG, BMP, GIF같은 일반적인 이미지 파일 포맷을 지원합니다.
 
 ![](/images/texture.jpg)
 
-Loading an image with this library is really easy:
+라이브러리를 사용해 이미지를 로딩하는 것은 아주 쉽습니다:
 
 ```c++
 void createTextureImage() {
@@ -135,35 +95,24 @@ void createTextureImage() {
 }
 ```
 
-The `stbi_load` function takes the file path and number of channels to load as
-arguments. The `STBI_rgb_alpha` value forces the image to be loaded with an
-alpha channel, even if it doesn't have one, which is nice for consistency with
-other textures in the future. The middle three parameters are outputs for the
-width, height and actual number of channels in the image. The pointer that is
-returned is the first element in an array of pixel values. The pixels are laid
-out row by row with 4 bytes per pixel in the case of `STBI_rgb_alpha` for a
-total of `texWidth * texHeight * 4` values.
+`stbi_load` 함수는 파일 경로와 로드할 채널 개수를 인자로 받습니다. `STBI_rgb_alpha`값은 이미지에 알파 채널이 없어도 알파 채널을 포함하여 로드하도록 되어 있으며 추후 다른 텍스처 포맷을 사용할 때도 일관적인 코드를 사용 가능하므로 좋습니다. 중간의 세 매개변수는 너비, 높이와 이미지의 실제 채널 수의 출력입니다. 반환되는 포인터는 픽셀값 배열의 첫 요소의 포인터입니다. 픽셀들은 각 픽셀별 4바이트로 각 행이 배치되어 있으며 `STBI_rgb_alpha`의 경우 총 `texWidth * texHeight * 4`개의 값이 존재합니다.
 
-## Staging buffer
+## 스테이징 버퍼
 
-We're now going to create a buffer in host visible memory so that we can use
-`vkMapMemory` and copy the pixels to it. Add variables for this temporary buffer
-to the `createTextureImage` function:
+이제 호스트에서 관찰 가능한 버퍼를 만들어 `vkMapMemory`를 사용해 픽셀 데이터를 복사할 수 있도록 하겠습니다. 임시 버퍼에 대한 변수를 `createTextureImage` 함수에 만듭니다:
 
 ```c++
 VkBuffer stagingBuffer;
 VkDeviceMemory stagingBufferMemory;
 ```
 
-The buffer should be in host visible memory so that we can map it and it should
-be usable as a transfer source so that we can copy it to an image later on:
+버퍼는 맵핑이 가능하고 전송의 소스로 활용할 수 있도록 호스트에서 관찰 가능한 곳에 있어야 하며, 그래야 나중에 이미지로 복사할 수 있습니다:
 
 ```c++
 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
 ```
 
-We can then directly copy the pixel values that we got from the image loading
-library to the buffer:
+이제 이미지 로딩 라이브러리부터 얻은 픽셀값을 버퍼로 복사합니다:
 
 ```c++
 void* data;
@@ -172,26 +121,22 @@ vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
 vkUnmapMemory(device, stagingBufferMemory);
 ```
 
-Don't forget to clean up the original pixel array now:
+이 시점에서 원본 픽셀 배열을 정리하는 것을 잊지 마세요:
 
 ```c++
 stbi_image_free(pixels);
 ```
 
-## Texture Image
+## 텍스처 이미지
 
-Although we could set up the shader to access the pixel values in the buffer,
-it's better to use image objects in Vulkan for this purpose. Image objects will
-make it easier and faster to retrieve colors by allowing us to use 2D
-coordinates, for one. Pixels within an image object are known as texels and
-we'll use that name from this point on. Add the following new class members:
+셰이더에서 버퍼의 픽셀 값을 접근하도록 설정할 수도 있지만 이러한 목적으로는 Vulkan의 이미지 객체를 사용하는 것이 더 좋습니다. 이미지 객체를 사용하는 장점 중 하나는 2D 좌표로 색상값을 얻을 수 있어서 더 빠르고 편리하다는 것입니다. 이미지 객체가 갖고있는 픽셀은 텍셀(texel)이라고 하며 여기서부터는 그렇게 지칭하겠습니다. 아래와 같은 클래스 멤버를 추가합니다:
 
 ```c++
 VkImage textureImage;
 VkDeviceMemory textureImageMemory;
 ```
 
-The parameters for an image are specified in a `VkImageCreateInfo` struct:
+이미지에 대한 매개변수는 `VkImageCreateInfo`에 명시됩니다:
 
 ```c++
 VkImageCreateInfo imageInfo{};
@@ -204,89 +149,54 @@ imageInfo.mipLevels = 1;
 imageInfo.arrayLayers = 1;
 ```
 
-The image type, specified in the `imageType` field, tells Vulkan with what kind
-of coordinate system the texels in the image are going to be addressed. It is
-possible to create 1D, 2D and 3D images. One dimensional images can be used to
-store an array of data or gradient, two dimensional images are mainly used for
-textures, and three dimensional images can be used to store voxel volumes, for
-example. The `extent` field specifies the dimensions of the image, basically how
-many texels there are on each axis. That's why `depth` must be `1` instead of
-`0`. Our texture will not be an array and we won't be using mipmapping for now.
+`imageType` 필드에 명시된 이미지 타입은 Vulkan에게 이미지의 텍셀이 어떤 좌표계를 사용하는지를 알려줍니다. 1D, 2D, 3D 이미지가 있습니다. 1차원 이미지는 데이터의 배열이나 그라디언트를 저장하기 위해 사용되고, 2차원 이미지는 주로 텍스처 용도로, 3차원 이미지는 복셀(voxel) 볼륨을 저장하기 위해 사용됩니다. `extent` 필드는 이미지의 크기를 명시하고, 이는 곧 각 축에 몇 개의 텍셀을 가지고 있는지를 의미합니다. 따라서 `depth`는 `0`이 아닌 `1`이어야 합니다. 우리 텍스처는 배열이 아니며 현재는 밉맵핑도 하지 않을 겁니다.
 
 ```c++
 imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
 ```
 
-Vulkan supports many possible image formats, but we should use the same format
-for the texels as the pixels in the buffer, otherwise the copy operation will
-fail.
+Vulkan은 다양한 이미지 포맷을 지원하지만 텍셀과 버퍼의 픽셀 포맷으로 같은 포맷을 사용해야 합니다. 그렇지 않으면 복사 연산이 실패하게 됩니다.
 
 ```c++
 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
 ```
 
-The `tiling` field can have one of two values:
+`tiling` 필드는 다음 두 값중 하나를 가집니다:
 
-* `VK_IMAGE_TILING_LINEAR`: Texels are laid out in row-major order like our
-`pixels` array
-* `VK_IMAGE_TILING_OPTIMAL`: Texels are laid out in an implementation defined
-order for optimal access
+* `VK_IMAGE_TILING_LINEAR`: 우리 `pixels` 배열의 경우처럼 텍셀이 행 우선 순서(row-major order)로 저장됨
+* `VK_IMAGE_TILING_OPTIMAL`: 텍셀이 최적 접근을 위해 구현에서 정의한 순서대로 저장됨
 
-Unlike the layout of an image, the tiling mode cannot be changed at a later
-time. If you want to be able to directly access texels in the memory of the
-image, then you must use `VK_IMAGE_TILING_LINEAR`. We will be using a staging
-buffer instead of a staging image, so this won't be necessary. We will be using
-`VK_IMAGE_TILING_OPTIMAL` for efficient access from the shader.
+이미지의 레이아웃과는 달리 타일링 모드는 나중에 바꿀 수 없습니다. 이미지 메모리의 텍셀에 직접 접근하고 싶다면 `VK_IMAGE_TILING_LINEAR`를 사용해야 합니다. 우리의 경우 스테이징 이미지가 아닌 스테이징 버퍼를 사용하고 있으므로 이렇게 할 필요는 없습니다. 셰이더에서 효율적인 접근이 가능하도록 `VK_IMAGE_TILING_OPTIMAL`를 사용할겁니다.
 
 ```c++
 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 ```
 
-There are only two possible values for the `initialLayout` of an image:
+이미지의 `initialLayout`는 두 가지 값이 가능합니다:
 
-* `VK_IMAGE_LAYOUT_UNDEFINED`: Not usable by the GPU and the very first
-transition will discard the texels.
-* `VK_IMAGE_LAYOUT_PREINITIALIZED`: Not usable by the GPU, but the first
-transition will preserve the texels.
+* `VK_IMAGE_LAYOUT_UNDEFINED`: GPU에서 사용이 불가능하고 첫 전환(transition) 이후 텍셀이 버려짐
+* `VK_IMAGE_LAYOUT_PREINITIALIZED`: GPU에서 사용이 불가능하고 첫 전환 이후 텍셀이 유지됨
 
-There are few situations where it is necessary for the texels to be preserved
-during the first transition. One example, however, would be if you wanted to use
-an image as a staging image in combination with the `VK_IMAGE_TILING_LINEAR`
-layout. In that case, you'd want to upload the texel data to it and then
-transition the image to be a transfer source without losing the data. In our
-case, however, we're first going to transition the image to be a transfer
-destination and then copy texel data to it from a buffer object, so we don't
-need this property and can safely use `VK_IMAGE_LAYOUT_UNDEFINED`.
+첫 전환 이후에 텍셀이 유지되어야 하는 경우는 별로 없습니다. 유지되어야 하는 한 예로 `VK_IMAGE_TILING_LINEAR`와 함께 이미지를 스테이징 이미지로 활용하는 경우가 있습니다. 이 경우 텍셀 데이터를 업로드한 이후에 이미지를 전송의 소스로 전환하며, 데이터를 버리지 않습니다. 하지만 우리의 경우 먼저 이미지를 전송의 목적지로 전환한 후 버퍼 객체로부터 텍셀 데이터를 복사하기 때문에 이러한 속성이 필요없고 따라서 `VK_IMAGE_LAYOUT_UNDEFINED`를 사용해도 됩니다.
 
 ```c++
 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
 ```
 
-The `usage` field has the same semantics as the one during buffer creation. The
-image is going to be used as destination for the buffer copy, so it should be
-set up as a transfer destination. We also want to be able to access the image
-from the shader to color our mesh, so the usage should include
-`VK_IMAGE_USAGE_SAMPLED_BIT`.
+`usage` 필드는 버퍼 생성과 동일한 의미를 갖습니다. 이미지는 버퍼 복사의 목적지로 활용될 것입니다. 또한 이미지는 셰이더에서 메쉬의 색상을 결정하기 위해 활용될 예정이므로 사용법에는 `VK_IMAGE_USAGE_SAMPLED_BIT`가 포함되어야 합니다.
 
 ```c++
 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
 ```
 
-The image will only be used by one queue family: the one that supports graphics
-(and therefore also) transfer operations.
+이미지는 하나의 큐 패밀리에서만 활용될 예정입니다. 전송 연산이 가능한 그래픽스 큐 패밀리입니다.
 
 ```c++
 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
 imageInfo.flags = 0; // Optional
 ```
 
-The `samples` flag is related to multisampling. This is only relevant for images
-that will be used as attachments, so stick to one sample. There are some
-optional flags for images that are related to sparse images. Sparse images are
-images where only certain regions are actually backed by memory. If you were
-using a 3D texture for a voxel terrain, for example, then you could use this to
-avoid allocating memory to store large volumes of "air" values. We won't be
-using it in this tutorial, so leave it to its default value of `0`.
+`samples` 플래그는 멀티샘플링과 관련되어 있습니다. 이는 이미지가 어태치먼트로 활용될때만 의미가 있으므로 샘플은 1로 둡니다. 희박한(sparse) 이미지의 경우에 대한 선택적인 플래그들이 몇 가지 있습니다. 희박한 이미지란 특정 영역만이 베이킹되는 이미지입니다. 예를 들어 복셀 지형을 위해 3D 텍스처를 사용한다고 하면 아무 것도 없는 영역에 대한 메모리 할당을 피하기 위해서 이러한 이미지를 사용할 수 있습니다. 이 튜토리얼에서는 이러한 사용 사례가 없으므로 기본값인 `0`으로 두겠습니다.
 
 ```c++
 if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
@@ -294,13 +204,7 @@ if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
 }
 ```
 
-The image is created using `vkCreateImage`, which doesn't have any particularly
-noteworthy parameters. It is possible that the `VK_FORMAT_R8G8B8A8_SRGB` format
-is not supported by the graphics hardware. You should have a list of acceptable
-alternatives and go with the best one that is supported. However, support for
-this particular format is so widespread that we'll skip this step. Using
-different formats would also require annoying conversions. We will get back to
-this in the depth buffer chapter, where we'll implement such a system.
+이미지는 `vkCreateImage`로 만들어지고 특별히 언급할만한 매개변수는 없습니다. `VK_FORMAT_R8G8B8A8_SRGB` 포맷을 하드웨어가 지원하지 않는 경우가 있을 수 있습니다. 가능한 대안들의 목록을 가지고 있다가 지원되는 가장 괜찮은 것을 사용해야 합니다. 하지만 이 포맷은 널리 지원되므로 이러한 처리 과정을 지금은 넘어가겠습니다. 다른 포맷을 사용하려면 좀 귀찮은 변환 과정을 수행해야 합니다. 깊이 버퍼 챕터에서 이러한 시스템을 구현하면서 다시 살펴볼 것입니다.
 
 ```c++
 VkMemoryRequirements memRequirements;
@@ -318,15 +222,9 @@ if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUC
 vkBindImageMemory(device, textureImage, textureImageMemory, 0);
 ```
 
-Allocating memory for an image works in exactly the same way as allocating
-memory for a buffer. Use `vkGetImageMemoryRequirements` instead of
-`vkGetBufferMemoryRequirements`, and use `vkBindImageMemory` instead of
-`vkBindBufferMemory`.
+이미지를 위한 메모리 할당은 버퍼 메모리 할당과 완전히 동일합니다. `vkGetBufferMemoryRequirements` 대신에 `vkGetImageMemoryRequirements`를 사용하고, `vkBindBufferMemory` 대신에 `vkBindImageMemory`를 사용합니다.
 
-This function is already getting quite large and there'll be a need to create
-more images in later chapters, so we should abstract image creation into a
-`createImage` function, like we did for buffers. Create the function and move
-the image object creation and memory allocation to it:
+함수가 꽤 커졌고, 나중 챕터에서는 이미지를 더 만들어야 하기 때문에 이미지 생성은 버퍼에서처럼 `createImage` 함수로 추상화해야 합니다. 함수를 만들고 이미지 객체의 생성과 메모리 할당을 이 곳으로 옮깁니다:
 
 ```c++
 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
@@ -365,11 +263,9 @@ void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling
 }
 ```
 
-I've made the width, height, format, tiling mode, usage, and memory properties
-parameters, because these will all vary between the images we'll be creating
-throughout this tutorial.
+너비, 높이, 포맷, 타일링 모드, 사용법, 메모리 속성을 매개변수로 만들었는데 이것들은 앞으로 튜토리얼에서 만들 이미지마다 다르기 때문입니다.
 
-The `createTextureImage` function can now be simplified to:
+`createTextureImage` 함수는 이제 아래와 같이 간략화됩니다:
 
 ```c++
 void createTextureImage() {
@@ -396,11 +292,9 @@ void createTextureImage() {
 }
 ```
 
-## Layout transitions
+## 레이아웃 전환(transitions)
 
-The function we're going to write now involves recording and executing a command
-buffer again, so now's a good time to move that logic into a helper function or
-two:
+이제 작성할 함수는 또한번 명령 버퍼를 기록하고 실행하는 부분이며 이에 따라 이러한 로직은 한두개의 헬퍼 함수로 옮기는 것이 좋겠습니다:
 
 ```c++
 VkCommandBuffer beginSingleTimeCommands() {
@@ -437,8 +331,7 @@ void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
 }
 ```
 
-The code for these functions is based on the existing code in `copyBuffer`. You
-can now simplify that function to:
+이 코드는 기존의 `copyBuffer`에 있던 코드에 기반해 만들어졌습니다. 이제 해당 함수는 아래와 같아집니다:
 
 ```c++
 void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
@@ -452,10 +345,7 @@ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
 }
 ```
 
-If we were still using buffers, then we could now write a function to record and
-execute `vkCmdCopyBufferToImage` to finish the job, but this command requires
-the image to be in the right layout first. Create a new function to handle
-layout transitions:
+아직 버퍼를 사용 중이라면 `vkCmdCopyBufferToImage`를 기록하고 실행하는 함수를 만들어 작업을 완료할 수도 있습니다. 하지만 이 명령은 먼저 이미지가 올바른 레이아웃에 있는 것을 요구합니다. 레이아웃 전환을 위한 함수를 새로 만듭니다:
 
 ```c++
 void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) {
@@ -465,12 +355,7 @@ void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayo
 }
 ```
 
-One of the most common ways to perform layout transitions is using an *image
-memory barrier*. A pipeline barrier like that is generally used to synchronize
-access to resources, like ensuring that a write to a buffer completes before
-reading from it, but it can also be used to transition image layouts and
-transfer queue family ownership when `VK_SHARING_MODE_EXCLUSIVE` is used. There
-is an equivalent *buffer memory barrier* to do this for buffers.
+레이아웃 전환을 위한 가장 일반적인 방법은 *이미지 메모리 배리어*를 사용하는 것입니다. 이와 같은 파이프라인 배리어는 리소스에 대한 접근을 동기화하기 위해 사용되는데 예를 들자면 버퍼에 값을 쓰는 것이 읽기 전에 끝나야 하는 것을 보장하기 위해서와 같은 것입니다. 하지만 또한 이미지 레이아웃을 전환하고 `VK_SHARING_MODE_EXCLUSIVE`가 사용될 때 큐 패밀리 소유권을 이전하는 데에도 사용됩니다. 버퍼에 대해서는 대응되는 *버퍼 메모리 배리어*라는 것이 존재합니다.
 
 ```c++
 VkImageMemoryBarrier barrier{};
@@ -479,18 +364,14 @@ barrier.oldLayout = oldLayout;
 barrier.newLayout = newLayout;
 ```
 
-The first two fields specify layout transition. It is possible to use
-`VK_IMAGE_LAYOUT_UNDEFINED` as `oldLayout` if you don't care about the existing
-contents of the image.
+첫 두 필드는 레이아웃 전환을 명시합니다. 이미지에 존재하는 내용을 상관하지 않는다면 `oldLayout`에는 `VK_IMAGE_LAYOUT_UNDEFINED`를 사용할 수도 있습니다.
 
 ```c++
 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
 ```
 
-If you are using the barrier to transfer queue family ownership, then these two
-fields should be the indices of the queue families. They must be set to
-`VK_QUEUE_FAMILY_IGNORED` if you don't want to do this (not the default value!).
+큐 패밀리 소유권 이전을 위해 배리어를 사용한다면, 이 두 필드는 큐 패밀리의 인덱스여야 합니다. 그렇지 않은 경우에는 `VK_QUEUE_FAMILY_IGNORED`로 설정해야 합니다 (이 값이 기본값이 아닙니다!).
 
 ```c++
 barrier.image = image;
@@ -501,21 +382,14 @@ barrier.subresourceRange.baseArrayLayer = 0;
 barrier.subresourceRange.layerCount = 1;
 ```
 
-The `image` and `subresourceRange` specify the image that is affected and the
-specific part of the image. Our image is not an array and does not have mipmapping
-levels, so only one level and layer are specified.
+`image`와 `subresourceRange`는 영향을 받는 이미지와 이미지의 특정 영역을 명시합니다. 우리 이미지는 배열도 아니고 밉맵 레벨도 없으므로 1 레벨과 하나의 레이어로 명시합니다.
 
 ```c++
 barrier.srcAccessMask = 0; // TODO
 barrier.dstAccessMask = 0; // TODO
 ```
 
-Barriers are primarily used for synchronization purposes, so you must specify
-which types of operations that involve the resource must happen before the
-barrier, and which operations that involve the resource must wait on the
-barrier. We need to do that despite already using `vkQueueWaitIdle` to manually
-synchronize. The right values depend on the old and new layout, so we'll get
-back to this once we've figured out which transitions we're going to use.
+배리어의 주 목적은 동기화이므로 리소스와 관련한 어떤 종류의 연산이 배리어 앞에 오고 어떤 연산이 배리어를 대기해야 하는시를 명시해야 합니다. 이미 `vkQueueWaitIdle`를 사용해 매뉴얼하게 동기화 하고 있지만 그래도 해 주어야 합니다. 올바른 값은 old와 new 레이아웃에 달려 있으며 어떤 전환할 수행할 것인지를 알게 된 후에 다시 돌아오겠습니다.
 
 ```c++
 vkCmdPipelineBarrier(
@@ -528,36 +402,18 @@ vkCmdPipelineBarrier(
 );
 ```
 
-All types of pipeline barriers are submitted using the same function. The first
-parameter after the command buffer specifies in which pipeline stage the
-operations occur that should happen before the barrier. The second parameter
-specifies the pipeline stage in which operations will wait on the barrier. The
-pipeline stages that you are allowed to specify before and after the barrier
-depend on how you use the resource before and after the barrier. The allowed
-values are listed in [this table](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-access-types-supported)
-of the specification. For example, if you're going to read from a uniform after
+모든 파이프라인 배리어는 같은 함수로 제출됩니다. 명령 버퍼 다음으로 오는 첫 매개변수는 배리어 앞에 수행되어야 할 연산의 파이프라인 스테이지를 명시합니다. 두 번째 매개변수는 배리어를 대기할 파이프라인 스테이지를 명시합니다. 배리어 앞과 뒤에 명시할 수 있는 파이프라인의 스테이지는 배리어 전후에 리소스를 어떻게 사용할 것인지에 달려 있습니다. 가능한 값의 목록은 명세의 [이 표](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-access-types-supported)에 있습니다. For example, if you're going to read from a uniform after
 the barrier, you would specify a usage of `VK_ACCESS_UNIFORM_READ_BIT` and the
 earliest shader that will read from the uniform as pipeline stage, for example
-`VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT`. It would not make sense to specify
-a non-shader pipeline stage for this type of usage and the validation layers
-will warn you when you specify a pipeline stage that does not match the type of
-usage.
-
-The third parameter is either `0` or `VK_DEPENDENCY_BY_REGION_BIT`. The latter
-turns the barrier into a per-region condition. That means that the
-implementation is allowed to already begin reading from the parts of a resource
-that were written so far, for example.
-
-The last three pairs of parameters reference arrays of pipeline barriers of the
-three available types: memory barriers, buffer memory barriers, and image memory
-barriers like the one we're using here. Note that we're not using the `VkFormat`
-parameter yet, but we'll be using that one for special transitions in the depth
-buffer chapter.
+`VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT`. 셰이더가 아닌 파이프라인 스테이지에 이러한 사용법을 명시한다면 검증 레이어가 용법에 맞지 않는 파이프라인 스테이지를 명시했다고 경고를 낼 것입니다.
+
+세 번째 매개변수는 `0` 또는 `VK_DEPENDENCY_BY_REGION_BIT`입니다. 후자는 배리어를 영역별 조건으로 바꿉니다. 즉, 예를 들자면 구현이 현재까지 쓰기가 완료된 리소스의 일부분을 읽을 수 있게 됩니다.
+
+마지막 세 개의 매개변수는 세 종류의 타입에 대한 파이프라인 배리어의 배열에 대한 참조입니다. 세 종류 타입은 메모리 배리어, 버퍼 메모리 배리어, 이미지 메모리 배리어이고 현재는 마지막 것을 사용입니다. `VkFormat` 매개변수는 아직 사용하지 않는 것에 유의하세요. 이는 깊이 버퍼 챕터에서 특수한 전환을 위해 사용할 예정입니다.
 
 ## Copying buffer to image
 
-Before we get back to `createTextureImage`, we're going to write one more helper
-function: `copyBufferToImage`:
+`createTextureImage`로 다시 돌아가기 전에 추가적인 헬퍼 함수 `copyBufferToImage`를 작성하겠습니다:
 
 ```c++
 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
@@ -567,9 +423,7 @@ void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t
 }
 ```
 
-Just like with buffer copies, you need to specify which part of the buffer is
-going to be copied to which part of the image. This happens through
-`VkBufferImageCopy` structs:
+버퍼의 복사와 마찬가지로 버퍼의 어떤 부분이 이미지의 어떤 부분으로 복사될 것인지를 명시해야 합니다. 이는 `VkBufferImageCopy` 구조체를 통해 명시됩니다:
 
 ```c++
 VkBufferImageCopy region{};
@@ -590,16 +444,9 @@ region.imageExtent = {
 };
 ```
 
-Most of these fields are self-explanatory. The `bufferOffset` specifies the byte
-offset in the buffer at which the pixel values start. The `bufferRowLength` and
-`bufferImageHeight` fields specify how the pixels are laid out in memory. For
-example, you could have some padding bytes between rows of the image. Specifying
-`0` for both indicates that the pixels are simply tightly packed like they are
-in our case. The `imageSubresource`, `imageOffset` and `imageExtent` fields
-indicate to which part of the image we want to copy the pixels.
+대부분의 필드는 직관적입니다. `bufferOffset`은 픽셀 값이 시작하는 버퍼의 바이트 단위 오프셋입니다. `bufferRowLength`와 `bufferImageHeight` 필드는 픽셀이 메모리에 어떻게 배치되어있는지를 명시합니다. 예를 들어 각 행에 패딩(padding) 바이트가 있을 수 있습니다. 둘 다 `0`으로 명시하였다는 의미는 패딩 없이 연속적으로 데이터가 존재한다는 뜻입니다. `imageSubresource`, `imageOffset`, `imageExtent`는 픽셀이 복사될 이미지의 영역을 명시합니다.
 
-Buffer to image copy operations are enqueued using the `vkCmdCopyBufferToImage`
-function:
+버퍼에서 메모리로의 복사 연산은 `vkCmdCopyBufferToImage` 함수를 통해 큐에 등록됩니다:
 
 ```c++
 vkCmdCopyBufferToImage(
@@ -612,57 +459,40 @@ vkCmdCopyBufferToImage(
 );
 ```
 
-The fourth parameter indicates which layout the image is currently using. I'm
-assuming here that the image has already been transitioned to the layout that is
-optimal for copying pixels to. Right now we're only copying one chunk of pixels
-to the whole image, but it's possible to specify an array of `VkBufferImageCopy`
-to perform many different copies from this buffer to the image in one operation.
+네 번째 매개변수는 현재 이미지가 사용하고 있는 레이아웃을 명시합니다. 여기서 저는 이미지가 이미 픽셀을 복사하기에 최적화된 레이아웃으로 전환되었다고 가정하고 있습니다. 지금은 픽셀 값 덩어리를 전체 이미지에 복사하고 있지만 `VkBufferImageCopy`의 배열을 명시해서 버퍼로부터의 서로 다른 복사 연산들을 한 번에 수행할 수도 있습니다.
 
-## Preparing the texture image
+## 텍스처 이미지 준비
 
-We now have all of the tools we need to finish setting up the texture image, so
-we're going back to the `createTextureImage` function. The last thing we did
-there was creating the texture image. The next step is to copy the staging
-buffer to the texture image. This involves two steps:
+이제 텍스처 이미지를 사용하기 위해 필요한 모든 도구가 준비되었으니 `createTextureImage` 함수로 다시 돌아갑시다. 여기서 마지막에 했던 것은 텍스처 이미지를 만든 것이었습니다. 그 다음 단계로 스테이징 버퍼를 텍스처 이미지로 복사해야 합니다. 여기에는 두 단계가 필요합니다:
 
-* Transition the texture image to `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`
-* Execute the buffer to image copy operation
+* 텍스처 이미지를 `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`로 전환
+* 버퍼에서 이미지로의 복사 연산 실행
 
-This is easy to do with the functions we just created:
+방금 만든 함수들을 사용하면 쉽게 수행할 수 있습니다:
 
 ```c++
 transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
 ```
 
-The image was created with the `VK_IMAGE_LAYOUT_UNDEFINED` layout, so that one
-should be specified as old layout when transitioning `textureImage`. Remember
-that we can do this because we don't care about its contents before performing
-the copy operation.
+이미지는 `VK_IMAGE_LAYOUT_UNDEFINED` 레이아웃으로 생성되었으므로 `textureImage`로 전환될 떄 기존(old) 레이아웃으로 명시되어야 합니다. 이것이 가능한 이유는 복사 연산을 수행하기 전, 기존에 쓰여있던 내용을 신경쓰지 않기 떄문에 가능한 것이라는 것을 기억하십시오.
 
-To be able to start sampling from the texture image in the shader, we need one
-last transition to prepare it for shader access:
+셰이더에서 텍스처 이미지를 샘플링하려면 마지막으로 셰이더에서 접근이 가능하도록 한번 더 전환이 필요합니다:
 
 ```c++
 transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
 ```
 
-## Transition barrier masks
+## 전환 배리어 마스크(mask)
 
-If you run your application with validation layers enabled now, then you'll see that
-it complains about the access masks and pipeline stages in
-`transitionImageLayout` being invalid. We still need to set those based on the
-layouts in the transition.
+지금 시점에 검증 레이어가 켜진 상태에서 프로그램을 실행하면 `transitionImageLayout`의 접근 마스크와 파이프라인 스테이지가 유효하지 않다는 오류를 보실 수 있습니다. 전환의 레이아웃에서 이들을 설정해 줘야 합니다.
 
-There are two transitions we need to handle:
+처리해야 할 전환이 두 가지 있습니다:
 
-* Undefined → transfer destination: transfer writes that don't need to wait on
-anything
-* Transfer destination → shader reading: shader reads should wait on transfer
-writes, specifically the shader reads in the fragment shader, because that's
-where we're going to use the texture
+* Undefined → transfer destination: 전송은 무언가를 기다릴 필요 없이 쓰기를 수행
+* Transfer destination → shader reading: 셰이더 읽기는 전송의 쓰기를 기다려야 하며, 정확히는 프래그먼트 셰이더의 읽기 연산임. 왜냐하면 이 시점이 텍스터를 사용하는 시점이므로
 
-These rules are specified using the following access masks and pipeline stages:
+이러한 규칙들은 다음와 같은 접근 마스크와 파이프라인 스테이지로 명시됩니다:
 
 ```c++
 VkPipelineStageFlags sourceStage;
@@ -694,51 +524,21 @@ vkCmdPipelineBarrier(
 );
 ```
 
-As you can see in the aforementioned table, transfer writes must occur in the
-pipeline transfer stage. Since the writes don't have to wait on anything, you
-may specify an empty access mask and the earliest possible pipeline stage
-`VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` for the pre-barrier operations. It should be
-noted that `VK_PIPELINE_STAGE_TRANSFER_BIT` is not a *real* stage within the
-graphics and compute pipelines. It is more of a pseudo-stage where transfers
-happen. See [the documentation](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#VkPipelineStageFlagBits)
-for more information and other examples of pseudo-stages.
-
-The image will be written in the same pipeline stage and subsequently read by
-the fragment shader, which is why we specify shader reading access in the
-fragment shader pipeline stage.
-
-If we need to do more transitions in the future, then we'll extend the function.
-The application should now run successfully, although there are of course no
-visual changes yet.
-
-One thing to note is that command buffer submission results in implicit
-`VK_ACCESS_HOST_WRITE_BIT` synchronization at the beginning. Since the
-`transitionImageLayout` function executes a command buffer with only a single
-command, you could use this implicit synchronization and set `srcAccessMask` to
-`0` if you ever needed a `VK_ACCESS_HOST_WRITE_BIT` dependency in a layout
-transition. It's up to you if you want to be explicit about it or not, but I'm
-personally not a fan of relying on these OpenGL-like "hidden" operations.
-
-There is actually a special type of image layout that supports all operations,
-`VK_IMAGE_LAYOUT_GENERAL`. The problem with it, of course, is that it doesn't
-necessarily offer the best performance for any operation. It is required for
-some special cases, like using an image as both input and output, or for reading
-an image after it has left the preinitialized layout.
-
-All of the helper functions that submit commands so far have been set up to
-execute synchronously by waiting for the queue to become idle. For practical
-applications it is recommended to combine these operations in a single command
-buffer and execute them asynchronously for higher throughput, especially the
-transitions and copy in the `createTextureImage` function. Try to experiment
-with this by creating a `setupCommandBuffer` that the helper functions record
-commands into, and add a `flushSetupCommands` to execute the commands that have
-been recorded so far. It's best to do this after the texture mapping works to
-check if the texture resources are still set up correctly.
-
-## Cleanup
-
-Finish the `createTextureImage` function by cleaning up the staging buffer and
-its memory at the end:
+이전 표에서처럼 전송 쓰기는 파이프라인 전환 단계에서 수행되어야 합니다. 쓰기 연산이 무언가를 기다릴 필요는 없으므로 빈 접근 마스크를 명시하고 파이프라인의 가장 첫 단계인 `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT`를 배리어 전 연산으로 지정해야 합니다. 중요한 것은 `VK_PIPELINE_STAGE_TRANSFER_BIT`은 *실제* 그래픽스나 컴퓨트 파이프라인의 스테이지가 아니라는 점입니다. 전송이 일어나는 의사(pseudo)-스테이지에 가깝습니다. 의사 스테이지의 예시들에 대해서는 [이 문서](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#VkPipelineStageFlagBits)를 살펴보세요.
+
+이미지는 같은 파이프라인 스테이지에서 쓰여지고 프래그먼트 셰이더에서 읽게 되므로 프래그먼트 셰이더 파이프라인 스테이지에 셰이더 읽기 접근을 명시하였습니다.
+
+나중에 추가적인 전환을 수행해야 한다면 그 때 함수를 확장할 것입니다. 이제 프로그램은 올바로 동작하고, 보이는 장면은 이전과 동일합니다.
+
+하나 언급하고 싶은 것은 초반부의 암시적 `VK_ACCESS_HOST_WRITE_BIT` 동기화에서의 명령 버퍼 제출 입니다. `transitionImageLayout` 함수는 명령이 하나만 존재하는 명령 버터를 실행하므로, 암시적 동기화를 수행하고 `srcAccessMask`를 `0`으로 설정할 수 있습니다. 이는 레이아웃 전환에서 `VK_ACCESS_HOST_WRITE_BIT` 의존성이 필요한 경우에 입니다. 이를 명시적으로 할지 아닐지는 여러분들에게 달려 있지만 저는 개인적으로 이러한 OpenGL 스타일의 "숨겨진" 연산이 있는 것을 좋아하지는 않습니다.
+
+사실 모든 연산을 지원하는 특별한 이미지 레이아웃인 `VK_IMAGE_LAYOUT_GENERAL`가 있습니다. 이것의 문제는 당연하지만 어떤 연산에 대해서 최선의 성능을 보장하지 않는다는 것입니다. 이것은 이미지를 입력과 출력에 동시에 사용하거나 사전 초기화가 끝난 이후에 이미지를 읽어온다거나 하는 등의 특정한 케이스에서는 필요할 수 있습니다.
+
+지금까지 명령을 제출한 헬퍼 함수들은 큐가 아이들 상태가 될때까지 대기하여 명령을 동기적으로 수행하도록 설정되었습니다. 실제 응용 프로그램에서는 이러한 연산들을 하나의 명령 버퍼에 통합하여 비동기적으로 실행하여 높은 쓰루풋(throughput)을 달성하는 것이 권장됩니다. 특히 `createTextureImage` 함수의 전환과 복사 연산에 대해서는요. 헬퍼 함수가 명령을 입력할 `setupCommandBuffer`를 만들고 `flushSetupCommands`를 추가하여 지금까지 기록된 명령을 실행하게 해보세요. 텍스처 맵핑 이후에 이러한 작업을 시도하여 텍스처 리소스가 문제 없이 설정되는지 확인해 보시는 것이 가장 좋을 것 같습니다.
+
+## 정리
+
+스테이징 버퍼와 그 메모리를 마지막에 정리하는 것으로 `createTextureImage` 함수를 마무리 합시다:
 
 ```c++
     transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
@@ -748,7 +548,7 @@ its memory at the end:
 }
 ```
 
-The main texture image is used until the end of the program:
+메인 텍스터 이미지는 프로그램 종료 시까지 사용됩니다:
 
 ```c++
 void cleanup() {
@@ -761,8 +561,7 @@ void cleanup() {
 }
 ```
 
-The image now contains the texture, but we still need a way to access it from
-the graphics pipeline. We'll work on that in the next chapter.
+이미지는 이제 텍스처를 포함하지만 그래픽스 파이프라인에서 접근이 가능하게 할 방법이 필요합니다. 다음 챕터에서 진행해 보겠습니다.
 
 [C++ code](/code/24_texture_image.cpp) /
 [Vertex shader](/code/22_shader_ubo.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index b6e1ee47..dad426a9 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -91,4 +91,15 @@
 - 목적지: Destination
 - 앨리어싱: Aliasing
 - 투영: Projection
-- 컬링: Culling
\ No newline at end of file
+- 컬링: Culling
+- 베이크: Bake(d)
+- 샘플러: Sampler
+- 배리어: Barrier
+- 소유권: Ownership
+- 텍셀: Texel
+- 복셀: Voxel
+- 타일링: Tiling
+- 전환: Transition
+- 희박한: Sparse
+- 패딩: Padding
+- 쓰루풋: Throughput
\ No newline at end of file

From d8d840113ab527a768db31539ee452c2eb100235 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 19 Feb 2024 22:33:42 +0900
Subject: [PATCH 38/47] kr translate 06-00 image view and sampler

---
 .../01_Image_view_and_sampler.md              | 169 +++++-------------
 kr/kr_glossary.md                             |   5 +-
 2 files changed, 50 insertions(+), 124 deletions(-)

diff --git a/kr/06_Texture_mapping/01_Image_view_and_sampler.md b/kr/06_Texture_mapping/01_Image_view_and_sampler.md
index 9d98c9e4..db6d4c47 100644
--- a/kr/06_Texture_mapping/01_Image_view_and_sampler.md
+++ b/kr/06_Texture_mapping/01_Image_view_and_sampler.md
@@ -1,16 +1,10 @@
-In this chapter we're going to create two more resources that are needed for the
-graphics pipeline to sample an image. The first resource is one that we've
-already seen before while working with the swap chain images, but the second one
-is new - it relates to how the shader will read texels from the image.
+이 챕터에서는 그래픽스 파이프라인에서 이미지를 샘플링하기 위해 필요한 리소스 두 개를 더 만들어 보겠습니다. 첫 번째 리소스는 스왑 체인 이미지를 다루면서 이미 살펴본 것이지만 두 번째는 새로운 것입니다. 셰이더에서 이미지로브터 텍셀을 어떻게 읽는 방법에 관한 리소스입니다.
 
-## Texture image view
+## 텍스처 이미지 뷰
 
-We've seen before, with the swap chain images and the framebuffer, that images
-are accessed through image views rather than directly. We will also need to
-create such an image view for the texture image.
+전에 본 스왑 체인 이미지와 프레임버퍼에서, 이미지는 직접 접근되는 것이 아니라 이미지 뷰를 통해 접근하였습니다. 텍스처 이미지에 관해서도 이러한 이미지 뷰가 필요합니다.
 
-Add a class member to hold a `VkImageView` for the texture image and create a
-new function `createTextureImageView` where we'll create it:
+텍스터 이미지의 `VkImageView`를 위한 클래스 멤버를 추가하고 이를 생성할 `createTextureImageView` 함수를 새로 추가합니다:
 
 ```c++
 VkImageView textureImageView;
@@ -32,8 +26,7 @@ void createTextureImageView() {
 }
 ```
 
-The code for this function can be based directly on `createImageViews`. The only
-two changes you have to make are the `format` and the `image`:
+이 함수의 코드는 `createImageViews`에 기반합니다. 두 가지 변경해야 할 것은 `format`과 `image` 입니다:
 
 ```c++
 VkImageViewCreateInfo viewInfo{};
@@ -48,9 +41,7 @@ viewInfo.subresourceRange.baseArrayLayer = 0;
 viewInfo.subresourceRange.layerCount = 1;
 ```
 
-I've left out the explicit `viewInfo.components` initialization, because
-`VK_COMPONENT_SWIZZLE_IDENTITY` is defined as `0` anyway. Finish creating the
-image view by calling `vkCreateImageView`:
+`viewInfo.components`에 관한 명시적 초기화는 제외하였는데 `VK_COMPONENT_SWIZZLE_IDENTITY`는 어차피 `0`으로 정의되어 있기 떄문입니다. `vkCreateImageView`를 호출함으로써 이미지 뷰 생성을 마칩니다:
 
 ```c++
 if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) {
@@ -58,8 +49,7 @@ if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCE
 }
 ```
 
-Because so much of the logic is duplicated from `createImageViews`, you may wish
-to abstract it into a new `createImageView` function:
+`createImageViews`와 대부분의 로직이 동일하기 때문에 `createImageView` 함수를 새로 추상화 하는것이 좋겠습니다:
 
 ```c++
 VkImageView createImageView(VkImage image, VkFormat format) {
@@ -83,7 +73,7 @@ VkImageView createImageView(VkImage image, VkFormat format) {
 }
 ```
 
-The `createTextureImageView` function can now be simplified to:
+`createTextureImageView` 함수는 이제 아래와 같이 간단해집니다:
 
 ```c++
 void createTextureImageView() {
@@ -91,7 +81,7 @@ void createTextureImageView() {
 }
 ```
 
-And `createImageViews` can be simplified to:
+그리고 `createImageViews`는 아래와 같이 간단해집니다:
 
 ```c++
 void createImageViews() {
@@ -103,8 +93,7 @@ void createImageViews() {
 }
 ```
 
-Make sure to destroy the image view at the end of the program, right before
-destroying the image itself:
+프로그램 종료 시점에 이미지 뷰를 소멸하는 것을 잊지 마십시오. 이미지 자체를 소멸하기 직전에 이러한 작업을 수행합니다.
 
 ```c++
 void cleanup() {
@@ -116,46 +105,27 @@ void cleanup() {
     vkFreeMemory(device, textureImageMemory, nullptr);
 ```
 
-## Samplers
+## 샘플러(Samplers)
 
-It is possible for shaders to read texels directly from images, but that is not
-very common when they are used as textures. Textures are usually accessed
-through samplers, which will apply filtering and transformations to compute the
-final color that is retrieved.
+셰이더가 이미지로부터 텍셀을 직접 읽는 것도 가능하지만 텍스처에 대해서 이렇게 하는 것은 일반적이지 않습니다. 텍스처는 대개 샘플러를 통해 접근되는데, 이는 추출할 최종 색상을 계산하기 위해 필터링과 변환을 수행합니다.
 
-These filters are helpful to deal with problems like oversampling. Consider a
-texture that is mapped to geometry with more fragments than texels. If you
-simply took the closest texel for the texture coordinate in each fragment, then
-you would get a result like the first image:
+이 필터들은 오버샘플링(oversampling) 같은 문제를 다루는 데 유용합니다. 텍셀보다 프래그먼트가 많은 물체게 대해 텍스처 맵핑이 수행된다고 생각해 보십시오. 각 프래그먼트의 텍스처 좌표에 대해 단순히 가장 가까운 텍셀을 사용하면 첫 번째 이미지와 같은 결과를 얻게 될겁니다:
 
 ![](/images/texture_filtering.png)
 
-If you combined the 4 closest texels through linear interpolation, then you
-would get a smoother result like the one on the right. Of course your
-application may have art style requirements that fit the left style more (think
-Minecraft), but the right is preferred in conventional graphics applications. A
-sampler object automatically applies this filtering for you when reading a color
-from the texture.
+네 개의 가장 가까운 텍셀을 선형 보간(linear interpolation)하면 오른쪽과 같이 좀 더 부드러운 결과를 얻을 수 있습니다. 물론 여러분 응용 프로그램의 아트 스타일이 왼쪽의 경우와 더 잘 어울릴 수도 있습니다(마인크래프트 같은 경우). 하지만 일반적인 그래픽스 응용 프로그램에서는 오른쪽의 경우가 더 선호됩니다. 샘플러 객체는 텍스처로부터 색상을 읽을 때 이러한 필터링을 자동으로 수행해 줍니다.
 
-Undersampling is the opposite problem, where you have more texels than
-fragments. This will lead to artifacts when sampling high frequency patterns
-like a checkerboard texture at a sharp angle:
+언더샘플링(undersampling)은 반대의 경우로, 프래그먼트보다 텍셀이 더 많은 경우입니다. 이러한 경우 체커보드(checkerboard) 텍스처와 같은 고주파(high frequency) 패턴을 비스듬히 바라볼 때 문제가 생깁니다:
 
 ![](/images/anisotropic_filtering.png)
 
-As shown in the left image, the texture turns into a blurry mess in the
-distance. The solution to this is [anisotropic filtering](https://en.wikipedia.org/wiki/Anisotropic_filtering),
-which can also be applied automatically by a sampler.
+왼쪽 이미지에서 볼 수 있듯이 먼 곳의 텍스처는 흐릿하게 뭉개집니다. 이러한 문제의 해결 방안은 [비등방성(anisotropic) 필터링](https://en.wikipedia.org/wiki/Anisotropic_filtering)으로, 역시나 샘플러를 통해 자동적으로 적용될 수 있습니다.
 
-Aside from these filters, a sampler can also take care of transformations. It
-determines what happens when you try to read texels outside the image through
-its *addressing mode*. The image below displays some of the possibilities:
+이러한 필터 이외에도 샘플러는 변환도 처리해 줍니다. 여러분의 이미지 범위 밖의 텍셀을 읽을 떄 어떻게 처리할지도 *어드레싱 모드(addressing mode)*를 기반으로 결정합니다. 아래 이미지는 몇 가지 가능성을 보여줍니다:
 
 ![](/images/texture_addressing.png)
 
-We will now create a function `createTextureSampler` to set up such a sampler
-object. We'll be using that sampler to read colors from the texture in the
-shader later on.
+이제 `createTextureSampler` 함수를 만들어 이러한 샘플러 객체를 설정해 봅시다. 나중에 셰이더에서 이러한 샘플러를 활용해 텍스처로부터 색상을 읽어올 것입니다.
 
 ```c++
 void initVulkan() {
@@ -173,8 +143,7 @@ void createTextureSampler() {
 }
 ```
 
-Samplers are configured through a `VkSamplerCreateInfo` structure, which
-specifies all filters and transformations that it should apply.
+샘플러는 `VkSamplerCreateInfo` 구조체를 통해 설정되는데, 적용되어야 할 필터와 변환들을 명시합니다.
 
 ```c++
 VkSamplerCreateInfo samplerInfo{};
@@ -183,11 +152,7 @@ samplerInfo.magFilter = VK_FILTER_LINEAR;
 samplerInfo.minFilter = VK_FILTER_LINEAR;
 ```
 
-The `magFilter` and `minFilter` fields specify how to interpolate texels that
-are magnified or minified. Magnification concerns the oversampling problem
-describes above, and minification concerns undersampling. The choices are
-`VK_FILTER_NEAREST` and `VK_FILTER_LINEAR`, corresponding to the modes
-demonstrated in the images above.
+`magFilter`와 `minFilter` 필드는 확대되거나 축소되는 텍스처를 어떻게 보간할 것인지를 명시합니다. 확대(magnification)는 위에서 설명한 오버샘플링 문제를 처리하는 방법이고 축소(minification)는 언더샘플링에 대한 방법입니다. 우리의 선택은 `VK_FILTER_NEAREST`와 `VK_FILTER_NEAREST`인데, 위 이미지에서 예시로 보여드린 방법에 대응되는 옵션입니다. 
 
 ```c++
 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
@@ -195,81 +160,54 @@ samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
 ```
 
-The addressing mode can be specified per axis using the `addressMode` fields.
-The available values are listed below. Most of these are demonstrated in the
-image above. Note that the axes are called U, V and W instead of X, Y and Z.
-This is a convention for texture space coordinates.
-
-* `VK_SAMPLER_ADDRESS_MODE_REPEAT`: Repeat the texture when going beyond the
-image dimensions.
-* `VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT`: Like repeat, but inverts the
-coordinates to mirror the image when going beyond the dimensions.
-* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE`: Take the color of the edge closest to
-the coordinate beyond the image dimensions.
-* `VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE`: Like clamp to edge, but
-instead uses the edge opposite to the closest edge.
-* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER`: Return a solid color when sampling
-beyond the dimensions of the image.
-
-It doesn't really matter which addressing mode we use here, because we're not
-going to sample outside of the image in this tutorial. However, the repeat mode
-is probably the most common mode, because it can be used to tile textures like
-floors and walls.
+어드레싱 모드는 축(axis)별로 `addressMode` 필터를 통해 명시됩니다. 가능한 값들은 아래와 같습니다. 위 이미지에서 거의 모든 경우의 예시를 보여드렸습니다. 축들은 X,Y,Z가 아닌 U,V,W로 명시된다는 점을 주의하십시오. 이것이 텍스처 공간 좌표를 표현하는 일반적인 표기법입니다.
+
+* `VK_SAMPLER_ADDRESS_MODE_REPEAT`: 이미지 범위 밖을 벗어날 경우 반복
+* `VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT`: 반복과 유사하지만 범위 밖을 벗어날 경우 좌표를 뒤집어 이미지가 거울상(mirror)이 되도록 함
+* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE`: 범위 밖을 벗어날 경우 가장 가까운 축의 모서리(edge) 색상을 사용함
+* `VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE`: 위 경우와 같지만 가장 가까운 모서리의 반대쪽 모서리를 사용
+* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER`: 범위 밖을 샘플링할 경우 단색(solid color)값을 반환함
+
+여기서는 어떤 어드레싱 모드를 사용하건 관계 없습니다. 이 튜토리얼에서는 이미지 범위 밖에서는 샘플링을 하는 경우가 없기 떄문입니다. 하지만 반복(repeat) 모드가 가장 일반적인데 이 모드가 벽이나 바닥 같은 타일 텍스처에 가장 적합하기 때문입니다.
 
 ```c++
 samplerInfo.anisotropyEnable = VK_TRUE;
 samplerInfo.maxAnisotropy = ???;
 ```
 
-These two fields specify if anisotropic filtering should be used. There is no
-reason not to use this unless performance is a concern. The `maxAnisotropy`
-field limits the amount of texel samples that can be used to calculate the final
-color. A lower value results in better performance, but lower quality results.
-To figure out which value we can use, we need to retrieve the properties of the physical device like so:
+이 두 필드는 비등방성 필터링을 사용할 것인지를 명시합니다. 성능에 문제가 없다면 이 기능을 사용하지 않은 이유가 없습니다. `maxAnisotropy` 필드는 최종 색성을 계산할 때 사용되는 텍셀 샘플의 수에 대한 제한값입니다. 값이 작으면 성능이 높지만 품질이 떨어집니다. 어떤 값을 사용할지를 알아내기 위해 물리적 장치의 속성을 얻어와야 합니다:
 
 ```c++
 VkPhysicalDeviceProperties properties{};
 vkGetPhysicalDeviceProperties(physicalDevice, &properties);
 ```
 
-If you look at the documentation for the `VkPhysicalDeviceProperties` structure, you'll see that it contains a `VkPhysicalDeviceLimits` member named `limits`. This struct in turn has a member called `maxSamplerAnisotropy` and this is the maximum value we can specify for `maxAnisotropy`. If we want to go for maximum quality, we can simply use that value directly:
+`VkPhysicalDeviceProperties` 구조체의 문서를 보시면 `limit`라고 이름지어진 `VkPhysicalDeviceLimits` 멤버를 보실 수 있습니다. 이 구조체는 `maxSamplerAnisotropy` 멤버를 가지고 있고 이것이 우리가 `maxAnisotropy`에 사용할 수 있는 최대값입니다. 가장 좋은 품질을 원한다면 그 값을 바로 사용하면 됩니다:
 
 ```c++
 samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
 ```
 
-You can either query the properties at the beginning of your program and pass them around to the functions that need them, or query them in the `createTextureSampler` function itself.
+프로그램의 시작 시점에 이 속성을 질의하고 값이 필요한 곳에 넘겨줄 수도 있습니다. 아니면 `createTextureSampler` 함수 내에서 질의하는 방법도 있습니다.
 
 ```c++
 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
 ```
 
-The `borderColor` field specifies which color is returned when sampling beyond
-the image with clamp to border addressing mode. It is possible to return black,
-white or transparent in either float or int formats. You cannot specify an
-arbitrary color.
+`borderColor` 필드는 clamp to border 어드레싱 모드일 때, 범위 밖을 샘플링하는 경우 반환할 색상 값을 명시합니다. 검은색, 흰색, 또는 투명색을 float이나 int 포맷으로 반환할 수 있습니다. 임의의 색상을 명시하는 것은 불가능합니다.
 
 ```c++
 samplerInfo.unnormalizedCoordinates = VK_FALSE;
 ```
 
-The `unnormalizedCoordinates` field specifies which coordinate system you want
-to use to address texels in an image. If this field is `VK_TRUE`, then you can
-simply use coordinates within the `[0, texWidth)` and `[0, texHeight)` range. If
-it is `VK_FALSE`, then the texels are addressed using the `[0, 1)` range on all
-axes. Real-world applications almost always use normalized coordinates, because
-then it's possible to use textures of varying resolutions with the exact same
-coordinates.
+`unnormalizedCoordinates` 필드는 이미지의 텍셀에 접근할 떄 어떤 좌표계를 사용할 지 명시합니다. `VK_TRUE`인 경우 `[0, texWidth)`와 `[0, texHeight)` 범위의 좌표를 사용하면 됩니다. `VK_FALSE`인 경우엔 텍셀은 모든 축에 대해 `[0,1)`로 접근합니다. 실제 응용 프로그램에서는 거의 항상 정규화된(normalized) 좌표계를 사용하는데, 이렇게 하면 다양한 해상도의 텍스처에 대해서도 동일한 좌표를 사용할 수 있기 때문입니다.
 
 ```c++
 samplerInfo.compareEnable = VK_FALSE;
 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
 ```
 
-If a comparison function is enabled, then texels will first be compared to a
-value, and the result of that comparison is used in filtering operations. This
-is mainly used for [percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html)
-on shadow maps. We'll look at this in a future chapter.
+비교(comparison) 함수가 활성화되면 텍셀은 먼저 값과 비교된 뒤에 그 비교 결과가 필터링 연산에 사용됩니다. 이는 주로 그림자 맵핑에서 [percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html)에 사용됩니다. 이에 대해서는 나중 챕터에서 살펴보겠습니다.
 
 ```c++
 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
@@ -278,12 +216,9 @@ samplerInfo.minLod = 0.0f;
 samplerInfo.maxLod = 0.0f;
 ```
 
-All of these fields apply to mipmapping. We will look at mipmapping in a [later
-chapter](/Generating_Mipmaps), but basically it's another type of filter that can be applied.
+이 필드들은 모두 밉맵핑에 적용됩니다. 밉맵핑에 대해서는 [나중 챕터](/Generating_Mipmaps)에서 살펴볼 것이고, 적용될 수 있는 또 다른 종류의 필터입니다.
 
-The functioning of the sampler is now fully defined. Add a class member to
-hold the handle of the sampler object and create the sampler with
-`vkCreateSampler`:
+이제 샘플러를 위한 기능이 모두 정의되었습니다. 샘플러 객체의 핸들을 저장할 클래스 멤버를 추가하고 `vkCreateSampler`를 사용해 샘플러를 생성합니다:
 
 ```c++
 VkImageView textureImageView;
@@ -300,14 +235,9 @@ void createTextureSampler() {
 }
 ```
 
-Note the sampler does not reference a `VkImage` anywhere. The sampler is a
-distinct object that provides an interface to extract colors from a texture. It
-can be applied to any image you want, whether it is 1D, 2D or 3D. This is
-different from many older APIs, which combined texture images and filtering into
-a single state.
+샘플러가 `VkImage`를 참조하지 않는다는 것을 주목하십시오. 샘플러는 텍스처에서 색상을 추출하는 인터페이스를 제공하는 별도의 객체입니다. 이는 1D, 2D, 3D 등 원하는 어떤 이미지에도 적용될 수 있습니다. 이것이 다른 오래된 API들과는 다른 점인데, 그것들의 경우 텍스처 이미지와 필터링을 하나의 상태로 결합합니다.
 
-Destroy the sampler at the end of the program when we'll no longer be accessing
-the image:
+프로그램의 종료 시점, 더 이상 이미지에 접근할 필요가 없어지는 시점에 샘플러를 소멸시킵니다:
 
 ```c++
 void cleanup() {
@@ -320,23 +250,20 @@ void cleanup() {
 }
 ```
 
-## Anisotropy device feature
+## 비등방성 장치 기능
 
-If you run your program right now, you'll see a validation layer message like
-this:
+지금 시점에서 프로그램을 실행하면 아래와 같은 검증 레이어 메시지를 보게 됩니다:
 
 ![](/images/validation_layer_anisotropy.png)
 
-That's because anisotropic filtering is actually an optional device feature. We
-need to update the `createLogicalDevice` function to request it:
+사실 비등방성 필터링은 장치의 선택적인 기능입니다. 따라서 그 기능을 요청하기 위해서는 `createLogicalDevice` 함수를 수정해야 합니다:
 
 ```c++
 VkPhysicalDeviceFeatures deviceFeatures{};
 deviceFeatures.samplerAnisotropy = VK_TRUE;
 ```
 
-And even though it is very unlikely that a modern graphics card will not support
-it, we should update `isDeviceSuitable` to check if it is available:
+최근의 그래픽 카드가 이를 지원하지 않은 가능성은 매우 낮지만, 그래도 `isDeviceSuitable`에서 이를 확인하도록 합니다:
 
 ```c++
 bool isDeviceSuitable(VkPhysicalDevice device) {
@@ -349,20 +276,16 @@ bool isDeviceSuitable(VkPhysicalDevice device) {
 }
 ```
 
-The `vkGetPhysicalDeviceFeatures` repurposes the `VkPhysicalDeviceFeatures`
-struct to indicate which features are supported rather than requested by setting
-the boolean values.
+불리언 값으로 기능을 요청하는 대신, `vkGetPhysicalDeviceFeatures`를 사용해 `VkPhysicalDeviceFeatures` 구조체를 변경함으로써 어떤 기능이 지원되는지를 표시하도록 할 수 있습니다.
 
-Instead of enforcing the availability of anisotropic filtering, it's also
-possible to simply not use it by conditionally setting:
+비등방성 필터링의 가용성을 강제하는 대신, 아래와 같이 그 기능을 사용하지 않도록 할 수도 있습니다:
 
 ```c++
 samplerInfo.anisotropyEnable = VK_FALSE;
 samplerInfo.maxAnisotropy = 1.0f;
 ```
 
-In the next chapter we will expose the image and sampler objects to the shaders
-to draw the texture onto the square.
+다음 챕터에서는 이미지와 샘플러 객체를 셰이더에 노출하여 사각형에 텍스처를 입혀 보도록 하겠습니다.
 
 [C++ code](/code/25_sampler.cpp) /
 [Vertex shader](/code/22_shader_ubo.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index dad426a9..2ce7aa38 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -102,4 +102,7 @@
 - 전환: Transition
 - 희박한: Sparse
 - 패딩: Padding
-- 쓰루풋: Throughput
\ No newline at end of file
+- 쓰루풋: Throughput
+- 보간: Interpolation
+- 비등방성: Anisotropic
+- 어드레싱 모드: Addressing mode
\ No newline at end of file

From 16261a0727e0f866d93e1e2b323ba15fb073bb2f Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 20 Feb 2024 12:15:57 +0900
Subject: [PATCH 39/47] kr translate change translation

---
 kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md     | 2 +-
 kr/04_Vertex_buffers/00_Vertex_input_description.md      | 2 +-
 kr/04_Vertex_buffers/01_Vertex_buffer_creation.md        | 2 +-
 kr/04_Vertex_buffers/02_Staging_buffer.md                | 2 +-
 kr/04_Vertex_buffers/03_Index_buffer.md                  | 2 +-
 kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md | 2 +-
 kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md     | 2 +-
 kr/06_Texture_mapping/00_Images.md                       | 2 +-
 8 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
index 87b6dcbc..e63d3196 100644
--- a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
+++ b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 지금까지 만든 프로그램으로 성공적으로 삼각형을 그렸지만 아직 잘 처리하지 못하는 상황이 있습니다. 윈도우 표면이 변경되어 스왑 체인이 더이상 호환되지 않을 때 입니다. 이러한 상황이 발생하는 이유 중 하나로 윈도우의 크기가 변하는 경우가 있습니다. 이러한 이벤트를 탐지하여 스왑 체인을 새로 만들어야만 합니다.
 
diff --git a/kr/04_Vertex_buffers/00_Vertex_input_description.md b/kr/04_Vertex_buffers/00_Vertex_input_description.md
index 28e00be0..c24311e4 100644
--- a/kr/04_Vertex_buffers/00_Vertex_input_description.md
+++ b/kr/04_Vertex_buffers/00_Vertex_input_description.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 다음 몇 챕터동안 정점 셰이더에 하드코딩된 정점 데이터를 메모리의 정점 버퍼(vertex buffer)로 바꾸어 보겠습니다. 먼저 가장 손쉬운 방법인 CPU에서 보이는(visible) 버퍼를 만든 뒤 `memcpy`를 통해 정점 데이터를 직접 복사하는 방법을 알아볼 것이고, 이후에 스테이징 버퍼(staging buffer)를 사용해 정점 데이터를 고성능 메모리에 복사하는 방법을 알아볼 것입니다.
 
diff --git a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
index 19ba00b2..574b436d 100644
--- a/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
+++ b/kr/04_Vertex_buffers/01_Vertex_buffer_creation.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 Vulkan에서 버퍼는 그래픽 카드가 읽을 수 있는, 임의의 데이터를 저장하는 메모리 영역을 의미합니다. 이 챕터에서의 예시처럼 정점 데이터를 저장하는 데 사용될 수도 있지만 나중 챕터에서 살펴볼 것인데 다른 용도로도 자주 사용됩니다. 지금까지 살펴본 Vulkan 객체와는 다르게 버퍼는 스스로 메모리를 할당하지 않습니다. 지금까지 살펴본 것처럼 Vulkan API는 프로그래머에게 거의 모든 제어권을 주는데, 메모리 관리 또한 이에 포함됩니다.
 
diff --git a/kr/04_Vertex_buffers/02_Staging_buffer.md b/kr/04_Vertex_buffers/02_Staging_buffer.md
index 0205f658..b2aa05a2 100644
--- a/kr/04_Vertex_buffers/02_Staging_buffer.md
+++ b/kr/04_Vertex_buffers/02_Staging_buffer.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 지금 만든 정점 버퍼는 잘 동작하지만 CPU에서 접근이 가능하도록 선택한 메모리 타입이 그래픽 카드에서 읽기에는 최적화된 메모리 타입은 아닐 수 있습니다. 가장 적합한 메모리는 `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` 플래그를 가지고 있고 대개는 대상 그래픽 카드에 대해 CPU에서는 접근이 불가능합니다. 이 챕터에서는 두 정점 버퍼를 만들 것입니다. 하나는 *스테이징 버퍼(staging buffer)*로 CPU에서 접근 가능하여 정점 배열을 넣을 수 있으며 다른 하나는 장치의 로컬 메모리에 있는 정점 버퍼입니다. 그러고 나서 버퍼 복사 명령을 사용해 스테이징 버퍼에서 실제 정점 버퍼로 데이터를 옮길 것입니다.
 
diff --git a/kr/04_Vertex_buffers/03_Index_buffer.md b/kr/04_Vertex_buffers/03_Index_buffer.md
index 5cbb0ce1..cb889b77 100644
--- a/kr/04_Vertex_buffers/03_Index_buffer.md
+++ b/kr/04_Vertex_buffers/03_Index_buffer.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 실제 응용 프로그램에서 렌더링할 3D 메쉬는 여러 삼각형에서 정점을 공유하는 경우가 많습니다. 이는 아래와 같은 간단한 사각형을 렌더링 할 때도 적용됩니다:
 
diff --git a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
index 0115deea..895a4253 100644
--- a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
+++ b/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 이제 각 정점에 대한 어트리뷰트를 정점 셰이더에 넘길 수 있게 되었습니다만, 전역 변수는 어떤가요? 이 챕터에서부터 3D 그래픽스로 넘어갈 것인데 그러려면 모델-뷰-투영(projection) 행렬이 있어야 합니다. 이를 정점 데이터로 포함할 수도 있지만 그건 메모리도 낭비될 뿐만 아니라 변환(transformation)이 바뀌게 되면 정점 버퍼가 업데이트되어야 하는 문제가 있습니다. 변환 정보는 매 프레임마다 변화합니다.
 
diff --git a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
index 05d0ef78..2bbd1c0e 100644
--- a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
+++ b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 이전 장에서의 기술자 레이아웃은 바인딩 될 수 있는 기술자의 타입을 명시합니다. 이 장에서는 각 `VkBuffer` 리소스를 위한 기술자 집합을 만들어서 유니폼 버퍼 기술자에 바인딩할 것입니다.
 
diff --git a/kr/06_Texture_mapping/00_Images.md b/kr/06_Texture_mapping/00_Images.md
index e6af931d..7f0ae75b 100644
--- a/kr/06_Texture_mapping/00_Images.md
+++ b/kr/06_Texture_mapping/00_Images.md
@@ -1,4 +1,4 @@
-## 개요
+## 서론
 
 지금까지 물체는 정점별로 할당된 색깔로 표현이 되었지만 이러한 방법으로는 한계가 있습니다. 이 챕터에서는 좀 더 흥미로운 표현을 위해 텍스처 맵핑을 구현해 보도록 하겠습니다. 이를 통해 나중에는 3D 모델을 로딩하고 그리는 것도 가능하게 될겁니다.
 

From bbcb64fb325897a0e246ed727e1537fb743e77cd Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 20 Feb 2024 13:07:33 +0900
Subject: [PATCH 40/47] kr translate 06-02 combined image sampler

---
 .../02_Combined_image_sampler.md              | 125 +++++-------------
 kr/kr_glossary.md                             |   3 +-
 2 files changed, 32 insertions(+), 96 deletions(-)

diff --git a/kr/06_Texture_mapping/02_Combined_image_sampler.md b/kr/06_Texture_mapping/02_Combined_image_sampler.md
index b86853d6..bff4550c 100644
--- a/kr/06_Texture_mapping/02_Combined_image_sampler.md
+++ b/kr/06_Texture_mapping/02_Combined_image_sampler.md
@@ -1,21 +1,12 @@
-## Introduction
+## 서론
 
-We looked at descriptors for the first time in the uniform buffers part of the
-tutorial. In this chapter we will look at a new type of descriptor: *combined
-image sampler*. This descriptor makes it possible for shaders to access an image
-resource through a sampler object like the one we created in the previous
-chapter.
+유니폼 버퍼 튜토리얼에서 처음으로 기술자에 대해 알아봤었습니다. 이 챕터에서는 새로운 종류의 기술자인 *결합된 이미지 샘플러(combined image sampler)* 에 대해 알아보겠습니다. 이 기술자를 사용해서 셰이더로부터 우리가 만든 샘플러 객체를 거쳐 이미지 리소스에 접글할 수 있게 됩니다.
 
-We'll start by modifying the descriptor layout, descriptor pool and descriptor
-set to include such a combined image sampler descriptor. After that, we're going
-to add texture coordinates to `Vertex` and modify the fragment shader to read
-colors from the texture instead of just interpolating the vertex colors.
+먼저 기술자 레이아웃, 기술자 풀, 기술자 집합이 결합된 이미지 샘플러와 같은 것을 포함할 수 있도록 수정할 것입니다. 그 이후에 `Vertex`에 텍스처 좌표를 추가하고 프래그먼트 셰이더를 수정하여 정점 색상을 보간하는 것이 아니라 텍스처로부터 색상값을 읽어오도록 할 것입니다.
 
-## Updating the descriptors
+## 기술자 수정
 
-Browse to the `createDescriptorSetLayout` function and add a
-`VkDescriptorSetLayoutBinding` for a combined image sampler descriptor. We'll
-simply put it in the binding after the uniform buffer:
+`createDescriptorSetLayout` 함수로 가서 결합된 이미지 샘플러 기술자를 위해 `VkDescriptorSetLayoutBinding`를 추가합니다. 유니폼 버퍼 이후에 바인딩에 추가 합니다:
 
 ```c++
 VkDescriptorSetLayoutBinding samplerLayoutBinding{};
@@ -32,17 +23,9 @@ layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
 layoutInfo.pBindings = bindings.data();
 ```
 
-Make sure to set the `stageFlags` to indicate that we intend to use the combined
-image sampler descriptor in the fragment shader. That's where the color of the
-fragment is going to be determined. It is possible to use texture sampling in
-the vertex shader, for example to dynamically deform a grid of vertices by a
-[heightmap](https://en.wikipedia.org/wiki/Heightmap).
+`stageFlags`를 사용해 결합된 이미지 샘플러 기술자가 프래그먼트 셰이더에서 사용될 것이라는 것을 꼭 명시하십시오. 프래그먼트의 색상이 결정되는 것은 그 시점이기 때문입니다. 정점 셰이더에서 텍스처 샘플링을 하는 것도 가능한데, 예를 들어 정점들을 [하이트맵(heightmap)](https://en.wikipedia.org/wiki/Heightmap)을 기반으로 동적으로 변경하고 하려고 하는 경우에 사용할 수 있습니다.
 
-We must also create a larger descriptor pool to make room for the allocation
-of the combined image sampler by adding another `VkPoolSize` of type
-`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` to the
-`VkDescriptorPoolCreateInfo`. Go to the `createDescriptorPool` function and
-modify it to include a `VkDescriptorPoolSize` for this descriptor:
+또한 결합된 이미지 샘플러를 위해 기술자 풀을 넉넉하게 만들어야 합니다. `VkDescriptorPoolCreateInfo`에 `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` 타입의 `VkPoolSize`를 추가할 것입니다. `createDescriptorPool` 함수로 가서 이 기술자를 위한 `VkDescriptorPoolSize`를 포함하도록 수정합니다:
 
 ```c++
 std::array<VkDescriptorPoolSize, 2> poolSizes{};
@@ -58,26 +41,11 @@ poolInfo.pPoolSizes = poolSizes.data();
 poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
 ```
 
-Inadequate descriptor pools are a good example of a problem that the validation
-layers will not catch: As of Vulkan 1.1, `vkAllocateDescriptorSets` may fail
-with the error code `VK_ERROR_POOL_OUT_OF_MEMORY` if the pool is not
-sufficiently large, but the driver may also try to solve the problem internally.
-This means that sometimes (depending on hardware, pool size and allocation size)
-the driver will let us get away with an allocation that exceeds the limits of
-our descriptor pool. Other times, `vkAllocateDescriptorSets` will fail and
-return `VK_ERROR_POOL_OUT_OF_MEMORY`. This can be particularly frustrating if
-the allocation succeeds on some machines, but fails on others.
-
-Since Vulkan shifts the responsiblity for the allocation to the driver, it is no
-longer a strict requirement to only allocate as many descriptors of a certain
-type (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, etc.) as specified by the
-corresponding `descriptorCount` members for the creation of the descriptor pool.
-However, it remains best practise to do so, and in the future,
-`VK_LAYER_KHRONOS_validation` will warn about this type of problem if you enable
-[Best Practice Validation](https://vulkan.lunarg.com/doc/view/1.2.189.0/linux/best_practices.html).
-
-The final step is to bind the actual image and sampler resources to the
-descriptors in the descriptor set. Go to the `createDescriptorSets` function.
+부적합한 기술자 풀은 검증 레이어가 문제를 탐지하지 못하는 대표적인 예입니다 (Vulkan 1.1 기준). 풀이 충분히 크지 않다면 `vkAllocateDescriptorSets`은 `VK_ERROR_POOL_OUT_OF_MEMORY` 오류 코드와 함께 실패하지만 드라이버 내부적으로 문제를 해결하려 시도합니다. 즉 어떤 경우에는 (하드웨어 및 풀 크기와 할당 크기에 따라) 기술자 풀의 크기 제한을 넘는 경우에도 드라이버가 우리의 할당 문제를 회피할 수 있게 해줄수도 있습니다. 그렇지 못한 경우에는 `vkAllocateDescriptorSets`가 실패하고 `VK_ERROR_POOL_OUT_OF_MEMORY`를 반환합니다. 어떤 환경에서는 할당에 성공하고 어떤 환경에서는 실패하기 때문에 까다로운 문제입니다.
+
+Vulkan은 할당과 관련한 역할을 드라이버에 맡기기 때문에, 특정한 타입 (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` 등)을의 기술자를 `descriptorCount` 멤버에 명시된 숫자대로 생성하는 것은 더 이상 엄격한 요구사항이 되지 못합니다. 하지만 그것을 지키는 것이 좋은 구현 방법이고 추후에는 [검증 모범사례](https://vulkan.lunarg.com/doc/view/1.2.189.0/linux/best_practices.html)를 활성화하는 경우 `VK_LAYER_KHRONOS_validation`가 이러한 종류의 문제에 대한 경고를 하게 될 것입니다.
+
+마지막 단계는 실제 이미지와 샘플러 리소스를 기술자 집합의 기술자들에 바인딩하는 것입니다. `createDescriptorSets` 함수로 가 봅시다.
 
 ```c++
 for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -95,10 +63,7 @@ for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
 }
 ```
 
-The resources for a combined image sampler structure must be specified in a
-`VkDescriptorImageInfo` struct, just like the buffer resource for a uniform
-buffer descriptor is specified in a `VkDescriptorBufferInfo` struct. This is
-where the objects from the previous chapter come together.
+결합된 이미지 샘플러 구조체의 리소스는 `VkDescriptorImageInfo` 구조체에 명시되어야 하고, 이는 유니폼 버퍼 기술자의 버퍼 리소스가 `VkDescriptorBufferInfo` 구조체에 명시되었던 것과 같습니다. 이제 이전 챕터에서의 구조체들이 함께 활용됩니다.
 
 ```c++
 std::array<VkWriteDescriptorSet, 2> descriptorWrites{};
@@ -122,15 +87,11 @@ descriptorWrites[1].pImageInfo = &imageInfo;
 vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
 ```
 
-The descriptors must be updated with this image info, just like the buffer. This
-time we're using the `pImageInfo` array instead of `pBufferInfo`. The descriptors
-are now ready to be used by the shaders!
+기술자는 버퍼와 동일하게 이미지 정보와 함께 갱신되어야 합니다. 이번에는 `pBufferInfo`가 아닌 `pImageInfo`가 사용됩니다. 이제 셰이더에서 기술자를 활용항 준비가 되었습니다!
 
-## Texture coordinates
+## 텍스처 좌표
 
-There is one important ingredient for texture mapping that is still missing, and
-that's the actual coordinates for each vertex. The coordinates determine how the
-image is actually mapped to the geometry.
+텍스처 맵핑을 위한 중요한 요소가 아직 빠져 있고, 이는 각 정점의 텍스처 좌표입니다. 텍스처 좌표는 이미지가 어떻게 대상에 맵핑될 것인지를 결정합니다.
 
 ```c++
 struct Vertex {
@@ -170,11 +131,7 @@ struct Vertex {
 };
 ```
 
-Modify the `Vertex` struct to include a `vec2` for texture coordinates. Make
-sure to also add a `VkVertexInputAttributeDescription` so that we can use access
-texture coordinates as input in the vertex shader. That is necessary to be able
-to pass them to the fragment shader for interpolation across the surface of the
-square.
+`Vertex` 구조체를 텍스처 좌표인 `vec2`를 포함하도록 수정합니다. `VkVertexInputAttributeDescription`도 추가해서 정점 셰이더의 입력으로 텍스처 좌표를 사용하도록 합니다. 이렇게 해야 이 값을 프래그먼트 셰이더로 넘길때 사각형의 표면에 걸쳐 보간이 이루어집니다.
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -185,16 +142,11 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-In this tutorial, I will simply fill the square with the texture by using
-coordinates from `0, 0` in the top-left corner to `1, 1` in the bottom-right
-corner. Feel free to experiment with different coordinates. Try using
-coordinates below `0` or above `1` to see the addressing modes in action!
+이 튜토리얼에서 저는 왼쪽 위 모서리에 `0, 0`을, 오른쪽 아래 모서리에 `1, 1`을 사용해서 텍스처가 사각형을 채우도록 하였습니다. 다른 좌표값으로 테스트 해 보세요. `0` 이하의 값이나 `1` 이상의 값으로 어드레싱 모드가 어떻게 동작하는지 살펴보세요!
 
-## Shaders
+## 셰이더
 
-The final step is modifying the shaders to sample colors from the texture. We
-first need to modify the vertex shader to pass through the texture coordinates
-to the fragment shader:
+마지막 단계는 셰이더를 수정해 텍스처로부터 색상을 샘플링하도록 하는 것입니다. 먼저 정점 셰이더를 수정해서 프래그먼트 셰이더로 텍스처 좌표를 넘기도록 합니다:
 
 ```glsl
 layout(location = 0) in vec2 inPosition;
@@ -211,9 +163,7 @@ void main() {
 }
 ```
 
-Just like the per vertex colors, the `fragTexCoord` values will be smoothly
-interpolated across the area of the square by the rasterizer. We can visualize
-this by having the fragment shader output the texture coordinates as colors:
+정점별 색상과 동일하게 `fragTexCoord`값도 래스터라이저에 의해 사각형 전체에 걸쳐 부드럽게 보간됩니다. 텍스처 좌표를 프래그먼트 세이더의 출력 색상으로 하여 이를 눈으로 확인할 수 있습니다:
 
 ```glsl
 #version 450
@@ -228,26 +178,19 @@ void main() {
 }
 ```
 
-You should see something like the image below. Don't forget to recompile the
-shaders!
+아래와 같은 이미지가 보일 겁니다. 셰이더를 다시 컴파일하는 것을 잊지 마세요!
 
 ![](/images/texcoord_visualization.png)
 
-The green channel represents the horizontal coordinates and the red channel the
-vertical coordinates. The black and yellow corners confirm that the texture
-coordinates are correctly interpolated from `0, 0` to `1, 1` across the square.
-Visualizing data using colors is the shader programming equivalent of `printf`
-debugging, for lack of a better option!
+초록색 채널이 수평축 좌표, 빨간색 채널이 수직축 좌표입니다. 검은색과 노란색 모서리를 통해 텍스처 좌표가 `0, 0`에서 `1, 1` 사이로 보간되었다는 것을 확인 가능합니다. 색상값으로 데이터를 가시화 하는 것이 셰이더 프로그래밍에서는 `printf`와 같은 겁니다. 더 나은 대안이 없기 때문이죠!
 
-A combined image sampler descriptor is represented in GLSL by a sampler uniform.
-Add a reference to it in the fragment shader:
+결합된 이미지 샘플러 기술자는 GLSL에서 샘플러 유니폼으로 표현됩니다. 프래그먼트 셰이더에서 이에 대한 참조를 추가합니다:
 
 ```glsl
 layout(binding = 1) uniform sampler2D texSampler;
 ```
 
-There are equivalent `sampler1D` and `sampler3D` types for other types of
-images. Make sure to use the correct binding here.
+다른 타입의 이미지를 위한 `sampler1D`와 `sampler3D` 타입도 있습니다. 올바른 바인딩을 해야 하는 것에 주의 하세요.
 
 ```glsl
 void main() {
@@ -255,16 +198,11 @@ void main() {
 }
 ```
 
-Textures are sampled using the built-in `texture` function. It takes a `sampler`
-and coordinate as arguments. The sampler automatically takes care of the
-filtering and transformations in the background. You should now see the texture
-on the square when you run the application:
+텍스처는 `texture` 내장함수에 의해 샘플링됩니다. 이 함수는 `sampler`와 텍스처 좌표를 인자로 받습니다. 샘플러는 필터링과 변환을 자동적으로 수행해줍니다. 이제 프로그램을 실행하면 사각형 위에 텍스처가 보일 겁니다:
 
 ![](/images/texture_on_square.png)
 
-Try experimenting with the addressing modes by scaling the texture coordinates
-to values higher than `1`. For example, the following fragment shader produces
-the result in the image below when using `VK_SAMPLER_ADDRESS_MODE_REPEAT`:
+텍스처 좌표를 `1`보다 큰 값으로 해서 어드레싱 모드를 살펴 보세요. 예를들어 다음 프래그먼트 셰이더는 `VK_SAMPLER_ADDRESS_MODE_REPEAT`인 경우 아래와 같은 이미지를 나타냅니다:
 
 ```glsl
 void main() {
@@ -274,7 +212,7 @@ void main() {
 
 ![](/images/texture_on_square_repeated.png)
 
-You can also manipulate the texture colors using the vertex colors:
+텍스처 색상을 정점 색상을 활용해 변경하는 것도 가능합니다:
 
 ```glsl
 void main() {
@@ -282,14 +220,11 @@ void main() {
 }
 ```
 
-I've separated the RGB and alpha channels here to not scale the alpha channel.
+여기서는 RGB와 알파 채널을 분리해서 알파 채널값은 영향을 받지 않도록 하였습니다.
 
 ![](/images/texture_on_square_colorized.png)
 
-You now know how to access images in shaders! This is a very powerful technique
-when combined with images that are also written to in framebuffers. You can use
-these images as inputs to implement cool effects like post-processing and camera
-displays within the 3D world.
+이제 셰이더에서 이미지에 접근하는 법을 알았습니다! 프레임버퍼에 쓰여진 이미지와 결합하게 되면 아주 강력한 기술이 됩니다. 그러한 이미지를 입력으로 사용해 멋진 후처리(post-processing) 효과나 3D 공간상에서 카메라를 표현하는 등의 작업을 할 수 있습니다.
 
 [C++ code](/code/26_texture_mapping.cpp) /
 [Vertex shader](/code/26_shader_textures.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index 2ce7aa38..9b50dffe 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -105,4 +105,5 @@
 - 쓰루풋: Throughput
 - 보간: Interpolation
 - 비등방성: Anisotropic
-- 어드레싱 모드: Addressing mode
\ No newline at end of file
+- 어드레싱 모드: Addressing mode
+- 하이트맵: Heightmap
\ No newline at end of file

From 39c8196ff56345072cbfff10eea15f153198e36f Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 28 Feb 2024 17:13:27 +0900
Subject: [PATCH 41/47] kr translate 07 depth buffering

---
 kr/07_Depth_buffering.md | 253 +++++++++++----------------------------
 1 file changed, 73 insertions(+), 180 deletions(-)

diff --git a/kr/07_Depth_buffering.md b/kr/07_Depth_buffering.md
index 92040cb3..389b1bf7 100644
--- a/kr/07_Depth_buffering.md
+++ b/kr/07_Depth_buffering.md
@@ -1,15 +1,10 @@
-## Introduction
+## 서론
 
-The geometry we've worked with so far is projected into 3D, but it's still
-completely flat. In this chapter we're going to add a Z coordinate to the
-position to prepare for 3D meshes. We'll use this third coordinate to place a
-square over the current square to see a problem that arises when geometry is not
-sorted by depth.
+지금까지 작업한 물체는 3차원으로 투영되긴 했지만 여전히 평평한 물체입니다. 이 챕터에서는 3D 메쉬를 위해 Z 좌표를 추가할 예정입니다. 새로 추가된 세 번째 좌표를 가지고 사각형을 지금 있는 사각형 위에 그려 봄으로써 물체들이 깊이 값에 따른 정렬(sort)이 되지 않으면 발생하는 문제를 살펴볼 것입니다.
 
-## 3D geometry
+## 3D 형상(geometry)
 
-Change the `Vertex` struct to use a 3D vector for the position, and update the
-`format` in the corresponding `VkVertexInputAttributeDescription`:
+`Vertex` 구조체를 수정하여 위치값으로 3차원 벡터를 사용하도록 하고 이와 대를되는 `VkVertexInputAttributeDescription`의 `format`도 갱신합니다:
 
 ```c++
 struct Vertex {
@@ -32,8 +27,7 @@ struct Vertex {
 };
 ```
 
-Next, update the vertex shader to accept and transform 3D coordinates as input.
-Don't forget to recompile it afterwards!
+다음으로 정점 셰이더가 3차원 좌표를 입력으로 받아서 변환을 수행하도록 바꿉니다. 수정 후에 다시 컴파일하는 것을 잊지 마세요!
 
 ```glsl
 layout(location = 0) in vec3 inPosition;
@@ -47,7 +41,7 @@ void main() {
 }
 ```
 
-Lastly, update the `vertices` container to include Z coordinates:
+마지막으로 `vertices` 컨테이너를 Z 좌표를 포함하도록 수정합니다:
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -58,16 +52,11 @@ const std::vector<Vertex> vertices = {
 };
 ```
 
-If you run your application now, then you should see exactly the same result as
-before. It's time to add some extra geometry to make the scene more interesting,
-and to demonstrate the problem that we're going to tackle in this chapter.
-Duplicate the vertices to define positions for a square right under the current
-one like this:
+지금 프로그램을 실행하전 전과 동일한 결과가 나타납니다. 좀 더 재미있는 결과를 보기 위해 형상들을 추가해서 이 챕터에서 다루고자하는 문제를 보여드리겠습니다. 정점들을 복사해서 현재 사각형 아래에 새로운 사각형이 아래 그림과 같이 위치하도록 정의합니다:
 
 ![](/images/extra_square.svg)
 
-Use Z coordinates of `-0.5f` and add the appropriate indices for the extra
-square:
+Z 좌표로 `-0.5f`를 사용하고 추가된 사각형에 대한 인덱스들도 추가합니다:
 
 ```c++
 const std::vector<Vertex> vertices = {
@@ -88,27 +77,17 @@ const std::vector<uint16_t> indices = {
 };
 ```
 
-Run your program now and you'll see something resembling an Escher illustration:
+이제 프로그램을 실행하면 마치 에셔의 그림같은 결과를 볼 수 있습니다:
 
 ![](/images/depth_issues.png)
 
-The problem is that the fragments of the lower square are drawn over the
-fragments of the upper square, simply because it comes later in the index array.
-There are two ways to solve this:
+문제는 아래에 위치한 사각형이 위쪽 사각형을 구성하는 프래그먼트 위에 그려진다는 것인데, 이는 아래 위치한 사각형의 인덱스가 인덱스 배열의 뒤쪽에 있기 때문입니다. 이 문제를 해결하는 방법은 두 가지가 있습니다:
 
-* Sort all of the draw calls by depth from back to front
-* Use depth testing with a depth buffer
+* 모든 드로우콜을 뒤쪽에서 앞쪽 깊이값 순서로 정렬
+* 깊이 버퍼를 사용해 깊이 테스트를 수행
+ 
+첫 번째 접근법은 투명한 물체를 그릴 때 일반적으로 사용되는 방법인데, 순서와 무관하게 투명한 물체를 그리는 것은 상당히 어려운 문제이기 때문입니다. 프래그먼트를 깊이 순서대로 정렬하는 문제는 *깊이 버퍼*를 사용해서 해결하는 것이 일반적입니다. 깊이 버퍼는 모든 위치값에 대해 깊이를 저장하기 위해 사용하는 추가적인 어태치먼트입니다. 색상 어태치먼트가 색상 값을 저장하는 것과 다를 것이 없습니다. 래스터라이저가 프래그먼트를 만들어 낼 때마가 깊이 테스트를 통해 새로운 프래그먼트가 이미 쓰여저 있는 프래그먼트보다 더 가까이 있는 것인지를 테스트합니다. 그렇지 않은 경우에는 프래그먼트가 버려집니다(discarded). 깊이 테스트를 통과하면 그 프래그먼트의 깊이 값이 깊이 버퍼에 쓰여집니다. 프래그먼트 셰이더에서 색상 출력값을 조정하는 것처럼 깊이 값을 조정하는 것도 가능합니다.
 
-The first approach is commonly used for drawing transparent objects, because
-order-independent transparency is a difficult challenge to solve. However, the
-problem of ordering fragments by depth is much more commonly solved using a
-*depth buffer*. A depth buffer is an additional attachment that stores the depth
-for every position, just like the color attachment stores the color of every
-position. Every time the rasterizer produces a fragment, the depth test will
-check if the new fragment is closer than the previous one. If it isn't, then the
-new fragment is discarded. A fragment that passes the depth test writes its own
-depth to the depth buffer. It is possible to manipulate this value from the
-fragment shader, just like you can manipulate the color output.
 
 ```c++
 #define GLM_FORCE_RADIANS
@@ -117,16 +96,11 @@ fragment shader, just like you can manipulate the color output.
 #include <glm/gtc/matrix_transform.hpp>
 ```
 
-The perspective projection matrix generated by GLM will use the OpenGL depth
-range of `-1.0` to `1.0` by default. We need to configure it to use the Vulkan
-range of `0.0` to `1.0` using the `GLM_FORCE_DEPTH_ZERO_TO_ONE` definition.
+GLM에서 만든 원근 투영 행렬은 기본적으로 OpenGL에서 사용하는 깊이 범위인 `-1.0`에서 `1.0` 사이로 깊이값을 도출하게 되어 있습니다. `GLM_FORCE_DEPTH_ZERO_TO_ONE`를 정의하여 Vulkan에서 사용하는 `0.0`에서 `1.0` 범위가 되도록 설정해야 합니다.
 
-## Depth image and view
+## 깊이 이미지와 뷰
 
-A depth attachment is based on an image, just like the color attachment. The
-difference is that the swap chain will not automatically create depth images for us. We only need a single depth image, because only one draw operation is
-running at once. The depth image will again require the trifecta of resources:
-image, memory and image view.
+색상 어태치먼트처럼 깊이 어태치먼트도 이미지를 기반으로 정의됩니다. 차이점은 스왑 체인이 깊이 이미지를 자동으로 만들어 주지 않는다는 점입니다. 한 번에 하나의 그리기 연산만 수행되는 상태이기 때문에 현재는 깊이 이미지는 하나만 있으면 됩니다. 깊이 이미지 또한 세 가지 리소스인 이미지, 메모리, 이미지 뷰 리소스를 필요로 합니다:
 
 ```c++
 VkImage depthImage;
@@ -134,7 +108,7 @@ VkDeviceMemory depthImageMemory;
 VkImageView depthImageView;
 ```
 
-Create a new function `createDepthResources` to set up these resources:
+이러한 리소스들을 준비하기 위해 `createDepthResources` 함수를 새로 만듭니다:
 
 ```c++
 void initVulkan() {
@@ -152,33 +126,17 @@ void createDepthResources() {
 }
 ```
 
-Creating a depth image is fairly straightforward. It should have the same
-resolution as the color attachment, defined by the swap chain extent, an image
-usage appropriate for a depth attachment, optimal tiling and device local
-memory. The only question is: what is the right format for a depth image? The
-format must contain a depth component, indicated by `_D??_` in the `VK_FORMAT_`.
+깊이 이미지를 만드는 것은 꽤 직관적입니다. 스왑 체인 범위를 통해 정의된 색상 어태치먼트와 동일한 해상도를 가져야 하며, 이미지의 사용법이 깊이 어태치먼트에 적합해야 하고, 타일링과 장치 로컬 메모리에 최적화되어야 합니다. 문제는, 깊이 이미지에 적합한 포맷이 무엇이냐 입니다. 깊이 이미지를 위한 포맷은 깊이 컴포넌트를 반드시 가져야 하고 이는 `VK_FORMAT_`에 `_D??_`로 표시되어 있습니다.
 
-Unlike the texture image, we don't necessarily need a specific format, because
-we won't be directly accessing the texels from the program. It just needs to
-have a reasonable accuracy, at least 24 bits is common in real-world
-applications. There are several formats that fit this requirement:
+텍스처 이미지와는 다르게 특정 포맷을 사용해야 하는 것은 없는데 프로그램에서 텍셀 값을 직접 접근하지는 않을 것이기 때문입니다. 그냥 적절한 정밀도를 가지면 되는데 실제 응용 프로그램에서는 최소 24비트 정도를 사용합니다. 이러한 요구조건에 맞는 포맷들이 몇 가지 있습니다:
 
-* `VK_FORMAT_D32_SFLOAT`: 32-bit float for depth
-* `VK_FORMAT_D32_SFLOAT_S8_UINT`: 32-bit signed float for depth and 8 bit
-stencil component
-* `VK_FORMAT_D24_UNORM_S8_UINT`: 24-bit float for depth and 8 bit stencil
-component
+* `VK_FORMAT_D32_SFLOAT`: 32비트 부동소수점 깊이값
+* `VK_FORMAT_D32_SFLOAT_S8_UINT`: 부호 있는 32비트 부동소수점 깊이값과 8비트의 스텐실 요소
+* `VK_FORMAT_D24_UNORM_S8_UINT`: 24비트 부동소수점 깊이값과 8비트 스텐실 요소
 
-The stencil component is used for [stencil tests](https://en.wikipedia.org/wiki/Stencil_buffer),
-which is an additional test that can be combined with depth testing. We'll look
-at this in a future chapter.
+스텐실 요소는 [스텐실 테스트](https://en.wikipedia.org/wiki/Stencil_buffer)에 사용되는데 깊이 테스트와 함께 사용되는 추가적인 테스트 입니다. 이에 대해서는 나중 챕터에서 살펴보도록 하겠습니다.
 
-We could simply go for the `VK_FORMAT_D32_SFLOAT` format, because support for it
-is extremely common (see the hardware database), but it's nice to add some extra
-flexibility to our application where possible. We're going to write a function
-`findSupportedFormat` that takes a list of candidate formats in order from most
-desirable to least desirable, and checks which is the first one that is
-supported:
+지금은 간단하게 `VK_FORMAT_D32_SFLOAT` 포맷을 사용할 것인데 이 포맷은 거의 대부분 하드웨어에서 지원되기 때문입니다(하드웨어 데이터베이스를 살펴보세요). 그래도 약간의 유연성을 가지도록 하는 것도 좋을 것 같습니다. `findSupportedFormat` 함수를 통해 선호도 순으로 정렬된 포맷들의 후보를 받고, 그 중 지원되는 가장 첫 번째 포맷을 확인합니다:
 
 ```c++
 VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
@@ -186,9 +144,7 @@ VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTil
 }
 ```
 
-The support of a format depends on the tiling mode and usage, so we must also
-include these as parameters. The support of a format can be queried using
-the `vkGetPhysicalDeviceFormatProperties` function:
+포맷의 지원 여부는 타일링 모드와 사용법에 따라 다르기 때문에 이러한 요소들도 매개변수로 받아야 합니다. 포맷의 지원 여부는 `vkGetPhysicalDeviceFormatProperties` 함수를 통해 질의할 수 있습니다:
 
 ```c++
 for (VkFormat format : candidates) {
@@ -197,14 +153,13 @@ for (VkFormat format : candidates) {
 }
 ```
 
-The `VkFormatProperties` struct contains three fields:
+`VkFormatProperties` 구조체는 세 개의 필드를 갖습니다:
 
-* `linearTilingFeatures`: Use cases that are supported with linear tiling
-* `optimalTilingFeatures`: Use cases that are supported with optimal tiling
-* `bufferFeatures`: Use cases that are supported for buffers
+* `linearTilingFeatures`: 선형 타일링이 지원되는 사용법
+* `optimalTilingFeatures`: 최적 타일링지 지원되는 사용법
+* `bufferFeatures`: 버퍼 용으로 지원되는 사용법
 
-Only the first two are relevant here, and the one we check depends on the
-`tiling` parameter of the function:
+여기서는 첫 두 경우만 신경쓰면 되고, 확인은 함수에서 `tiling` 매개변수로 받은 값으로 수행합니다:
 
 ```c++
 if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
@@ -214,8 +169,7 @@ if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features)
 }
 ```
 
-If none of the candidate formats support the desired usage, then we can either
-return a special value or simply throw an exception:
+후보의 어떤 포맷도 지원되지 않는 경우 특수한 값을 반환하거나 예외를 던지도록 처리할 수 있습니다:
 
 ```c++
 VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
@@ -234,8 +188,7 @@ VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTil
 }
 ```
 
-We'll use this function now to create a `findDepthFormat` helper function to
-select a format with a depth component that supports usage as depth attachment:
+이제 이 함수를 사용하는 `findDepthFormat` 헬퍼 함수를 만들어 깊이 요소를 가지면서 깊이 어태치먼트 사용법을 지원하는 포맷을 선택하도록 합니다:
 
 ```c++
 VkFormat findDepthFormat() {
@@ -247,12 +200,7 @@ VkFormat findDepthFormat() {
 }
 ```
 
-Make sure to use the `VK_FORMAT_FEATURE_` flag instead of `VK_IMAGE_USAGE_` in
-this case. All of these candidate formats contain a depth component, but the
-latter two also contain a stencil component. We won't be using that yet, but we
-do need to take that into account when performing layout transitions on images
-with these formats. Add a simple helper function that tells us if the chosen
-depth format contains a stencil component:
+여기에서는 `VK_IMAGE_USAGE_` 대신 `VK_FORMAT_FEATURE_` 플래그를 사용해야 합니다. 후보 포맷들은 모두 깊이 요소를 가지고 있지만 뒤의 두 경우는 스텐실 요소도 포함합니다. 아직은 사용하지 않을 것이지만 이러한 포맷들에 대해 레이아웃 전환을 수행할 때에는 스텐실 요소도 고려해야 합니다. 간단한 헬퍼 함수를 하나 더 추가해서 선택된 깊이 포맷이 스텐실 요소를 포함하는지 체크합니다:
 
 ```c++
 bool hasStencilComponent(VkFormat format) {
@@ -260,23 +208,20 @@ bool hasStencilComponent(VkFormat format) {
 }
 ```
 
-Call the function to find a depth format from `createDepthResources`:
+`createDepthResources`에서 함수를 호출해 깊이 포맷을 찾습니다:
 
 ```c++
 VkFormat depthFormat = findDepthFormat();
 ```
 
-We now have all the required information to invoke our `createImage` and
-`createImageView` helper functions:
+이제 `createImage`와 `createImageView` 헬퍼 함수를 호출하기 위한 모든 필요한 정보가 준비되었습니다:
 
 ```c++
 createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
 depthImageView = createImageView(depthImage, depthFormat);
 ```
 
-However, the `createImageView` function currently assumes that the subresource
-is always the `VK_IMAGE_ASPECT_COLOR_BIT`, so we will need to turn that field
-into a parameter:
+하지만 `createImageView` 함수는 현재 서브리소스가 `VK_IMAGE_ASPECT_COLOR_BIT`인 것으로 가정하고 있기 때문에 이를 매개변수로 바꿔야 합니다:
 
 ```c++
 VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) {
@@ -286,7 +231,7 @@ VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags a
 }
 ```
 
-Update all calls to this function to use the right aspect:
+이 함수를 호출하는 모든 부분에서 적합한 aspect를 사용하도록 수정합니다:
 
 ```c++
 swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT);
@@ -296,27 +241,19 @@ depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_
 textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT);
 ```
 
-That's it for creating the depth image. We don't need to map it or copy another
-image to it, because we're going to clear it at the start of the render pass
-like the color attachment.
+여기까지가 깊이 이미지 생성 부분입니다. 맵핑이나 다른 이미지를 복사할 필요는 없습니다. 색상 어태치먼트처럼 렌더 패스의 시작 지점에서 지울 것이기 때문입니다.
 
-### Explicitly transitioning the depth image
+### 깊이 이미지의 명시적 전환
 
-We don't need to explicitly transition the layout of the image to a depth
-attachment because we'll take care of this in the render pass. However, for
-completeness I'll still describe the process in this section. You may skip it if
-you like.
+이미지의 레이아웃을 깊이 어태치먼트로 명시적으로 전환할 필요는 없는데, 이를 렌더 패스에서 처리할 예정이기 때문입니다. 하지만 완전성을 위해 이 섹션에서 설명은 진행 하도록 하겠습니다. 필요하다면 그냥 넘어가셔도 됩니다.
 
-Make a call to `transitionImageLayout` at the end of the `createDepthResources`
-function like so:
+`createDepthResources`의 마지막에 `transitionImageLayout`를 호출합니다:
 
 ```c++
 transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
 ```
 
-The undefined layout can be used as initial layout, because there are no
-existing depth image contents that matter. We need to update some of the logic
-in `transitionImageLayout` to use the right subresource aspect:
+깊이 이미지에 이미 쓰여있는 정보는 상관이 없기 때문에 정의되지 않은 레이아웃을 초기 레이아웃으로 사용해도 됩니다. `transitionImageLayout`에서 적정한 서브리소스 aspect를 사용하도록 몇 가지 로직을 수정합니다:
 
 ```c++
 if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
@@ -330,10 +267,9 @@ if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
 }
 ```
 
-Although we're not using the stencil component, we do need to include it in the
-layout transitions of the depth image.
+스텐실 요소는 사용하지 않을 것이지만 깊이 이미지의 레이아웃 전환에는 포함 시켜야 합니다.
 
-Finally, add the correct access masks and pipeline stages:
+마지막으로 적절한 접근 마스크와 파이프라인 스테이지를 추가합니다:
 
 ```c++
 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
@@ -359,17 +295,11 @@ if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANS
 }
 ```
 
-The depth buffer will be read from to perform depth tests to see if a fragment
-is visible, and will be written to when a new fragment is drawn. The reading
-happens in the `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` stage and the
-writing in the `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`. You should pick the
-earliest pipeline stage that matches the specified operations, so that it is
-ready for usage as depth attachment when it needs to be.
+깊이 테스트 진행 과정에서 프래그먼트가 보이는지를 테스트 하기 위해 깊이 버퍼값을 읽어야 하고, 새로운 프래그먼트가 그려지면 값이 쓰여져야 합니다. 값을 읽는 것은 `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` 스테이지에서, 쓰는 것은 `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT` 스테이지에서 이루어집니다. 명시된 연산과 일치하는 가장 빠른 단계의 파이프라인 스테이지를 선택해서 필요한 시점에 깊이 어태치먼트로 사용될 수 있게끔 해야 합니다.
 
-## Render pass
+## 렌더 패스
 
-We're now going to modify `createRenderPass` to include a depth attachment.
-First specify the `VkAttachmentDescription`:
+이제 `createRenderPass`를 수정해서 깊이 어태치먼트를 포함하도록 해야 합니다. 먼저 `VkAttachmentDescription`를 명시합니다:
 
 ```c++
 VkAttachmentDescription depthAttachment{};
@@ -383,11 +313,7 @@ depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 ```
 
-The `format` should be the same as the depth image itself. This time we don't
-care about storing the depth data (`storeOp`), because it will not be used after
-drawing has finished. This may allow the hardware to perform additional
-optimizations. Just like the color buffer, we don't care about the previous
-depth contents, so we can use `VK_IMAGE_LAYOUT_UNDEFINED` as `initialLayout`.
+`format`은 깊이 이미지와 동일해야 합니다. 그리기가 끝나면 깊이 값은 사용되지 않기 때문에 깊이갚의 저장(`storeOp`)은 신경쓰지 않습니다. 이렇게 하면 하드웨어가 추가적인 최적화를 진행할 수 있게 됩니다. 색상 버퍼처럼 이전 깊이 값은 신경쓰지 않으므로 `VK_IMAGE_LAYOUT_UNDEFINED`를 `initialLayout`로 사용합니다.
 
 ```c++
 VkAttachmentReference depthAttachmentRef{};
@@ -395,7 +321,7 @@ depthAttachmentRef.attachment = 1;
 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 ```
 
-Add a reference to the attachment for the first (and only) subpass:
+첫 서브패스의 어태치먼트에 참조를 추가합니다:
 
 ```c++
 VkSubpassDescription subpass{};
@@ -405,9 +331,7 @@ subpass.pColorAttachments = &colorAttachmentRef;
 subpass.pDepthStencilAttachment = &depthAttachmentRef;
 ```
 
-Unlike color attachments, a subpass can only use a single depth (+stencil)
-attachment. It wouldn't really make any sense to do depth tests on multiple
-buffers.
+색상 어태치먼트와는 달리 서브패스는 하나의 깊이(+스텐실) 어태치먼트만 사용할 수 있습니다. 여러 버퍼에 대해 깊이 테스트를 수행하는 것은 말이 안됩니다.
 
 ```c++
 std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
@@ -421,8 +345,7 @@ renderPassInfo.dependencyCount = 1;
 renderPassInfo.pDependencies = &dependency;
 ```
 
-Next, update the `VkSubpassDependency` struct to refer to both
-attachments.
+다음으로 `VkSubpassDependency` 구조체를 갱신해서 두 어태치먼트를 참조하도록 합니다.
 
 ```c++
 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
@@ -431,13 +354,11 @@ dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIP
 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
 ```
 
-Finally, we need to extend our subpass dependencies to make sure that there is no conflict between the transitioning of the depth image and it being cleared as part of its load operation. The depth image is first accessed in the early fragment test pipeline stage and because we have a load operation that *clears*, we should specify the access mask for writes.
+마지막으로 서브패스의 의존성을 확장하여 로드(load) 연산의 한 과정으로 깊이 이미지의 전환과 지우기간에 충돌이 없도록 해 줍니다. 깊이 이미지는 먼저 초기 프래그먼트 테스트 파이프라인 스테이지에서 접근되고 *지우기* 로드 연산을 사용하기 때문에 접근 마스크를 쓰기로 명시해 주어야 합니다.
 
-## Framebuffer
+## 프레임버퍼
 
-The next step is to modify the framebuffer creation to bind the depth image to
-the depth attachment. Go to `createFramebuffers` and specify the depth image
-view as second attachment:
+다음 단계는 프레임버퍼 생성 부분을 수정해 깊이 이미지를 깊이 어태치먼트에 바인딩하는 것입니다. `createFramebuffers`로 가서 깊이 이미지 뷰를 두 번째 어태치먼트로 명시합니다:
 
 ```c++
 std::array<VkImageView, 2> attachments = {
@@ -455,12 +376,9 @@ framebufferInfo.height = swapChainExtent.height;
 framebufferInfo.layers = 1;
 ```
 
-The color attachment differs for every swap chain image, but the same depth
-image can be used by all of them because only a single subpass is running at the
-same time due to our semaphores.
+각 스왑 체인 이미지마다 색상 어태치먼트가 다르지만 깊이 이미지는 하나로 모두에 대해 사용할 수 있는데 우리 세마포어로 인해 한 번에 하나의 서브패스만 실행되기 때문입니다.
 
-You'll also need to move the call to `createFramebuffers` to make sure that it
-is called after the depth image view has actually been created:
+`createFramebuffers`의 호출을 옮겨서 깊이 이미지 뷰가 실제로 생성된 다음에 호출되도록 합니다:
 
 ```c++
 void initVulkan() {
@@ -471,11 +389,9 @@ void initVulkan() {
 }
 ```
 
-## Clear values
+## 지우기 값
 
-Because we now have multiple attachments with `VK_ATTACHMENT_LOAD_OP_CLEAR`, we
-also need to specify multiple clear values. Go to `recordCommandBuffer` and
-create an array of `VkClearValue` structs:
+`VK_ATTACHMENT_LOAD_OP_CLEAR`에 여러 개의 어태치먼트가 존재하기 때문에 지우기 값도 여러 개를 명시해 주어야 합니다. `recordCommandBuffer`로 가서 `VkClearValue`의 배열을 만듭니다:
 
 ```c++
 std::array<VkClearValue, 2> clearValues{};
@@ -486,18 +402,13 @@ renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
 renderPassInfo.pClearValues = clearValues.data();
 ```
 
-The range of depths in the depth buffer is `0.0` to `1.0` in Vulkan, where `1.0`
-lies at the far view plane and `0.0` at the near view plane. The initial value
-at each point in the depth buffer should be the furthest possible depth, which
-is `1.0`.
+Vulkan에서 깊이 버퍼의 깊이값은 `0.0`과 `1.0` 사이이고, `1.0`이 원면, `0.0`이 근면입니다. 각 점의 초기값은 가장 먼 깊이여야 하므로 `1.0`으로 설정합니다.
 
-Note that the order of `clearValues` should be identical to the order of your attachments.
+`clearValues`의 순서가 어태치먼트의 순서와 대응된다는 것에 유의하세요.
 
-## Depth and stencil state
+## 깊이와 스텐실 상태
 
-The depth attachment is ready to be used now, but depth testing still needs to
-be enabled in the graphics pipeline. It is configured through the
-`VkPipelineDepthStencilStateCreateInfo` struct:
+이제 깊이 어태치먼트를 사용할 준비가 되었고, 그래픽스 파이프라인에서 실제로 깊이 테스트를 수행하도록 설정해야 합니다. 이는 `VkPipelineDepthStencilStateCreateInfo` 구조체를 통해 설정됩니다:
 
 ```c++
 VkPipelineDepthStencilStateCreateInfo depthStencil{};
@@ -506,18 +417,13 @@ depthStencil.depthTestEnable = VK_TRUE;
 depthStencil.depthWriteEnable = VK_TRUE;
 ```
 
-The `depthTestEnable` field specifies if the depth of new fragments should be
-compared to the depth buffer to see if they should be discarded. The
-`depthWriteEnable` field specifies if the new depth of fragments that pass the
-depth test should actually be written to the depth buffer.
+`depthTestEnable` 필드는 새로운 프래그먼트의 깊이값에 대해 깊이 버퍼에 기존에 쓰여진 값과의 비교를 통해 버리기를 수행할지 여부를 명시합니다. `depthWriteEnable` 필드는 테스트를 통과한 새로운 프래그먼트의 깊이 값이 깊이 버퍼에 쓰여길 것인지를 명시합니다.
 
 ```c++
 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
 ```
 
-The `depthCompareOp` field specifies the comparison that is performed to keep or
-discard fragments. We're sticking to the convention of lower depth = closer, so
-the depth of new fragments should be *less*.
+`depthCompareOp` 필드는 프래그먼트의 버리기나 유지를 결정하기 위한 비교 연산을 명시합니다. 일반적으로 사용되는 적은 깊이값(=더 가까운 프래그먼트)을 사용할 것이고, 이는 즉 새로운 프래그먼트의 깊이값이 더 *작아야 한다*는 의미입니다.
 
 ```c++
 depthStencil.depthBoundsTestEnable = VK_FALSE;
@@ -525,10 +431,7 @@ depthStencil.minDepthBounds = 0.0f; // Optional
 depthStencil.maxDepthBounds = 1.0f; // Optional
 ```
 
-The `depthBoundsTestEnable`, `minDepthBounds` and `maxDepthBounds` fields are
-used for the optional depth bound test. Basically, this allows you to only keep
-fragments that fall within the specified depth range. We won't be using this
-functionality.
+`depthBoundsTestEnable`, `minDepthBounds`, `maxDepthBounds` 필드는 깊이 바운드(bound) 테스트에 대한 선택적인 값입니다. 이는 특정 범위 내의 프래그먼트만 유지할 수 있도록 해 줍니다. 우리는 사용하지 않을 것입니다.
 
 ```c++
 depthStencil.stencilTestEnable = VK_FALSE;
@@ -536,29 +439,21 @@ depthStencil.front = {}; // Optional
 depthStencil.back = {}; // Optional
 ```
 
-The last three fields configure stencil buffer operations, which we also won't
-be using in this tutorial. If you want to use these operations, then you will
-have to make sure that the format of the depth/stencil image contains a stencil
-component.
+마지막 세 필드는 스텐실 버퍼 연산의 설정이고, 이 튜토리얼에서는 사용하지 않습니다. 이 연산을 사용하려면 깊이/스텐실 이미지의 포맷이 스텐실 요소를 가지고 있어야만 합니다.
 
 ```c++
 pipelineInfo.pDepthStencilState = &depthStencil;
 ```
 
-Update the `VkGraphicsPipelineCreateInfo` struct to reference the depth stencil
-state we just filled in. A depth stencil state must always be specified if the
-render pass contains a depth stencil attachment.
+`VkGraphicsPipelineCreateInfo` 구조체를 수정하여 방금 설정한 깇이 스텐실 상태를 참조하도록 합니다. 깊이 스텐실 상태는 렌더 패스가 깊이 스텐실 어태치먼트를 가지는 경우, 항상 명시 되어야만 합니다.
 
-If you run your program now, then you should see that the fragments of the
-geometry are now correctly ordered:
+이 상태에서 프로그램을 실행하면 각 물체의 프래그먼트가 올바른 순서로 표시되는 것을 보실 수 있습니다:
 
 ![](/images/depth_correct.png)
 
-## Handling window resize
+## 윈도우 리사이징 처리
 
-The resolution of the depth buffer should change when the window is resized to
-match the new color attachment resolution. Extend the `recreateSwapChain`
-function to recreate the depth resources in that case:
+윈도우가 리사이징되면 새로운 색상 어태치먼트의 해상도에 맞게 깊이 버퍼 해상도 또한 변해야 합니다. `recreateSwapChain` 함수를 확장하여 이러한 경우 깊이 리소스를 재생성하도록 합니다:
 
 ```c++
 void recreateSwapChain() {
@@ -579,7 +474,7 @@ void recreateSwapChain() {
 }
 ```
 
-The cleanup operations should happen in the swap chain cleanup function:
+정리는 스왑 체인 정리 함수에서 수행되어야 합니다:
 
 ```c++
 void cleanupSwapChain() {
@@ -591,9 +486,7 @@ void cleanupSwapChain() {
 }
 ```
 
-Congratulations, your application is now finally ready to render arbitrary 3D
-geometry and have it look right. We're going to try this out in the next chapter
-by drawing a textured model!
+축하합니다. 이제 프로그램이 어떤 3D 형상이라도 제대로 그릴 수 있는 준비가 되었습니다. 다음 챕터에서 텍스처가 입혀진 모델을 그려서 이를 시도해 보겠습니다!
 
 [C++ code](/code/27_depth_buffering.cpp) /
 [Vertex shader](/code/27_shader_depth.vert) /

From eca0c50f6fb1a09f4434876cb39dace04304b6df Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 4 Mar 2024 12:39:54 +0900
Subject: [PATCH 42/47] kr translate 08 loading models

---
 kr/08_Loading_models.md | 166 ++++++++++------------------------------
 kr/kr_glossary.md       |   5 +-
 2 files changed, 45 insertions(+), 126 deletions(-)

diff --git a/kr/08_Loading_models.md b/kr/08_Loading_models.md
index 620e31bf..a7077953 100644
--- a/kr/08_Loading_models.md
+++ b/kr/08_Loading_models.md
@@ -1,35 +1,22 @@
-## Introduction
+## 서론
 
-Your program is now ready to render textured 3D meshes, but the current geometry
-in the `vertices` and `indices` arrays is not very interesting yet. In this
-chapter we're going to extend the program to load the vertices and indices from
-an actual model file to make the graphics card actually do some work.
+이제 텍스처가 입혀진 3D 메쉬를 렌더링할 준비가 되었지만 지금의 `vertices`와 `indices` 배열에 정의된 형상은 좀 재미가 없습니다. 이 챕터에서는 프로그램을 확장해서 실제 모델 파일로부터 정점과 인덱스를 로드하여 그래픽 카드가 좀 더 실질적인 작업을 하도록 만들어 보겠습니다.
 
-Many graphics API tutorials have the reader write their own OBJ loader in a
-chapter like this. The problem with this is that any remotely interesting 3D
-application will soon require features that are not supported by this file
-format, like skeletal animation. We *will* load mesh data from an OBJ model in
-this chapter, but we'll focus more on integrating the mesh data with the program
-itself rather than the details of loading it from a file.
+많은 그래픽스 API 튜토리얼에서는 이러한 챕터에서 직접 OBJ 로더를 작성합니다. 문제는 실제 3D 응용 프로그램을 만들다면 이 포맷에서 지원하지 않는, 예를 들자면 스켈레톤 애니메이션 같은 기능을 얼마 지나지 않아 필요로 하게 된다는 것입니다. 이 챕터에서 우리도 역시 OBJ 모델로부터 메쉬 데이터를 로딩할 것이지만, 파일에서 이러한 데이터를 어떻게 로딩하는지보다는 실제 프로그램에서 어떻게 메쉬 데이터를 사용하도록 해야 하는지에 대해서 집중하도록 하겠습니다.
 
-## Library
+## 라이브러리
 
-We will use the [tinyobjloader](https://github.com/syoyo/tinyobjloader) library
-to load vertices and faces from an OBJ file. It's fast and it's easy to
-integrate because it's a single file library like stb_image. Go to the
-repository linked above and download the `tiny_obj_loader.h` file to a folder in
-your library directory.
+우리는 [tinyobjloader](https://github.com/syoyo/tinyobjloader) 라이브러리를 사용해 OBJ 파일로부터 정점과 표면(face) 정보를 로드할 것입니다. stb_image처럼 하나의 파일로 된 라이브러리기 때문에 빠르고, 프로그램에 통합하기도 쉽습니다. 위 링크의 레포지토리로 가서 `tiny_obj_loader.h` 파일을 여러분의 라이브러리 리렉토리에 다운로드 하십시오.
 
-**Visual Studio**
+**비주얼 스튜디오**
 
-Add the directory with `tiny_obj_loader.h` in it to the `Additional Include
-Directories` paths.
+`tiny_obj_loader.h`가 있는 디렉토리를 `추가 포함 디렉토리` 경로에 추가하세요.
 
 ![](/images/include_dirs_tinyobjloader.png)
 
 **Makefile**
 
-Add the directory with `tiny_obj_loader.h` to the include directories for GCC:
+`tiny_obj_loader.h`가 있는 디렉토리는 GCC의 include 리렉토리로 추가하세요:
 
 ```text
 VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64
@@ -41,28 +28,18 @@ TINYOBJ_INCLUDE_PATH = /home/user/libraries/tinyobjloader
 CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH)
 ```
 
-## Sample mesh
+## 예제 메쉬
 
-In this chapter we won't be enabling lighting yet, so it helps to use a sample
-model that has lighting baked into the texture. An easy way to find such models
-is to look for 3D scans on [Sketchfab](https://sketchfab.com/). Many of the
-models on that site are available in OBJ format with a permissive license.
+이 챕터에서 아직 라이팅(lighting)을 적용하진 않을 것이기 때문에 텍스처에 라이팅이 베이크 되어 있는 예제 모델을 사용하는 것이 좋을 것 같습니다. 쉬운 방법 중 하나는 [Sketchfab](https://sketchfab.com/)에서 3D 스캐닝 모델을 찾는 것입니다. 이 사이트의 많은 모델들이 OBJ 포맷을 허용적 라이센스(permissive license)로 제공합니다.
 
-For this tutorial I've decided to go with the [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)
-model by [nigelgoh](https://sketchfab.com/nigelgoh) ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). I tweaked the size and orientation of the model to use it
-as a drop in replacement for the current geometry:
+이 튜토리얼에서는 [nigelgoh](https://sketchfab.com/nigelgoh)이 모델링한 [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)을 사용하기로 했습니다 ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). 크기와 자세를 바꾸어서 현재 형상을 바로 대체할 수 있도록 했습니다:
 
 * [viking_room.obj](/resources/viking_room.obj)
 * [viking_room.png](/resources/viking_room.png)
 
-Feel free to use your own model, but make sure that it only consists of one
-material and that is has dimensions of about 1.5 x 1.5 x 1.5 units. If it is
-larger than that, then you'll have to change the view matrix. Put the model file
-in a new `models` directory next to `shaders` and `textures`, and put the
-texture image in the `textures` directory.
+다른 모델을 사용해도 되지만 하나의 머티리얼(material)로만 구성되고, 크기가 대략 1.5 x 1.5 x 1.5 여야 합니다. 이보다 큰 경우에는 뷰 행렬을 수정해야 합니다. `shaders`와 `textures` 디렉토리와 같은 위치에 `models` 디렉토리를 만들고 모델 파일을 넣으십시오. 그리고 텍스처 이미지는 `texture` 디렉토리에 넣으십시오.
 
-Put two new configuration variables in your program to define the model and
-texture paths:
+모델과 텍스처 경로에 대한 변수를 프로그램에 추가합니다:
 
 ```c++
 const uint32_t WIDTH = 800;
@@ -72,17 +49,15 @@ const std::string MODEL_PATH = "models/viking_room.obj";
 const std::string TEXTURE_PATH = "textures/viking_room.png";
 ```
 
-And update `createTextureImage` to use this path variable:
+그리고 `createTextureImage`에서 이 경로를 사용하도록 수정합니다:
 
 ```c++
 stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
 ```
 
-## Loading vertices and indices
+## 정점과 인덱스 로딩
 
-We're going to load the vertices and indices from the model file now, so you
-should remove the global `vertices` and `indices` arrays now. Replace them with
-non-const containers as class members:
+이제 모델 파일로부터 정점과 인덱스를 로딩할 것입니다. 따라서 전역 선언된 `vertices`와 `indices` 배열은 제거해야 합니다. 이들을 상수가 아닌 컨테이너로 클래스 멤버에 추가합니다:
 
 ```c++
 std::vector<Vertex> vertices;
@@ -91,28 +66,20 @@ VkBuffer vertexBuffer;
 VkDeviceMemory vertexBufferMemory;
 ```
 
-You should change the type of the indices from `uint16_t` to `uint32_t`, because
-there are going to be a lot more vertices than 65535. Remember to also change
-the `vkCmdBindIndexBuffer` parameter:
+인덱스의 타입을 `uint16_t`에서 `uint32_t`로 수정해야 하는데 65535보다 훨씬 많은 정점이 존재하기 때문입니다. `vkCmdBindIndexBuffer` 매개변수도 바꾸는 것을 잊지 마세요:
 
 ```c++
 vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
 ```
 
-The tinyobjloader library is included in the same way as STB libraries. Include
-the `tiny_obj_loader.h` file and make sure to define
-`TINYOBJLOADER_IMPLEMENTATION` in one source file to include the function
-bodies and avoid linker errors:
+tinyobjloader 라이브러리는 STB 라이브러리와 같은 방식으로 include됩니다. `tiny_obj_loader.h` 파일을 include하고 소스 파일 중 하나에 `TINYOBJLOADER_IMPLEMENTATION`를 선언하여 함수 본문에 대한 링크 오류가 발생하지 않도록 합니다:
 
 ```c++
 #define TINYOBJLOADER_IMPLEMENTATION
 #include <tiny_obj_loader.h>
 ```
 
-We're now going to write a `loadModel` function that uses this library to
-populate the `vertices` and `indices` containers with the vertex data from the
-mesh. It should be called somewhere before the vertex and index buffers are
-created:
+이제 이 라이브러리를 사용해 메쉬의 정점 데이터를 `vertices`와 `indices` 컨테이너에 담는 `loadModel` 함수를 만들 것입니다. 이 함수는 정점과 인덱스 버퍼가 생성되기 이전에 호출되어야 합니다:
 
 ```c++
 void initVulkan() {
@@ -130,8 +97,7 @@ void loadModel() {
 }
 ```
 
-A model is loaded into the library's data structures by calling the
-`tinyobj::LoadObj` function:
+`tinyobj::LoadObj` 함수를 호출하면 모델이 라이브러리의 데이터 구조에 담겨 로딩됩니다:
 
 ```c++
 void loadModel() {
@@ -146,27 +112,13 @@ void loadModel() {
 }
 ```
 
-An OBJ file consists of positions, normals, texture coordinates and faces. Faces
-consist of an arbitrary amount of vertices, where each vertex refers to a
-position, normal and/or texture coordinate by index. This makes it possible to
-not just reuse entire vertices, but also individual attributes.
+OBj 파일은 위치, 법선, 텍스처 좌표와 표면(face)으로 구성되어 있습니다 표면은 임의 개의 정점으로 정의되는데 각 정점은 위치, 법선과 텍스처 좌표의 인덱스를 참조합니다. 이러한 방식을 통해 정점 전체를 재사용하는 것 뿐만 아니라 개별 속성을 재사용하는 것도 가능합니다.
 
-The `attrib` container holds all of the positions, normals and texture
-coordinates in its `attrib.vertices`, `attrib.normals` and `attrib.texcoords`
-vectors. The `shapes` container contains all of the separate objects and their
-faces. Each face consists of an array of vertices, and each vertex contains the
-indices of the position, normal and texture coordinate attributes. OBJ models
-can also define a material and texture per face, but we will be ignoring those.
+`attrib` 컨테이너는 전체 위치, 법선, 텍스처 좌표를 `attrib.vertices`, `attrib.normals`, `attrib.texcoords` 벡터에 저장하고 있습니다. `shapes` 컨테이너는 모든 개별 물체와 물체를 구성하는 표면을 가지고 있습니다. 각 표면은 정점의 배열로 이루어지며 각 정점은 위치, 법선, 텍스처 좌표 속성의 인덱스를 가지고 있습니다. OBJ 모델은 각 표면별 텍스처와 머티리얼을 정의할 수 있게 되어 있는데 지금은 이를 무시할 것입니다.
 
-The `err` string contains errors and the `warn` string contains warnings that occurred while loading the
-file, like a missing material definition. Loading only really failed if the
-`LoadObj` function returns `false`. As mentioned above, faces in OBJ files can
-actually contain an arbitrary number of vertices, whereas our application can
-only render triangles. Luckily the `LoadObj` has an optional parameter to
-automatically triangulate such faces, which is enabled by default.
+`err` 문자열은 파일을 로딩하는 과정에서 발생한 오류를, `warn` 문자열은 경고를 가지고 있습니다. 예를 들자면 머티리얼 정의가 없는 경우 등이 있을 것입니다. `LoadObj` 함수가 `false`를 반환하는 경우가 실제 로딩이 실패한 경우입니다. 위에서 이야기한 것처럼 OBJ 파일에서 표면은 임의의 정점을 가질 수 있지만, 우리 프로그램에서는 삼각형만 그릴 것입니다. 다행히 `LoadObj` 함수는 표면을 자동으로 삼각화(triangulate) 해 주는 기능을 사용하기 위한 매개변수가 있고, 이는 사용하는 것이 기본값으로 되어 있습니다.
 
-We're going to combine all of the faces in the file into a single model, so just
-iterate over all of the shapes:
+파일에 들어있는 모든 표면을 하나의 모델로 만들 것이기 때문에 shape을 반복(iterate)합니다:
 
 ```c++
 for (const auto& shape : shapes) {
@@ -174,9 +126,7 @@ for (const auto& shape : shapes) {
 }
 ```
 
-The triangulation feature has already made sure that there are three vertices
-per face, so we can now directly iterate over the vertices and dump them
-straight into our `vertices` vector:
+삼각화 기능을 통해 각 표면별로 세 개의 정점을 가지는 것이 보장되어 있기 때문에 정점을 반복하며 `vertices` 벡터에 집어 넣습니다:
 
 ```c++
 for (const auto& shape : shapes) {
@@ -190,10 +140,7 @@ for (const auto& shape : shapes) {
 ```
 
 For simplicity, we will assume that every vertex is unique for now, hence the
-simple auto-increment indices. The `index` variable is of type
-`tinyobj::index_t`, which contains the `vertex_index`, `normal_index` and
-`texcoord_index` members. We need to use these indices to look up the actual
-vertex attributes in the `attrib` arrays:
+simple auto-increment indices.. `index` 변수는 `tinyobj::index_t` 타입인데, 이는 `vertex_index`, `normal_index`, `texcoord_index` 멤버를 가집니다. 이 인덱스를 사용해서 `attrib` 배열로부터 실제 정점 어트리뷰트를 가져옵니다:
 
 ```c++
 vertex.pos = {
@@ -210,21 +157,13 @@ vertex.texCoord = {
 vertex.color = {1.0f, 1.0f, 1.0f};
 ```
 
-Unfortunately the `attrib.vertices` array is an array of `float` values instead
-of something like `glm::vec3`, so you need to multiply the index by `3`.
-Similarly, there are two texture coordinate components per entry. The offsets of
-`0`, `1` and `2` are used to access the X, Y and Z components, or the U and V
-components in the case of texture coordinates.
+안타깝게도 `attrib.vertices` 배열은 `glm::vec3`와 같은 것이 아니고 `float`의 배열입니다. 따라서 익덱스에 `3`을 곱해 주어야 합니다. 또한 텍스처 좌표 요소는 두 개씩 들어 있습니다. X, Y, Z 요소 또는 텍스처 좌표의 경우 U, V 요소에 대해서 `0`, `1`, `2`의 오프셋을 가집니다.
 
-Run your program now with optimization enabled (e.g. `Release` mode in Visual
-Studio and with the `-O3` compiler flag for GCC`). This is necessary, because
-otherwise loading the model will be very slow. You should see something like the
-following:
+최적화를 수행한 상태(예를들어 비주얼 스튜디오에서는 `Release` 모드, GCC에서는 `-O3` 컴파일 플래스를 사용)에서 프로그램을 실행해 보세요. 이렇게 하지 않으면 모델을 로딩하는 것이 매우 느릴 겁니다. 그 결과로 아래와 같은 모습이 보일 겁니다:
 
 ![](/images/inverted_texture_coordinates.png)
 
-Great, the geometry looks correct, but what's going on with the texture? The OBJ format assumes a coordinate system where a vertical coordinate of `0` means the bottom of the image, however we've uploaded our image into Vulkan in a top to bottom orientation where `0` means the top of the image. Solve this by
-flipping the vertical component of the texture coordinates:
+좋습니다. 형상은 맞는 것 같네요. 하지만 텍스처에 무슨 문제가 생긴 걸까요? OBJ 포맷은 수직 좌표에서 `0`이 이미지의 하단이라고 가정하는데 우리는 Vulkan에 이미지를 업로드 할 때 위쪽이 `0`을 의미하도록 정의하였습니다. 텍스처 좌표의 수직 요소를 뒤집어서 이 문제를 해결합니다:
 
 ```c++
 vertex.texCoord = {
@@ -233,22 +172,18 @@ vertex.texCoord = {
 };
 ```
 
-When you run your program again, you should now see the correct result:
+다시 프로그램을 실행하면, 올바른 결과를 볼 수 있습니다:
 
 ![](/images/drawing_model.png)
 
-All that hard work is finally beginning to pay off with a demo like this!
 
->As the model rotates you may notice that the rear (backside of the walls) looks a bit funny. This is normal and is simply because the model is not really designed to be viewed from that side.
+지금까지의 모든 노력이 이제 이 같은 데모를 통해 결실을 얻게 되었습니다!
 
-## Vertex deduplication
+>모델이 회전되면서 뒤쪽(벽면의 뒷면)이 이상하게 보이는 것을 눈치 채실 수 있을 겁니다. 이는 모델 자체가 뒤쪽에서 보는 것을 고려하지 많고 만들어졌기 때문이고, 정상적인 현상입니다.
 
-Unfortunately we're not really taking advantage of the index buffer yet. The
-`vertices` vector contains a lot of duplicated vertex data, because many
-vertices are included in multiple triangles. We should keep only the unique
-vertices and use the index buffer to reuse them whenever they come up. A
-straightforward way to implement this is to use a `map` or `unordered_map` to
-keep track of the unique vertices and respective indices:
+## 중복 정점 제거
+
+아쉽게도 지금은 인덱스 버퍼를 통한 이점을 얻지 못하고 있습니다. `vertices` 벡터는 중복된 정점 데이터가 많은데 많은 정점들이 여러 삼각형에 중복되어 포함되기 때문입니다. 고유한 정점만을 남기고 인덱스 버퍼를 사용해 재사용해야 합니다. 이를 구현하는 간단한 방법은 `map`이나 `unordered_map`을 사용해 고유한 정점과 그에 상응하는 인덱스를 추적하는 것입니다:
 
 ```c++
 #include <unordered_map>
@@ -273,17 +208,9 @@ for (const auto& shape : shapes) {
 }
 ```
 
-Every time we read a vertex from the OBJ file, we check if we've already seen a
-vertex with the exact same position and texture coordinates before. If not, we
-add it to `vertices` and store its index in the `uniqueVertices` container.
-After that we add the index of the new vertex to `indices`. If we've seen the
-exact same vertex before, then we look up its index in `uniqueVertices` and
-store that index in `indices`.
+OBJ 파일에서 정점을 읽어올 때마다 동일한 위치와 텍스처 좌표를 갖는 정점이 이미 존재하는지를 살펴봅니다. 없다면, `vertices`에 추가하고 그 인덱스를 `uniqueVertices` 컨테이너에 추가합니다. 그리고 그 정점의 인덱스를 `indices`에 추가합니다. 이미 존재하는 정점이라면 `uniqueVertices`에서 인덱스를 찾아서 그 인덱스를 `indices`에 저장합니다.
 
-The program will fail to compile right now, because using a user-defined type
-like our `Vertex` struct as key in a hash table requires us to implement two
-functions: equality test and hash calculation. The former is easy to implement
-by overriding the `==` operator in the `Vertex` struct:
+지금 컴파일하면 컴파일이 실패하게 되는데 `Vertex`와 같은 유저가 정의한 타입을 해시 테이블의 키로 사용하기 위해서는 두 가지 기능을 구현해야 하기 때문입니다. 동일성 테스트와 해시 계산 기능을 구현해야 합니다. 전자는 `==`연산자를 `Vertex` 구조체에 오버라이딩 하면 됩니다:
 
 ```c++
 bool operator==(const Vertex& other) const {
@@ -291,11 +218,7 @@ bool operator==(const Vertex& other) const {
 }
 ```
 
-A hash function for `Vertex` is implemented by specifying a template
-specialization for `std::hash<T>`. Hash functions are a complex topic, but
-[cppreference.com recommends](http://en.cppreference.com/w/cpp/utility/hash) the
-following approach combining the fields of a struct to create a decent quality
-hash function:
+`Vertex`의 해시 함수는 `std::hash<T>`에 대한 템플릿 특수화를 명시하여 구현할 수 있습니다. 해시 함수는 복잡한 주제이지만 [cppreference.com의 추천](http://en.cppreference.com/w/cpp/utility/hash)에 따라서 아래와 같이 구조체의 필드를 결합해서 괜찮은 해시 함수를 정의할 수 있습니다:
 
 ```c++
 namespace std {
@@ -309,23 +232,16 @@ namespace std {
 }
 ```
 
-This code should be placed outside the `Vertex` struct. The hash functions for
-the GLM types need to be included using the following header:
+이 코드는 `Vertex` 구조체 밖에 정의되어야 합니다. GLM 타입에 대한 해시 함수는 아래와 같은 헤더를 include해야 사용할 수 있습니다:
 
 ```c++
 #define GLM_ENABLE_EXPERIMENTAL
 #include <glm/gtx/hash.hpp>
 ```
 
-The hash functions are defined in the `gtx` folder, which means that it is
-technically still an experimental extension to GLM. Therefore you need to define
-`GLM_ENABLE_EXPERIMENTAL` to use it. It means that the API could change with a
-new version of GLM in the future, but in practice the API is very stable.
+해시 함수는 `gtx` 폴더에 정의되어 있는데 이는 이 기능이 사실은 GLM의 실험적 확장 기능이라는 의미입니다. 따라서 사용하기 위해서는 `GLM_ENABLE_EXPERIMENTAL`가 정의되어 있어야 합니다. 또한 이 기능은 나중에 GLM 버전이 바뀌면 API가 바뀔 수 있다는 뜻이기도 하지만 실제로는 API는 상당히 안정적입니다.(*역주: 바뀔 가능성이 적다는 의미*)
 
-You should now be able to successfully compile and run your program. If you
-check the size of `vertices`, then you'll see that it has shrunk down from
-1,500,000 to 265,645! That means that each vertex is reused in an average number
-of ~6 triangles. This definitely saves us a lot of GPU memory.
+이제 컴파일 오류 없이 프로그램을 실행할 수 있습니다. `vertices`의 크기를 확인해 보면 1,500,000에서 265,645로 줄어든 것을 확인할 수 있습니다! 즉 각 정점이 평균적으로 대략 여섯 개의 삼각형에서 재사용되고 있다는 뜻입니다. 이를 통해 GPU 메모리가 상당히 절약되었을 것입니다.
 
 [C++ code](/code/28_model_loading.cpp) /
 [Vertex shader](/code/27_shader_depth.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index 9b50dffe..e6c6898d 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -106,4 +106,7 @@
 - 보간: Interpolation
 - 비등방성: Anisotropic
 - 어드레싱 모드: Addressing mode
-- 하이트맵: Heightmap
\ No newline at end of file
+- 하이트맵: Heightmap
+- 표면: Face
+- 라이팅: Lighting
+- 머티리얼: Material
\ No newline at end of file

From 69c5920c5655714bd17002500bb6629948db861e Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Tue, 5 Mar 2024 14:04:22 +0900
Subject: [PATCH 43/47] kr translate 09 generating mipmaps

---
 kr/09_Generating_Mipmaps.md | 100 ++++++++++++++++++------------------
 kr/kr_glossary.md           |   5 +-
 2 files changed, 53 insertions(+), 52 deletions(-)

diff --git a/kr/09_Generating_Mipmaps.md b/kr/09_Generating_Mipmaps.md
index e4033136..894b9d4a 100644
--- a/kr/09_Generating_Mipmaps.md
+++ b/kr/09_Generating_Mipmaps.md
@@ -1,15 +1,16 @@
-## Introduction
-Our program can now load and render 3D models. In this chapter, we will add one more feature, mipmap generation. Mipmaps are widely used in games and rendering software, and Vulkan gives us complete control over how they are created.
+## 서론
 
-Mipmaps are precalculated, downscaled versions of an image. Each new image is half the width and height of the previous one.  Mipmaps are used as a form of *Level of Detail* or *LOD.* Objects that are far away from the camera will sample their textures from the smaller mip images. Using smaller images increases the rendering speed and avoids artifacts such as [Moiré patterns](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern). An example of what mipmaps look like:
+이제 우리 프로그램에서 3D 모델을 로딩하고 렌더링할 수 있게 되었습니다. 이 챕터에서는 기능을 하나 더 추가할 것인데, 밉맵 생성입니다. 밉맵은 게임이나 렌더링 소프트웨어에서 널리 사용되는 방법이고, Vulkan에서는 밉맵이 어떻게 생성될 것인지에 대한 완전한 제어 권한을 제공해 줍니다.
+
+밉맵은 미리 계산된, 축소된 이미지입니다. 각 이미지는 전 단계의 이미지보다 가로와 세로가 절반 크기로 줄어든 이미지 입니다. 밉맵은 *디테일 레벨(Level of Detail)*, 다시말해 *LOD*으로써 사용됩니다. 카메라에서 멀리 떨어진 물체는 텍스처를 샘플링 할 때 더 작은 밉 이미지로부터 샘플링합니다. 더 작은 이미지를 사용하면 렌더링 속도가 빨라지며 [Moiré 패턴](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern)과 같은 문제점을 해결할 수 있습니다. 밉맵의 예시는 아래와 같습니다:
 
 ![](/images/mipmaps_example.jpg)
 
-## Image creation
+## 이미지 생성
 
-In Vulkan, each of the mip images is stored in different *mip levels* of a `VkImage`. Mip level 0 is the original image, and the mip levels after level 0 are commonly referred to as the *mip chain.*
+Vulkan에서 각 밉 이미지는 `VkImage`의 서로 다른 *밉 레벨*에 저장됩니다. 밉 레벨 0은 원본 이미지이고 0 레벨 이후의 밉 레벨들은 일반적으로 *밉 체인(chain)*이라고 부릅니다.
 
-The number of mip levels is specified when the `VkImage` is created. Up until now, we have always set this value to one. We need to calculate the number of mip levels from the dimensions of the image. First, add a class member to store this number:
+밉 레벨의 개수는 `VkImage`이 생성될 때 명시됩니다. 지금까지는 항상 이 값을 1로 설정했었습니다. 이제는 이미지의 크기로부터 밉 레벨의 개수를 계산해야 합니다. 먼저 이 숫자를 저장하기 위한 클래스 멤버를 추가합니다:
 
 ```c++
 ...
@@ -18,7 +19,7 @@ VkImage textureImage;
 ...
 ```
 
-The value for `mipLevels` can be found once we've loaded the texture in `createTextureImage`:
+`mipLevels` 값은 `createTextureImage`에서 텍스처를 로딩한 뒤 저장됩니다:
 
 ```c++
 int texWidth, texHeight, texChannels;
@@ -28,9 +29,9 @@ mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHei
 
 ```
 
-This calculates the number of levels in the mip chain. The `max` function selects the largest dimension. The `log2` function calculates how many times that dimension can be divided by 2. The `floor` function handles cases where the largest dimension is not a power of 2.  `1` is added so that the original image has a mip level.
+위와 같이 밉 체인의 레벨 개수를 계산합니다. `max` 함수는 가로 세로 중 큰 값을 찾습니다. `log2` 함수를 통해 그 값이 2로 몇번 나눌 수 있는지를 계산합니다. `floor` 함수를 통해 값이 2의 배수가 아닐 경우를 처리합니다. 원본 이미지가 밉 레벨 하나를 차지하기 때문에 `1`을 더합니다.
 
-To use this value, we need to change the `createImage`, `createImageView`, and `transitionImageLayout` functions to allow us to specify the number of mip levels. Add a `mipLevels` parameter to the functions:
+이 값을 사용하기 위해서는 `createImage`, `createImageView`, `transitionImageLayout` 함수를 수정해서 밉 레벨을 명시해야 합니다. 해당 함수들에 `mipLevels` 매개변수를 추가합니다:
 
 ```c++
 void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
@@ -52,7 +53,7 @@ void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayo
     ...
 ```
 
-Update all calls to these functions to use the right values:
+이 함수들을 호출하는 곳에서는 올바른 값을 넘겨주도록 수정합니다:
 
 ```c++
 createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
@@ -74,11 +75,11 @@ transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UND
 
 
 
-## Generating Mipmaps
+## 밉맵 생성
 
-Our texture image now has multiple mip levels, but the staging buffer can only be used to fill mip level 0. The other levels are still undefined. To fill these levels we need to generate the data from the single level that we have. We will use the `vkCmdBlitImage` command. This command performs copying, scaling, and filtering operations. We will call this multiple times to *blit* data to each level of our texture image.
+이제 텍스처 이미지가 여러 밉 레벨을 가지고 있지만 스테이징 버퍼는 밉 레벨 0을 채우는 데에만 사용되고 있습니다. 다른 레벨들에 대해서는 아직 정의되지 않았습니다. 이러한 레벨들을 채우기 위해서는 하나의 레벨으로부터 데이터들을 생성해야만 합니다. 이를 위해 `vkCmdBlitImage` 명령을 사용할 것입니다. 이 명령은 복사, 크기 변환과 필터링 연산을 수행합니다. 이를 여러 번 호출해서 데이터를 텍스처 이미지의 여러 레벨으로 *blit* 하도록 할 것입니다.
 
-`vkCmdBlitImage` is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. Add `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` to the texture image's usage flags in `createTextureImage`:
+`vkCmdBlitImage`은 전송 연산으로 취급되기 때문에, Vulkan에게 텍스처 이미지가 전송의 소스와 목적지로 사용된 것임을 알려줘야 합니다. `createTextureImage`에서 텍스처 이미지의 사용법 플래그에 `VK_IMAGE_USAGE_TRANSFER_SRC_BIT`를 추가합니다:
 
 ```c++
 ...
@@ -86,9 +87,9 @@ createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TI
 ...
 ```
 
-Like other image operations, `vkCmdBlitImage` depends on the layout of the image it operates on. We could transition the entire image to `VK_IMAGE_LAYOUT_GENERAL`, but this will most likely be slow. For optimal performance, the source image should be in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination image should be in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Vulkan allows us to transition each mip level of an image independently. Each blit will only deal with two mip levels at a time, so we can transition each level into the optimal layout between blits commands.
+다른 이미지 연산들처럼 `vkCmdBlitImage`도 연산이 수행되는 이미지의 레이아웃에 의존적입니다. 전체 이미지를 `VK_IMAGE_LAYOUT_GENERAL`로 전환할 수도 있지만 이렇게 하면 느려질 겁니다. 최적 성능을 위해서는 소스 이미지는 `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`에, 목적 이미지는 `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`에 있어야 합니다. Vulkan에서는 이미지의 각 레벨을 독립적으로 전환할 수 있도록 되어 있습니다. 각 blit마다 두 개의 밉 레벨을 처리하므로 blit 명령간에 최적 레이아웃으로 전환해 사용하면 됩니다.
 
-`transitionImageLayout` only performs layout transitions on the entire image, so we'll need to write a few more pipeline barrier commands. Remove the existing transition to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` in `createTextureImage`:
+`transitionImageLayout`는 전체 이미지에 대한 레이아웃 전환만 수행하므로 몇 가지 파이프라인 배리어 연산을 작성해야 합니다. `createTextureImage`에 기존에 있던 `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`로의 전환을 삭제합니다:
 
 ```c++
 ...
@@ -98,9 +99,9 @@ transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UND
 ...
 ```
 
-This will leave each level of the texture image in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Each level will be transitioned to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` after the blit command reading from it is finished.
+이렇게 하면 텍스처 이미지의 각 레벨이 `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`가 됩니다. 각 레벨은 blit 명령이 끝난 뒤에 `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`로 전환될 겁니다.
 
-We're now going to write the function that generates the mipmaps:
+이제 밉맵 생성을 위한 코드를 작성할 것입니다:
 
 ```c++
 void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {
@@ -120,7 +121,7 @@ void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_
 }
 ```
 
-We're going to make several transitions, so we'll reuse this `VkImageMemoryBarrier`. The fields set above will remain the same for all barriers. `subresourceRange.miplevel`, `oldLayout`, `newLayout`, `srcAccessMask`, and `dstAccessMask` will be changed for each transition.
+몇 번의 전환을 수행할 것이므로 `VkImageMemoryBarrier`를 재사용할 것입니다. 위에서 설정한 필드는 모든 배리어에 대해 동일하게 사용됩니다. `subresourceRange.miplevel`, `oldLayout`, `newLayout`, `srcAccessMask`, `dstAccessMask`은 각 전환마다 바뀔 예정입니다.
 
 ```c++
 int32_t mipWidth = texWidth;
@@ -131,7 +132,7 @@ for (uint32_t i = 1; i < mipLevels; i++) {
 }
 ```
 
-This loop will record each of the `VkCmdBlitImage` commands. Note that the loop variable starts at 1, not 0.
+위 반복문에서 각각의 `VkCmdBlitImage` 명령을 기록할 것입니다. 반복문의 변수가 0이 아닌 1부터 시작한다는 것에 유의하세요.
 
 ```c++
 barrier.subresourceRange.baseMipLevel = i - 1;
@@ -147,7 +148,7 @@ vkCmdPipelineBarrier(commandBuffer,
     1, &barrier);
 ```
 
-First, we transition level `i - 1` to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. This transition will wait for level `i - 1` to be filled, either from the previous blit command, or from `vkCmdCopyBufferToImage`. The current blit command will wait on this transition.
+먼저 `i - 1` 레벨을 `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`로 전환합니다. 이 전환은 `i - 1` 레벨이 다 채워질 때까지 기다릴 것인데 이는 이전의 blit 명령 또는 `vkCmdCopyBufferToImage`에 의해 이루어집니다. 현재의 blit 명령은 이러한 전환을 대기하게 됩니다.
 
 ```c++
 VkImageBlit blit{};
@@ -165,7 +166,7 @@ blit.dstSubresource.baseArrayLayer = 0;
 blit.dstSubresource.layerCount = 1;
 ```
 
-Next, we specify the regions that will be used in the blit operation. The source mip level is `i - 1` and the destination mip level is `i`. The two elements of the `srcOffsets` array determine the 3D region that data will be blitted from. `dstOffsets` determines the region that data will be blitted to. The X and Y dimensions of the `dstOffsets[1]` are divided by two since each mip level is half the size of the previous level. The Z dimension of `srcOffsets[1]` and `dstOffsets[1]` must be 1, since a 2D image has a depth of 1.
+다음으로 blit 연산에 사용될 영역을 명시합니다. 소스 밉 레벨은 `i - 1`이고 목적 밉 레벨은 `i` 입니다. `srcOffsets` 배열의 두 요소는 데이터가 blit될 소스의 3D 영역을 결정합니다. `dstOffsets`은 데이터가 blit될 목적 영역을 의미합니다. `dstOffsets[1]`의 X와 Y 크기는 2로 나누었는데 각 밉 레벨이 이전 레벨의 절반 크기이기 때문입니다. `srcOffsets[1]`와 `dstOffsets[1]`의 Z 크기는 1이어야 하는데, 2D 이미지는 깊이값이 1이기 때문입니다.
 
 ```c++
 vkCmdBlitImage(commandBuffer,
@@ -175,11 +176,11 @@ vkCmdBlitImage(commandBuffer,
     VK_FILTER_LINEAR);
 ```
 
-Now, we record the blit command. Note that `textureImage` is used for both the `srcImage` and `dstImage` parameter. This is because we're blitting between different levels of the same image. The source mip level was just transitioned to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination level is still in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` from `createTextureImage`.
+이제 blit 명령을 기록합니다. `srcImage`와 `dstImage` 매개변수에 모두 `textureImage` 가 사용된 것을 주목하십시오. 같은 이미지의 다른 레벨로 blit을 하고 있기 때문입니다. 소스 밉 레벨은 `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`로 전환되었고 목적 레벨은 `createTextureImage`에서 정의한대로 `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`인 상태입니다.
 
-Beware if you are using a dedicated transfer queue (as suggested in [Vertex buffers](!en/Vertex_buffers/Staging_buffer)): `vkCmdBlitImage` must be submitted to a queue with graphics capability.
+[정점 버퍼](!kr/Vertex_buffers/Staging_buffer)에서 제시한 직접 만든 전송 큐를 사용하는 경우 주의하셔야 합니다. `vkCmdBlitImage`는 그래픽스 기능의 큐에 제출되어야만 합니다.
 
-The last parameter allows us to specify a `VkFilter` to use in the blit. We have the same filtering options here that we had when making the `VkSampler`. We use the `VK_FILTER_LINEAR` to enable interpolation.
+마지막 매개변수는 blit에 사용할 `VkFilter`를 명시합니다. 여기서는 `VkSampler`를 만들 때 사용한 것과 같은 필터링 옵션을 사용합니다. 보간을 수행하기 위해 `VK_FILTER_LINEAR`를 사용합니다.
 
 ```c++
 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
@@ -194,7 +195,7 @@ vkCmdPipelineBarrier(commandBuffer,
     1, &barrier);
 ```
 
-This barrier transitions mip level `i - 1` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This transition waits on the current blit command to finish. All sampling operations will wait on this transition to finish.
+이 배리어가 밉 레벨 `i - 1`을 `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`로 전환합니다. 이 전환은 현재의 blit 명령이 끝날 때까지 대기합니다. 이 전환이 끝날때까지 모든 샘플링 연산이 대기상태가 될 겁니다.
 
 ```c++
     ...
@@ -203,7 +204,7 @@ This barrier transitions mip level `i - 1` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_
 }
 ```
 
-At the end of the loop, we divide the current mip dimensions by two. We check each dimension before the division to ensure that dimension never becomes 0. This handles cases where the image is not square, since one of the mip dimensions would reach 1 before the other dimension. When this happens, that dimension should remain 1 for all remaining levels.
+반복문의 끝 부분에서 현재의 밉 크기를 2로 나눕니다. 나누기 전에 크기가 0이 되지 않도록 확입니다. 이를 통해 이미지가 정사각형 크기가 아닐 때를 처리할 수 있는데 이 경우 밉의 가로 크기는 1이 되었는데 세로 크기는 그렇지 않은 상태일 수도 있기 때문입니다. 이러한 상황이 생기면 나머지 레벨이 처리될 때까지 가로 크기는 1로 고정됩니다.
 
 ```c++
     barrier.subresourceRange.baseMipLevel = mipLevels - 1;
@@ -222,9 +223,9 @@ At the end of the loop, we divide the current mip dimensions by two. We check ea
 }
 ```
 
-Before we end the command buffer, we insert one more pipeline barrier. This barrier transitions the last mip level from `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This wasn't handled by the loop, since the last mip level is never blitted from.
+명령 버퍼를 끝내기 전에 파이프라인 배리어를 하나 더 추가했습니다. 이 배리어는 마지막 밉 레벨을 `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`에서 `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`로 바꿉니다. 이 경우는 반복문에서 처리될 수 없는데, 마지막 밉 레벨은 blit이 수행되지 않기 때문입니다.
 
-Finally, add the call to `generateMipmaps` in `createTextureImage`:
+끝으로 `createTextureImage`에 `generateMipmaps` 호출을 추가합니다:
 
 ```c++
 transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
@@ -234,13 +235,13 @@ transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UND
 generateMipmaps(textureImage, texWidth, texHeight, mipLevels);
 ```
 
-Our texture image's mipmaps are now completely filled.
+이제 텍스처 이미지의 밉맵들이 모두 채워집니다.
 
-## Linear filtering support
+## 선형 필터링 지원
 
-It is very convenient to use a built-in function like `vkCmdBlitImage` to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. It requires the texture image format we use to support linear filtering, which can be checked with the `vkGetPhysicalDeviceFormatProperties` function. We will add a check to the `generateMipmaps` function for this.
+`vkCmdBlitImage`와 같은 내장 함수를 통해 모든 밉 수준을 생성하는 것이 편리하지만, 안타깝게도 모든 플랫폼에서 지원이 보장된 것은 아닙니다. 우리가 사용하는 텍스처 이미지 포맷이 선형 필터링을 지원해야만 하고, 이는 `vkGetPhysicalDeviceFormatProperties` 함수를 통해 확인할 수 있습니다. `generateMipmaps` 함수에 확인 과정을 추가할 것입니다.
 
-First add an additional parameter that specifies the image format:
+먼저 이미지 포맷을 명시하는 매개변수를 추가합니다:
 
 ```c++
 void createTextureImage() {
@@ -255,7 +256,7 @@ void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int3
 }
 ```
 
-In the `generateMipmaps` function, use `vkGetPhysicalDeviceFormatProperties` to request the properties of the texture image format:
+`generateMipmaps` 함수에서 `vkGetPhysicalDeviceFormatProperties`를 사용해 텍스처 이미지 포맷에 대한 속성을 요청합니다:
 
 ```c++
 void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {
@@ -267,7 +268,7 @@ void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int3
     ...
 ```
 
-The `VkFormatProperties` struct has three fields named `linearTilingFeatures`, `optimalTilingFeatures` and `bufferFeatures` that each describe how the format can be used depending on the way it is used. We create a texture image with the optimal tiling format, so we need to check `optimalTilingFeatures`. Support for the linear filtering feature can be checked with the `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`:
+`VkFormatProperties` 구조체는 `linearTilingFeatures`, `optimalTilingFeatures`, `bufferFeatures` 필드를 갖는데 이들은 사용 방식에 따라서 포맷이 어떻게 사용될 수 있는지를 기술합니다. 우리는 텍스처 이미지를 최적 타일링 포맷으로 만들었기 때문에 `optimalTilingFeatures`를 확인해야 합니다. 선형 필터링 기능에 대한 지원을 확인하는 것은 `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`로 할 수 있습니다:
 
 ```c++
 if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
@@ -275,13 +276,13 @@ if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_F
 }
 ```
 
-There are two alternatives in this case. You could implement a function that searches common texture image formats for one that *does* support linear blitting, or you could implement the mipmap generation in software with a library like [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h). Each mip level can then be loaded into the image in the same way that you loaded the original image.
+처리 방안의 대안은 두 가지가 있습니다. 선형 blit을 *지원하는* 일반적인 이미지 포맷을 찾는 함수를 작성할 수도 있고, 아니면 [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h)와 같은 라이브러리를 사용해 소프트웨어적으로 밉맵 생성을 구현할 수도 있습니다. 원본 이미지를 로딩한 것과 같은 방식으로 각 밉 레벨을 로딩할 수 있습니다.
 
-It should be noted that it is uncommon in practice to generate the mipmap levels at runtime anyway. Usually they are pregenerated and stored in the texture file alongside the base level to improve loading speed. Implementing resizing in software and loading multiple levels from a file is left as an exercise to the reader.
+중요한 것은 어쨌든 실제로는 밉맵 레벨을 런타임에 생성하는 것은 일반적이지 않은 경우라는 것입니다. 로딩 속도 향상을 위해 일반적으로 이들은 미리 생성되어서 텍스처 파일의 기본 레벨 옆에 저장됩니다. 소프트웨어적으로 크기를 변환한 뒤 여러 레벨을 한 파일로 로딩하는 것은 독자들을 위한 연습 문제로 남겨두겠습니다.
 
-## Sampler
+## 샘플러
 
-While the `VkImage` holds the mipmap data, `VkSampler` controls how that data is read while rendering. Vulkan allows us to specify `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode` ("Lod" means "Level of Detail"). When a texture is sampled, the sampler selects a mip level according to the following pseudocode:
+`VkImage`가 밉맵 데이터를 가지고 있으므로 `VkSampler`는 렌더링시에 데이터를 어떻게 읽어올 것인지를 제어할 수 있습니다. Vulkan은 `minLod`, `maxLod`, `mipLodBias`, `mipmapMode`를 명시할 수 있게 되어 있습니다("Lod"가 "디테일 레벨"을 의미합니다). 텍스처가 샘플링될 때, 샘플러는 아래 의사코드와 같은 방식으로 밉 레벨을 선택합니다:
 
 ```c++
 lod = getLodLevelFromScreenSize(); //smaller when the object is close, may be negative
@@ -296,9 +297,9 @@ if (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) {
 }
 ```
 
-If `samplerInfo.mipmapMode` is `VK_SAMPLER_MIPMAP_MODE_NEAREST`, `lod` selects the mip level to sample from. If the mipmap mode is `VK_SAMPLER_MIPMAP_MODE_LINEAR`, `lod` is used to select two mip levels to be sampled. Those levels are sampled and the results are linearly blended.
+`samplerInfo.mipmapMode`가 `VK_SAMPLER_MIPMAP_MODE_NEAREST`면, `lod`가 샘플링할 밉 레벨을 결정합니다. 밉맵 모드가 `VK_SAMPLER_MIPMAP_MODE_LINEAR`면, `lod`는 샘플링할 두 개의 밉 레벨을 결정합니다. 두 레벨에서 모두 샘플링이 되고 이들을 선형적으로 혼합한 결과가 반환됩니다.
 
-The sample operation is also affected by `lod`:
+샘플링 연산 또한 `lod`에 영향을 미칩니다:
 
 ```c++
 if (lod <= 0) {
@@ -308,9 +309,9 @@ if (lod <= 0) {
 }
 ```
 
-If the object is close to the camera, `magFilter` is used as the filter. If the object is further from the camera, `minFilter` is used. Normally, `lod` is non-negative, and is only 0 when close the camera. `mipLodBias` lets us force Vulkan to use lower `lod` and `level` than it would normally use.
+물체가 카메라에 가까이 있으면 `magFilter`가 필터로 사용됩니다. 물체가 카메라에서 멀리 있으면 `minFilter`가 사용됩니다. 일반적으로 `lod`는 양수이고 카메라에 가까이 있을 경우에는 0입니다. `mipLodBias`를 통해 Vulkan에 일반적으로 적용되는 것보다 더 낮은 `lod`와 `level`을 사용하도록 하게 할 수 있습니다.
 
-To see the results of this chapter, we need to choose values for our `textureSampler`. We've already set the `minFilter` and `magFilter` to use `VK_FILTER_LINEAR`. We just need to choose values for `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode`.
+이 챕터의 결과를 보기 위해서 `textureSampler`를 위한 값을 선택해야 합니다. `minFilter`와 `magFilter`에 대해서는 `VK_FILTER_LINEAR`를 이미 설정해 두었습니다. `minLod`, `maxLod`, `mipLodBias`, `mipmapMode`만 선택하면 됩니다.
 
 ```c++
 void createTextureSampler() {
@@ -323,30 +324,29 @@ void createTextureSampler() {
 }
 ```
 
-To allow the full range of mip levels to be used, we set `minLod` to 0.0f, and `maxLod` to `VK_LOD_CLAMP_NONE`. This constant is equal to `1000.0f`, which means that all available mipmap levels in the texture will be sampled. We have no reason to change the `lod` value, so we set `mipLodBias` to 0.0f.
+전체 밉 레벨을 사용하기 위해서 `minLod`는 0.0f로, `maxLod`는 `VK_LOD_CLAMP_NONE`로 설정했습니다. 이 값은 `1000.0f`와 같은데 텍스처의 모든 가능한 밉맵 레벨이 샘플링 가능하다는 뜻입니다. `lod` 값을 바꿀 이유는 없으므로 `mipLodBias`는 0.0f로 설정합니다.
 
-Now run your program and you should see the following:
+이제 프로그램을 실행하면 아래와 같은 결과를 볼 수 있습니다:
 
 ![](/images/mipmaps.png)
 
-It's not a dramatic difference, since our scene is so simple. There are subtle differences if you look closely.
+큰 차이는 없는데, 우리 장면이 아주 간단하기 때문입니다. 자세히 들여다보면 몇 가지 세세한 차이점은 있습니다.
 
 ![](/images/mipmaps_comparison.png)
 
-The most noticeable difference is the writing on the papers. With mipmaps, the writing has been smoothed. Without mipmaps, the writing has harsh edges and gaps from Moiré artifacts.
+가장 큰 차이점은 종이에 쓰여진 것들입니다. 밉맵을 사용하면 좀 더 부드럽게 표시됩니다. 맵밉이 없을 땐 모서리가 두드러지고 Moiré 패턴으로 인한 간격이 보입니다.
 
-You can play around with the sampler settings to see how they affect mipmapping. For example, by changing `minLod`, you can force the sampler to not use the lowest mip levels:
+샘플러 설정을 바꾸어 밉맵핑에 어떤 영향을 주는지 살펴보세요. 예를 들어 `minLod`를 바꾸면 샘플러가 가장 낮은 밉 레벨을 사용하지 않도록 할 수 있습니다:
 
 ```c++
 samplerInfo.minLod = static_cast<float>(mipLevels / 2);
 ```
 
-These settings will produce this image:
-
+위와 같은 설정으로 인해 아래와 같은 결과가 도출됩니다:
 
 ![](/images/highmipmaps.png)
 
-This is how higher mip levels will be used when objects are further away from the camera.
+이것이 물체가 카메라에서부터 멀어졌을 때 높은 레벨의 밉이 적용된 모습니다.
 
 
 [C++ code](/code/29_mipmapping.cpp) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index e6c6898d..564453f0 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -88,7 +88,7 @@
 - 전송: Transfer
 - 사용법: Usage
 - 소스: Source
-- 목적지: Destination
+- 목적(지): Destination
 - 앨리어싱: Aliasing
 - 투영: Projection
 - 컬링: Culling
@@ -109,4 +109,5 @@
 - 하이트맵: Heightmap
 - 표면: Face
 - 라이팅: Lighting
-- 머티리얼: Material
\ No newline at end of file
+- 머티리얼: Material
+- 상세도: Level of Detail
\ No newline at end of file

From a7b55556af43911de04f039960de441a3eaf71d0 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Mon, 11 Mar 2024 11:46:47 +0900
Subject: [PATCH 44/47] kr translate 10 multisampling

---
 kr/10_Multisampling.md | 83 +++++++++++++++++++-----------------------
 kr/kr_glossary.md      |  2 +-
 2 files changed, 39 insertions(+), 46 deletions(-)

diff --git a/kr/10_Multisampling.md b/kr/10_Multisampling.md
index 70b27b51..146757e5 100644
--- a/kr/10_Multisampling.md
+++ b/kr/10_Multisampling.md
@@ -1,25 +1,24 @@
-## Introduction
+## 서론
 
-Our program can now load multiple levels of detail for textures which fixes artifacts when rendering objects far away from the viewer. The image is now a lot smoother, however on closer inspection you will notice jagged saw-like patterns along the edges of drawn geometric shapes. This is especially visible in one of our early programs when we rendered a quad:
+우리 프로그램은 이제 텍스처의 다양한 디테일 레벨을 로딩할 수 있어서 관찰자로부터 멀리 떨어져 있는 물체를 렌더링 할 때 발생하는 문제를 해결할 수 있게 되었습니다. 이제 이미지가 훨씬 부드럽게 보이지만 자세히 살펴보면 물체의 모서리를 따라서 톱날과 같은 들쭉날쭉한 패턴을 볼 수 있을 겁니다. 우리가 초반에 만든 사각형을 렌더링하는 프로그램과 같은 경우에 더 두드러집니다:
 
 ![](/images/texcoord_visualization.png)
 
-This undesired effect is called "aliasing" and it's a result of a limited numbers of pixels that are available for rendering. Since there are no displays out there with unlimited resolution, it will be always visible to some extent. There's a number of ways to fix this and in this chapter we'll focus on one of the more popular ones: [Multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) (MSAA).
+이러한 의도하지 않은 효과는 "앨리어싱(aliasing)" 이라고 하며 렌더링에 사용하는 픽셀 수의 한계로 인해 나타나는 현상입니다. 무한한 해상도를 가진 디스플레이는 존재하지 않으니, 어느 정도는 피할 수 없는 현상이긴 합니다. 이 문제를 해결하는 여러 가지 방법이 있는데 이 챕터에서는 유명한 방법 중 하나인 [멀티샘플 안티앨리어싱(anti-aliasing), MSAA](https://en.wikipedia.org/wiki/Multisample_anti-aliasing)에 집중해 보도록 하겠습니다.
 
-In ordinary rendering, the pixel color is determined based on a single sample point which in most cases is the center of the target pixel on screen. If part of the drawn line passes through a certain pixel but doesn't cover the sample point, that pixel will be left blank, leading to the jagged "staircase" effect.
+일반적인 렌더링 과정에서 픽셀 색상은 하나의 샘플링 포인트로부터 결정되고, 일반적으로 이 포인트는 스크린의 각 픽셀의 중심점입니다. 어떤 선의 일부분이 픽셀을 지나긴 하지만 샘플링 포인트를 지나지는 않는다면, 해당 픽셀은 빈 픽셀이 되고 이로 인해 들쭉날쭉한 "계단" 현상이 발생합니다.
 
 ![](/images/aliasing.png)
 
-What MSAA does is it uses multiple sample points per pixel (hence the name) to determine its final color. As one might expect, more samples lead to better results, however it is also more computationally expensive.
+MSAA가 하는 것은 이름 그대로 하나의 픽셀에 대해 여러 샘플링 포인트를 사용해서 최종 색상을 결정하는 것입니다. 예상할 수 있듯이 더 많은 샘플을 사용하면 더 좋은 결과를 얻을 수 있지만 연산량이 증가하게 됩니다.
 
 ![](/images/antialiasing.png)
 
-In our implementation, we will focus on using the maximum available sample count. Depending on your application this may not always be the best approach and it might be better to use less samples for the sake of higher performance if the final result meets your quality demands.
+우리의 구현에서는 사용 가능한 최대 샘플링 개수에 집중할 것입니다. 응용 프로그램에 따라 이러한 접근법보다는 품질이 만족되는 선에서 더 적은 샘플을 사용하는 것이 성능 면에서 더 나은 선택일 수 있습니다.
 
+## 가용한 샘플 개수 획득
 
-## Getting available sample count
-
-Let's start off by determining how many samples our hardware can use. Most modern GPUs support at least 8 samples but this number is not guaranteed to be the same everywhere. We'll keep track of it by adding a new class member:
+먼저 우리 하드웨어가 얼마나 많은 샘플을 사용할 수 있는지부터 결정해 보겠습니다. 대부분의 현대 GPU들은 최소 8개의 샘플을 지원하지만 이 숫자가 항상 보장되는 것은 아닙니다. 새 플래스 멤버를 추가해서 추적해 보도록 하겠습니다:
 
 ```c++
 ...
@@ -27,7 +26,7 @@ VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
 ...
 ```
 
-By default we'll be using only one sample per pixel which is equivalent to no multisampling, in which case the final image will remain unchanged. The exact maximum number of samples can be extracted from `VkPhysicalDeviceProperties` associated with our selected physical device. We're using a depth buffer, so we have to take into account the sample count for both color and depth. The highest sample count that is supported by both (&) will be the maximum we can support. Add a function that will fetch this information for us:
+기본적으로 픽셀당 하나의 샘플을 사용하는데 이는 멀티샘플링을 적용하지 않는 것과 마찬가지입니다. 정확한 최대 샘플 개수는 선택된 물리적 장치와 관련된 `VkPhysicalDeviceProperties`를 통해 얻을 수 있습니다. 깊이 버퍼를 사용하고 있기 때문에 색상과 깊이에 대한 샘플 개수를 모두 고려할 필요가 있습니다. 두 개가 모두 지원하는 최대 샘플 개수가 최종적으로 사용할 최대 샘플 개수입니다. 이러한 정보를 획득하기 위한 함수를 추가합니다:
 
 ```c++
 VkSampleCountFlagBits getMaxUsableSampleCount() {
@@ -46,7 +45,7 @@ VkSampleCountFlagBits getMaxUsableSampleCount() {
 }
 ```
 
-We will now use this function to set the `msaaSamples` variable during the physical device selection process. For this, we have to slightly modify the `pickPhysicalDevice` function:
+이제 이 함수를 사용해서 물리적 장치 선택 과정에서 `msaaSamples` 변수의 값을 설정할 것입니다. `pickPhysicalDevice` 함수를 조금만 변경하면 됩니다:
 
 ```c++
 void pickPhysicalDevice() {
@@ -62,9 +61,9 @@ void pickPhysicalDevice() {
 }
 ```
 
-## Setting up a render target
+## 렌더 타겟 설정
 
-In MSAA, each pixel is sampled in an offscreen buffer which is then rendered to the screen. This new buffer is slightly different from regular images we've been rendering to - they have to be able to store more than one sample per pixel. Once a multisampled buffer is created, it has to be resolved to the default framebuffer (which stores only a single sample per pixel). This is why we have to create an additional render target and modify our current drawing process. We only need one render target since only one drawing operation is active at a time, just like with the depth buffer. Add the following class members:
+MSAA에서 각 픽셀은 오프스크린 버퍼에서 샘플링되고, 그 이후에 화면에 렌더링됩니다. 이 새로 등장한 버퍼는 지금까지 렌더링을 수행한 일반적인 이미지와는 약간 다릅니다. 각 픽셀에 하나 이상의 샘플을 저장합니다. 멀티샘플 버퍼가 생성되고 난 이후에 기본 프레임버퍼 (픽셀당 하나의 샘플을 저장하는)에 적용(resolve)되어야 합니다. 따라서 추가적인 렌더 타겟을 생성하고 그리기 과정을 수정해야만 합니다. 깊이 버퍼처럼 한 번에 하나의 그리기 연산만 활성화되기 때문에 렌더 타겟은 하나만 있으면 됩니다. 아래 클래스 멤버들을 추가합니다:
 
 ```c++
 ...
@@ -74,7 +73,7 @@ VkImageView colorImageView;
 ...
 ```
 
-This new image will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during the image creation process. Modify the `createImage` function by adding a `numSamples` parameter:
+이 새로운 이미지가 픽셀당 의도한 숫자만큼의 샘플을 저장할 것이므로 그 숫자를 이미지 생성 과정에서 `VkImageCreateInfo`에 넘겨줘야 합니다. `createImage` 함수에 `numSamples` 매개변수를 추가합니다:
 
 ```c++
 void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
@@ -83,7 +82,7 @@ void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCo
     ...
 ```
 
-For now, update all calls to this function using `VK_SAMPLE_COUNT_1_BIT` - we will be replacing this with proper values as we progress with implementation:
+지금은 이 함수를 호출하는 모든 부분에서 `VK_SAMPLE_COUNT_1_BIT`를 사용하도록 수정합니다. 구현을 진행하면서 이 부분들을 적절한 값으로 대체할 것입니다:
 
 ```c++
 createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
@@ -91,7 +90,7 @@ createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_
 createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
 ```
 
-We will now create a multisampled color buffer. Add a `createColorResources` function and note that we're using `msaaSamples` here as a function parameter to `createImage`. We're also using only one mip level, since this is enforced by the Vulkan specification in case of images with more than one sample per pixel. Also, this color buffer doesn't need mipmaps since it's not going to be used as a texture:
+이제 멀티샘플 색상 버퍼를 생성할 차례입니다. `createColorResources` 함수를 추가할 것인데 여기서는 `createImage`의 매개변수로 `msaaSamples`를 사용한 것에 주의하십시오. 또한 밉 레벨은 하나만 사용할 것인데 Vulkan 명세에서 픽셀당 샘플이 하나 이상인 경우에는 반드시 이렇게 하도록 요구하고 있습니다. 또한 어차피 텍스처로 활용할 것이 아니기 때문에 밉맵이 필요하지 않습니다:
 
 ```c++
 void createColorResources() {
@@ -102,7 +101,7 @@ void createColorResources() {
 }
 ```
 
-For consistency, call the function right before `createDepthResources`:
+일관성을 위해 `createDepthResources` 바로 앞에서 이 함수를 호출합니다:
 
 ```c++
 void initVulkan() {
@@ -113,7 +112,7 @@ void initVulkan() {
 }
 ```
 
-Now that we have a multisampled color buffer in place it's time to take care of depth. Modify `createDepthResources` and update the number of samples used by the depth buffer:
+이제 멀티샘플 색상 버퍼가 준비되었으니 깊이 값을 처리할 차례입니다. `createDepthResources`를 수정하여 깊이 버퍼에 사용할 샘플 개수를 적용합니다:
 
 ```c++
 void createDepthResources() {
@@ -123,7 +122,7 @@ void createDepthResources() {
 }
 ```
 
-We have now created a couple of new Vulkan resources, so let's not forget to release them when necessary:
+Vulkan 리소스를 추가적으로 만든 것이니 적절한 시점에 해제하는 것도 잊으면 안됩니다:
 
 ```c++
 void cleanupSwapChain() {
@@ -134,7 +133,7 @@ void cleanupSwapChain() {
 }
 ```
 
-And update the `recreateSwapChain` so that the new color image can be recreated in the correct resolution when the window is resized:
+`recreateSwapChain`를 수정하여 윈도우 크기가 변하면 적절한 해상도로 색상 이미지를 다시 생성하도록 합니다:
 
 ```c++
 void recreateSwapChain() {
@@ -146,11 +145,11 @@ void recreateSwapChain() {
 }
 ```
 
-We made it past the initial MSAA setup, now we need to start using this new resource in our graphics pipeline, framebuffer, render pass and see the results!
+이제 초기 MSAA 설정은 끝났고, 그래픽스 파이프라인, 프레임버퍼, 렌더패스에서 새로 만든 리소스를 사용하도록 하여 결과를 살펴보겠습니다!
 
-## Adding new attachments
+## 새로운 어태치먼트 추가
 
-Let's take care of the render pass first. Modify `createRenderPass` and update color and depth attachment creation info structs:
+먼저 렌더 패스부터 작업합니다. `createRenderPass`의 색상과 깊이 어태치먼트 생성 정보 구조체를 수정합니다:
 
 ```c++
 void createRenderPass() {
@@ -162,7 +161,7 @@ void createRenderPass() {
     ...
 ```
 
-You'll notice that we have changed the finalLayout from `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`. That's because multisampled images cannot be presented directly. We first need to resolve them to a regular image. This requirement does not apply to the depth buffer, since it won't be presented at any point. Therefore we will have to add only one new attachment for color which is a so-called resolve attachment:
+`finalLayout`을 `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`에서 `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`로 수정한 것을 눈치채셨을 겁니다. 그 이유는 멀티샘플링된 이미지가 바로 표시될 수 없기 때문입니다. 이 이미지를 일반적인 이미지에 먼저 적용(resolve)해야만 합니다. 이러한 요구사항이 깊이 버퍼에 대해서는 적용되지 않는데, 어쨌든 화면에 표시되지 않는 버퍼이기 때문입니다. 따라서 색상에 대한  어태치먼트만 추가하면 되고, 이를 적용 어태치먼트라고 하겠습니다:
 
 ```c++
     ...
@@ -178,7 +177,7 @@ You'll notice that we have changed the finalLayout from `VK_IMAGE_LAYOUT_PRESENT
     ...
 ```
 
-The render pass now has to be instructed to resolve multisampled color image into regular attachment. Create a new attachment reference that will point to the color buffer which will serve as the resolve target:
+이제 렌더 패스가 멀티샘플링된 색상 이미지를 일반적인 어태치먼트로 적용하도록 명시해야만 합니다. 적용의 대상이 되는 색상 버퍼를 참조인 어태치먼트를 새로 만듭니다:
 
 ```c++
     ...
@@ -188,7 +187,7 @@ The render pass now has to be instructed to resolve multisampled color image int
     ...
 ```
 
-Set the `pResolveAttachments` subpass struct member to point to the newly created attachment reference. This is enough to let the render pass define a multisample resolve operation which will let us render the image to screen:
+`pResolveAttachments` 서브패스 구조체 멤버가 새로 만든 어태치먼트 참조를 가리키도록 설정합니다. 이렇게 하면 렌더 패스가 멀티샘플 적용 연산을 정의하여 이미지를 화면에 렌더링 할 수 있습니다:
 
 ```
     ...
@@ -196,7 +195,7 @@ Set the `pResolveAttachments` subpass struct member to point to the newly create
     ...
 ```
 
-Since we're reusing the multisampled color image, it's necessary to update the `srcAccessMask` of the `VkSubpassDependency`. This update ensures that any write operations to the color attachment are completed before subsequent ones begin, thus preventing write-after-write hazards that can lead to unstable rendering results:
+멀티샘플링된 색상 이미지를 재사용하고 있으므로 `VkSubpassDependency`의 `srcAccessMask`를 수정해야 합니다. 이러한 수정을 통해 색상 어태치먼트로의 쓰기 연산이 이후의 연산이 시작되기 전에 완료될 수 있어서 쓰기 연산의 중복으로 인해 발생할 수 있는 불안정한 렌더링 문제를 해결할 수 있습니다:
 
 ```c++
     ...
@@ -204,7 +203,7 @@ Since we're reusing the multisampled color image, it's necessary to update the `
     ...
 ```
 
-Now update render pass info struct with the new color attachment:
+이제 렌더 패스 정보 구조체를 새로운 색상 어태치먼트로 갱신합니다:
 
 ```c++
     ...
@@ -212,7 +211,7 @@ Now update render pass info struct with the new color attachment:
     ...
 ```
 
-With the render pass in place, modify `createFramebuffers` and add the new image view to the list:
+렌더 패스가 준비되면 `createFramebuffers`를 수정하여 새로운 이미지 뷰 들을 목록에 추가합니다:
 
 ```c++
 void createFramebuffers() {
@@ -226,7 +225,7 @@ void createFramebuffers() {
 }
 ```
 
-Finally, tell the newly created pipeline to use more than one sample by modifying `createGraphicsPipeline`:
+마지막으로 `createGraphicsPipeline`를 수정해 새로 만들어진 파이프라인에 샘플을 하나 이상 사용하도록 명시합니다: 
 
 ```c++
 void createGraphicsPipeline() {
@@ -236,21 +235,21 @@ void createGraphicsPipeline() {
 }
 ```
 
-Now run your program and you should see the following:
+이제 프로그램을 실행하면 아래와 같은 화면이 보입니다:
 
 ![](/images/multisampling.png)
 
-Just like with mipmapping, the difference may not be apparent straight away. On a closer look you'll notice that the edges are not as jagged anymore and the whole image seems a bit smoother compared to the original.
+밉맵핑처럼 변화가 확 눈에 들어오지는 않습니다. 자세히 살펴보면 모서리가 이제 더이상 들쭉날쭉하지 않고, 전체적으로 그 전의 이미지보다 부드러워 진 것을 확인할 수 있습니다.
 
 ![](/images/multisampling_comparison.png)
 
-The difference is more noticable when looking up close at one of the edges:
+모서리 부분을 확대해 보면 차이점이 좀 더 눈에 띕니다:
 
 ![](/images/multisampling_comparison2.png)
 
-## Quality improvements
+## 품질 향상
 
-There are certain limitations of our current MSAA implementation which may impact the quality of the output image in more detailed scenes. For example, we're currently not solving potential problems caused by shader aliasing, i.e. MSAA only smoothens out the edges of geometry but not the interior filling. This may lead to a situation when you get a smooth polygon rendered on screen but the applied texture will still look aliased if it contains high contrasting colors. One way to approach this problem is to enable [Sample Shading](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap27.html#primsrast-sampleshading) which will improve the image quality even further, though at an additional performance cost:
+현재의 MSAA 구현은 좀 더 복잡한 장면의 경우에 대해서는 품질에 문제가 발생할 수 있습니다. 에를 들어, 셰이더 앨리어싱으로 인해 발생될 수 있는 잠재적인 문제는 해결하고 있지 않습니다. 즉, MSAA는 모서리를 부드럽게만 할 뿐, 내부에 채워진 값에 대해서는 그렇지 못합니다. 이로 인해 예를 들어 폴리곤 자체는 부드럽게 표현되지만 적용된 색상에 대해서는 색상 대조가 큰 경우 앨리어싱이 발생하게 됩니다. 이 문제에 대한 접근법 중 하나로 [샘플 세이딩](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap27.html#primsrast-sampleshading)을 활성화하는 것이 있는데, 이렇게 하면 이미지 품질을 더 높일 수 있지만 더 높은 계산 비용이 발생합니다:
 
 ```c++
 
@@ -268,16 +267,13 @@ void createGraphicsPipeline() {
 }
 ```
 
-In this example we'll leave sample shading disabled but in certain scenarios the quality improvement may be noticeable:
+이 예시에서는 샘플 세이딩을 비활성화 한 채로 놔둘 것이지만 경우에 따라 이러한 품질의 차이가 크게 눈에 뜨일수도 있습니다:
 
 ![](/images/sample_shading.png)
 
-## Conclusion
+## 결론
 
-It has taken a lot of work to get to this point, but now you finally have a good
-base for a Vulkan program. The knowledge of the basic principles of Vulkan that
-you now possess should be sufficient to start exploring more of the features,
-like:
+여기에 오기까지 힘드셨을 것이지만, 이제 Vulkan 프로그램에 대한 기본 지식을 갖게 되셨을 겁니다. 여러분이 갖게 된 Vulkan의 기본 원리에 대한 지식은, 더 다양한 기능들을 살펴보기 위한 배경으로 충분할 것입니다:
 
 * Push constants
 * Instanced rendering
@@ -288,10 +284,7 @@ like:
 * Multiple subpasses
 * Compute shaders
 
-The current program can be extended in many ways, like adding Blinn-Phong
-lighting, post-processing effects and shadow mapping. You should be able to
-learn how these effects work from tutorials for other APIs, because despite
-Vulkan's explicitness, many concepts still work the same.
+현재 만들어진 프로그램은 다양한 방식으로 확장될 수 있는데, Blinn-Phong 라이팅을 추가한다거나, 후처리 효과를 더한다거나, 그림자 맵핑을 수행하는 등이 있을 겁니다. 다른 API의 튜토리얼을 통해 이러한 효과들이 작동하는 방식을 배우실 수 있을 겁니다. Vulkan은 명시성이라는 특징이 있긴 하지만 대부분의 컨셉은 비슷하기 때문입니다.
 
 [C++ code](/code/30_multisampling.cpp) /
 [Vertex shader](/code/27_shader_depth.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index 564453f0..7cfdf0fb 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -29,7 +29,7 @@
 - 할당/할당자: Allocate/Allocator
 - 검증 레이어: Validation Layer
 - 윈도우즈(OS): Windows
-- 오프 스크린: Off-screen
+- 오프스크린: Off-screen
 - 질의: Query
 - 프레임버퍼: Framebuffer
 - 주사율: Refresh Rate

From e07abd6447adaf2fa2905f3b1257e78f45d4c6a3 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 20 Mar 2024 12:48:11 +0900
Subject: [PATCH 45/47] kr translate 11 compute shader

---
 kr/11_Compute_Shader.md | 213 ++++++++++++++++++++--------------------
 kr/kr_glossary.md       |   4 +-
 2 files changed, 111 insertions(+), 106 deletions(-)

diff --git a/kr/11_Compute_Shader.md b/kr/11_Compute_Shader.md
index 628f58e9..aedd6459 100644
--- a/kr/11_Compute_Shader.md
+++ b/kr/11_Compute_Shader.md
@@ -1,54 +1,54 @@
-## Introduction
+## 서론
 
-In this bonus chapter we'll take a look at compute shaders. Up until now all previous chapters dealt with the traditional graphics part of the Vulkan pipeline. But unlike older APIs like OpenGL, compute shader support in Vulkan is mandatory. This means that you can use compute shaders on every Vulkan implementation available, no matter if it's a high-end desktop GPU or a low-powered embedded device.
+이 추가 챕터에서는 컴퓨트 셰이더에 대해 알아보겠습니다. 이전 챕터에서는 Vulkan 파이프라인의 전통적인 그래픽 부분에 대해 다뤘습니다. 그러나 OpenGL과 같은 이전 API와 달리 Vulkan에서는 컴퓨트 셰이더 지원이 필수 사항이 되었습니다. 이는 고성능 데스크톱 GPU이든 저전력 임베디드 장치이든 상관없이 모든 Vulkan 구현에서 컴퓨트 셰이더를 사용할 수 있다는 것을 의미합니다.
 
-This opens up the world of general purpose computing on graphics processor units (GPGPU), no matter where your application is running. GPGPU means that you can do general computations on your GPU, something that has traditionally been a domain of CPUs. But with GPUs having become more and more powerful and more flexible, many workloads that would require the general purpose capabilities of a CPU can now be done on the GPU in realtime.
+이로써 응용 프로그램이 실행되는 장치의 종류에 관계없이 일반 목적으로의 GPU 컴퓨팅(GPGPU)을 수행할 수 있게 되었습니다. GPU에서 일반 계산을 수행할 수 있다는 것은 과거에는 CPU의 영역이었던 많은 작업을 GPU에서 실시간으로 수행할 수 있다는 것을 의미합니다. GPU가 점점 더 강력하고 유연해지면서, CPU의 일반 목적 기능이 필요한 많은 작업이 GPU에서 실시간으로 수행될 수 있게 되었습니다.
 
-A few examples of where the compute capabilities of a GPU can be used are image manipulation, visibility testing, post processing, advanced lighting calculations, animations, physics (e.g. for a particle system) and much more. And it's even possible to use compute for non-visual computational only work that does not require any graphics output, e.g. number crunching or AI related things. This is called "headless compute".
+GPU의 컴퓨트 기능을 사용할 수 있는 몇 가지 예는 이미지 조작, 가시성 테스트, 후처리, 고급 조명 계산, 애니메이션, 물리(예를 들어 파티클 시스템) 등이 있습니다. 게다가 컴퓨트를 사용하여 그래픽 출력이 필요하지 않은 비시각적인 계산 작업, 예를 들어 숫자 계산이나 AI 관련 작업도 가능합니다. 이를 "헤드리스 컴퓨트"라고 합니다.
 
-## Advantages
+## 장점
 
-Doing computationally expensive calculations on the GPU has several advantages. The most obvious one is offloading work from the CPU. Another one is not requiring moving data between the CPU's main memory and the GPU's memory. All of the data can stay on the GPU without having to wait for slow transfers from main memory.
+GPU에서 컴퓨팅 비용이 높은 연산을 하는 것에는 몇 가지 장점이 있습니다. 가장 명확한 것은 CPU에서의 연산 비용을 줄일 수 있다는 것입니다. 또 다른 장점은 CPU의 메인 메모리에서 GPU의 메모리로 데이터를 옮길 필요가 없다는 것입니다. 모든 데이터는 GPU 내에 상주할 수 있으므로 메인 메모리로부터의 느린 전송을 기다릴 필요가 없습니다.
 
-Aside from these, GPUs are heavily parallelized with some of them having tens of thousands of small compute units. This often makes them a better fit for highly parallel workflows than a CPU with a few large compute units.
+이외에도 GPU는 몇 만 개의 작은 연산 유닛을 가진 병렬화된 연산 장치입니다. 이로 인해 몇 개의 큰 연산 유닛을 가진 CPU보다 병렬화된 연산에 더 적합합니다.
 
-## The Vulkan pipeline
+## Vulkan 파이프라인
 
-It's important to know that compute is completely separated from the graphics part of the pipeline. This is visible in the following block diagram of the Vulkan pipeline from the official specification:
+파이프라인의 그래픽스 부분과 컴퓨트 부분이 완전히 분리되어 있다는 사실을 이해하고 있는 것이 중요합니다. 이는 Vulkan 공식 명세에서 가져온 아래 Vulkan 파이프라인에 대한 블록 다이어그램을 통해서도 확인할 수 있습니다:
 
 ![](/images/vulkan_pipeline_block_diagram.png)
 
-In this diagram we can see the traditional graphics part of the pipeline on the left, and several stages on the right that are not part of this graphics pipeline, including the compute shader (stage). With the compute shader stage being detached from the graphics pipeline we'll be able to use it anywhere we see fit. This is very different from e.g. the fragment shader which is always applied to the transformed output of the vertex shader.
+이 다이어그램에서 파이프라인의 일반적인 그래픽스 부분은 왼쪽에 표시되어 있고, 이러한 그래픽스 파트가 아닌 부분은 오른쪽에 표시되어 있는데 컴퓨트 셰이더(스테이지)도 오른쪽에 표시되어 있습니다. 컴퓨트 셰이더 스테이지가 그래픽스 파이프라인과 분리되어 있으므로 언제든 필요할 때 사용할 수 있습니다. 이는 예를 들어 프래그먼트 셰이더가 항상 정점 셰이더의 변환된 출력값을 활용해야 하는 것과는 아주 다르다고 볼 수 있습니다.
 
-The center of the diagram also shows that e.g. descriptor sets are also used by compute, so everything we learned about descriptors layouts, descriptor sets and descriptors also applies here.
+다이어그램의 중간에는 컴퓨트에도 사용되는 기술자 집합 등이 표시되어 있습니다. 따라서 우리가 배웠던 기술자 레이아웃, 기술자 집합 등이 여기에서도 활용될 것입니다.
 
-## An example
+## 예시
 
-An easy to understand example that we will implement in this chapter is a GPU based particle system. Such systems are used in many games and often consist of thousands of particles that need to be updated at interactive frame rates. Rendering such a system requires 2 main components: vertices, passed as vertex buffers, and a way to update them based on some equation.
+이해하기 쉬운 예시로 이 챕터에서는 GPU 기반의 파티클(particle) 시스템을 구현해 볼 것입니다. 이러한 시스템은 여러 게임에서 활용되며 몇 천개의 파티클들을 실시간에 갱신해야 합니다. 이러한 시스템을 렌더링 하기 위해서는 두 가지 주요 구성요소가 필요합니다. 정점 버퍼에서 전달된 정점들과, 이들을 수식(equation)에 기반하여 갱신하는 방법입니다.
 
-The "classical" CPU based particle system would store particle data in the system's main memory and then use the CPU to update them. After the update, the vertices need to be transferred to the GPU's memory again so it can display the updated particles in the next frame. The most straight-forward way would be recreating the vertex buffer with the new data for each frame. This is obviously very costly. Depending on your implementation, there are other options like mapping GPU memory so it can be written by the CPU (called "resizable BAR" on desktop systems, or unified memory on integrated GPUs) or just using a host local buffer (which would be the slowest method due to PCI-E bandwidth). But no matter what buffer update method you choose, you always require a "round-trip" to the CPU to update the particles.
+"전통적인" CPU 기반의 파티클 시스템은 파티클 데이터를 시스템의 메인 메모리에 저장해 두고 CPU를 사용해 갱신하였습니다. 갱신이 끝나면 GPU의 메모리로 정점들이 다시 전달되어 다음 프레임에 갱신된 파티클의 위치가 표시될 수 있도록 해야만 했습니다. 가장 간단한 방법으로는 각 프레임마다 새로운 데이터로 정점 버퍼를 다시 만드는 방법이 있습니다. 물론 아주 높은 비용이 발생하죠. 구현에 따라 GPU 메모리를 맵핑하여 CPU로부터 값을 쓸 수 있게 한다거나 (데스크톱 시스템에서는 "resizable BAR", 내장 GPU에서는 통합 메모리라고 불립니다) 아니면 그냥 호스트의 지역 버퍼 (PCI-E 대역폭 문제로 아주 느립니다)를 사용하는 방법이 있습니다. 어떤 방법을 선택하든 CPU에서 갱신된 파티클이 "왕복(round-trip)"해야 한다는 요구사항이 생깁니다.
 
-With a GPU based particle system, this round-trip is no longer required. Vertices are only uploaded to the GPU at the start and all updates are done in the GPU's memory using compute shaders. One of the main reasons why this is faster is the much higher bandwidth between the GPU and it's local memory. In a CPU based scenario, you'd be limited by main memory and PCI-express bandwidth, which is often just a fraction of the GPU's memory bandwidth.
+GPU 기반의 파티클 시스템에서는 이러한 왕복이 필요하지 않습니다. 정점은 처음에 GPU로 업로드되기만 하고, 이후의 모든 갱신은 컴퓨트 셰이더를 사용해 GPU의 메모리에서 이루어집니다. 이 방법이 빠른 가장 주요한 이유는 GPU와 GPU의 지역 메모리 사이의 대역폭이 훨씬 크기 때문입니다. CPU 기반의 시나리오에서는 메인 메모리와 PCI-express 대역폭으로 인해 속도가 제한되는데 이는 GPU의 메모리 대역폭에 비해 훨씬 작습니다.
 
-When doing this on a GPU with a dedicated compute queue, you can update particles in parallel to the rendering part of the graphics pipeline. This is called "async compute", and is an advanced topic not covered in this tutorial.
+이러한 작업이 GPU의 컴퓨트 큐에서 이루어진다면 그래픽스 파이프라인의 렌더링 부분과 파티클의 갱신을 병렬적으로 수행할 수 있습니다. 이를 "비동기 컴퓨트"라 하고, 이 튜토리얼에서는 다루지 않는 고급 주제입니다.
 
-Here is a screenshot from this chapter's code. The particles shown here are updated by a compute shader directly on the GPU, without any CPU interaction:
+아래는 이 챕터 코드의 실행 예시입니다. 이 파티클들은 GPU의 컴퓨트 셰이더에서 직접 갱신되며, CPU와의 상호작용은 없습니다:
 
 ![](/images/compute_shader_particles.png)
 
-## Data manipulation
+## 데이터 조작(manipulation)
 
-In this tutorial we already learned about different buffer types like vertex and index buffers for passing primitives and uniform buffers for passing data to a shader. And we also used images to do texture mapping. But up until now, we always wrote data using the CPU and only did reads on the GPU.
+이 튜토리얼을 통해 이미 정점 버퍼, 인덱스 버퍼를 통해 프리미티브 데이터를 전달하는 방법과 유니폼 버퍼를 통해 셰이더에 데이터를 전달하는 법 등을 배웠습니다. 또 이미지를 사용해 텍스처 맵핑을 하는 법도요. 하지만 지금까지 우리는 항상 CPU에서 데이터를 쓰고, GPU에서 그 데이터를 읽기만 했습니다.
 
-An important concept introduced with compute shaders is the ability to arbitrarily read from **and write to** buffers. For this, Vulkan offers two dedicated storage types.
+컴퓨트 셰이더에서 소개하는 중요한 개념은 버터의 데이터를 읽고 **쓰는** 기능입니다. 이를 위해 Vulkan은 두 종류의 스토리지를 제공합니다.
 
-### Shader storage buffer objects (SSBO)
+### 셰이더 스토리지 버퍼 객체(Shader storage buffer objects, SSBO)
 
-A shader storage buffer (SSBO) allows shaders to read from and write to a buffer. Using these is similar to using uniform buffer objects. The biggest differences are that you can alias other buffer types to SSBOs and that they can be arbitrarily large.
+셰이더 스토리지 버퍼(SSBO)를 통해 셰이더가 버퍼의 데이터를 읽고 쓸 수 있습니다. 사용 방법은 유니폼 버퍼 객체와 비슷합니다. 가장 큰 차이는 다른 버퍼 타입을 SSBO로 사용할 수 있어서 임의의 크기로 사용할 수 있다는 점입니다.
 
-Going back to the GPU based particle system, you might now wonder how to deal with vertices being updated (written) by the compute shader and read (drawn) by the vertex shader, as both usages would seemingly require different buffer types.
+GPU 기반의 파티클 시스템으로 돌아가서, 정점의 갱신(쓰기)를 어떻게 컴퓨트 셰이더로 수행하고 읽기(그리기)는 정점 셰이더로 수행하는지 의아하실겁니다. 왜냐하면 두 사용법이 서로 다른 버퍼 타입을 요구하는 것 같아 보이기 때문입니다.
 
-But that's not the case. In Vulkan you can specify multiple usages for buffers and images. So for the particle vertex buffer to be used as a vertex buffer (in the graphics pass) and as a storage buffer (in the compute pass), you simply create the buffer with those two usage flags:
+하지만 그렇지 않습니다. Vulkan에서는 버퍼와 이미지에 여러 사용법을 명시할 수 있습니다. 따라서 파티클 정점 버퍼를 (그래픽스 패스에서) 정점 버퍼로 활용하고 (컴퓨트 패스에서) 스토리지 버퍼로도 사용할 수 있습니다. 단지 버퍼를 만들 때 두 개의 사용법 플래그를 명시해주면 됩니다:
 
 ```c++
 VkBufferCreateInfo bufferInfo{};
@@ -60,15 +60,16 @@ if (vkCreateBuffer(device, &bufferInfo, nullptr, &shaderStorageBuffers[i]) != VK
     throw std::runtime_error("failed to create vertex buffer!");
 }
 ```
-The two flags `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` and `VK_BUFFER_USAGE_STORAGE_BUFFER_BIT` set with `bufferInfo.usage` tell the implementation that we want to use this buffer for two different scenarios: as a vertex buffer in the vertex shader and as a store buffer. Note that we also added the `VK_BUFFER_USAGE_TRANSFER_DST_BIT` flag in here so we can transfer data from the host to the GPU. This is crucial as we want the shader storage buffer to stay in GPU memory only (`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`) we need to to transfer data from the host to this buffer.
 
-Here is the same code using using the `createBuffer` helper function:
+`bufferInfo.usage`에 명시한 `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`와 `VK_BUFFER_USAGE_STORAGE_BUFFER_BIT` 두 개의 플래그는 이 버퍼를 두 개의 시나리오에서 사용할 것이라는 뜻입니다. 정점 셰이더에서는 정점 버퍼로, 그리고 그리고 스토리지 버퍼로 말이죠. `VK_BUFFER_USAGE_TRANSFER_DST_BIT` 플래그 또한 추가해서 호스트에서 GPU로 데이터를 전송할 수 있도록도 한 것에 주의하세요. 셰이더 스토리지 버퍼가 GPU 메모리에만 상주해 있길 원하므로(`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`), 호스트에서 이 버퍼로 데이터를 전송해야만 합니다.
+
+`createBuffer` 헬퍼 함수를 통한 구현은 아래와 같습니다:
 
 ```c++
 createBuffer(bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, shaderStorageBuffers[i], shaderStorageBuffersMemory[i]);
 ```
 
-The GLSL shader declaration for accessing such a buffer looks like this:
+이러한 버퍼에 접근하기 위한 GLSL 셰이더에서의 선언은 아래와 같습니다:
 
 ```glsl
 struct Particle {
@@ -86,21 +87,21 @@ layout(std140, binding = 2) buffer ParticleSSBOOut {
 };
 ```
 
-In this example we have a typed SSBO with each particle having a position and velocity value (see the `Particle` struct). The SSBO then contains an unbound number of particles as marked by the `[]`. Not having to specify the number of elements in an SSBO is one of the advantages over e.g. uniform buffers. `std140` is a memory layout qualifier that determines how the member elements of the shader storage buffer are aligned in memory. This gives us certain guarantees, required to map the buffers between the host and the GPU.
+이 예제에서는 타입이 명시된 SSBO를 정의했는데 각 파티클은 위치와 속도(`Particle` 구조체 참고)를 가지고 있습니다. SSBO는 `[]`를 통해 명시되지 않은 개수의 파티클을 가지도록 했습니다. SSBO에 원소의 개수를 명시하지 않아도 되는 것도 예를 들자면 유니폼 버퍼와 비교했을 때의 장점입니다. `std140`는 메모리 레이아웃 한정자로 셰이더 스토리지 버퍼의 원소들이 어떻게 메모리에 정렬되어있는지를 결정합니다. 이를 통해 호스트와 GPU 사이의 버퍼를 맵핑하는 데 필요한 요구사항이 만족되었다고 보장합니다.
 
-Writing to such a storage buffer object in the compute shader is straight-forward and similar to how you'd write to the buffer on the C++ side:
+스토리지 버퍼 객체에 컴퓨트 셰이더를 통해 쓰기를 수행하는 것은 어렵지 않은데, C++ 쪽에서 버퍼에 쓰기를 수행하는 것과 비슷합니다:
 
 ```glsl
 particlesOut[index].position = particlesIn[index].position + particlesIn[index].velocity.xy * ubo.deltaTime;
 ```
 
-### Storage images
+### 스토리지 이미지
 
-*Note that we won't be doing image manipulation in this chapter. This paragraph is here to make readers aware that compute shaders can also be used for image manipulation.*
+*이 챕터에서 이미지 조작을 수행하지는 않을 것입니다. 이 문단은 컴퓨트 셰이더를 통해 이미지 조작도 가능하다는 것을 독자들에게 알려주기 위함입니다.*
 
-A storage image allows you read from and write to an image. Typical use cases are applying image effects to textures, doing post processing (which in turn is very similar) or generating mip-maps.
+스토리지 이미지는 이미지에 읽고 쓰기를 가능하게 해줍니다. 일반적인 사용법으로는 텍스처에 이미지 이펙트를 적용한다거나, 후처리를 수행한다거나 (둘 다 비슷합니다), 밉맵을 생성하는 것입니다.
 
-This is similar for images:
+이미지에 대해서도 비슷합니다:
 
 ```c++
 VkImageCreateInfo imageInfo {};
@@ -113,31 +114,31 @@ if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
 }
 ```
 
-The two flags `VK_IMAGE_USAGE_SAMPLED_BIT` and `VK_IMAGE_USAGE_STORAGE_BIT` set with `imageInfo.usage` tell the implementation that we want to use this image for two different scenarios: as an image sampled in the fragment shader and as a storage image in the computer shader;
+`imageInfo.usage`에 설정된 `VK_IMAGE_USAGE_SAMPLED_BIT`과 `VK_IMAGE_USAGE_STORAGE_BIT`는 이 이미지를 서로 다른 두 시나리오에 사용할 것을 명시합니다. 프래그먼트에서 샘플링될 이미지와 컴퓨트 셰이더에서의 스토리지 이미지 입니다.
 
-The GLSL shader declaration for storage image looks similar to sampled images used e.g. in the fragment shader:
+GLSL 셰이더에서의 스토리지 이미지 선언은 프래그먼트 셰이더에서의 샘플링된 이미지의 사용과 비슷합니다:
 
 ```glsl
 layout (binding = 0, rgba8) uniform readonly image2D inputImage;
 layout (binding = 1, rgba8) uniform writeonly image2D outputImage;
 ```
 
-A few differences here are additional attributes like `rgba8` for the format of the image, the `readonly` and `writeonly` qualifiers, telling the implementation that we will only read from the input image and write to the output image. And last but not least we need to use the `image2D` type to declare a storage image.
+몇 가지 차이점은 이미지 포맷을 명시하기 위한 `rgba8`과 같은 어트리뷰트와 `readonly`와  `writeonly` 한정자를 통해서 입력 이미지는 읽기만, 출력 이미지는 쓰기만 수행할 것이라는 것을 명시한 점입니다. 또한 스토리지 이미지 선언을 위해 `image2D` 타입을 명시하였습니다.
 
-Reading from and writing to storage images in the compute shader is then done using `imageLoad` and `imageStore`: 
+컴퓨트 셰이더에서 스토리지 이미지를 읽고 쓰는 것은 `imageLoad`와 `imageStore`를 통해 수행됩니다:
 
 ```glsl
 vec3 pixel = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.xy)).rgb;
 imageStore(outputImage, ivec2(gl_GlobalInvocationID.xy), pixel);
 ```
 
-## Compute queue families
+## 컴퓨트 큐 패밀리
 
-In the [physical device and queue families chapter](03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md#page_Queue-families) we already learned about queue families and how to select a graphics queue family. Compute uses the queue family properties flag bit `VK_QUEUE_COMPUTE_BIT`. So if we want to do compute work, we need to get a queue from a queue family that supports compute.
+[물리적 장치와 큐 패밀리 챕터](03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md#page_Queue-families)에서 큐 패밀리가 무엇인지와 그래픽스 큐 패밀리는 선택하는 방법을 배웠습니다. 컴퓨트의 경우 `VK_QUEUE_COMPUTE_BIT` 플래그의 큐 패밀리 속성을 사용합니다. 따라서 컴퓨트 작업을 하려면 컴퓨트를 지원하는 큐 패밀리로부터 큐를 얻어와야 합니다.
 
-Note that Vulkan requires an implementation which supports graphics operations to have at least one queue family that supports both graphics and compute operations, but it's also possible that implementations offer a dedicated compute queue. This dedicated compute queue (that does not have the graphics bit) hints at an asynchronous compute queue. To keep this tutorial beginner friendly though, we'll use a queue that can do both graphics and compute operations. This will also save us from dealing with several advanced synchronization mechanisms.
+Vulkan은 그래픽스와 컴퓨트 연산을 모두 지원하는 큐 패밀리를 적어도 하나 갖는 그래픽스 연산을 지원하는 구현이 필요합니다. 하지만 구현이 전용 컴퓨트 큐를 제공해도 됩니다. 이러한 전용 컴퓨트 큐(그래픽스를 포함하지 않는)는 비동기 컴퓨트 큐임을 암시합니다. 이 튜토리얼에서는 좀 더 쉬운 안내를 위해 그래픽스와 컴퓨트 연산을 모두 지원하는 큐를 사용할 것입니다. 이렇게 하면 추가적인 비동기 메커니즘 또한 필요하지 않습니다.
 
-For our compute sample we need to change the device creation code a bit:
+우리 예제에서는 장치 생성 코드를 일부 수정해야 합니다:
 
 ```c++
 uint32_t queueFamilyCount = 0;
@@ -156,21 +157,21 @@ for (const auto& queueFamily : queueFamilies) {
 }
 ```
 
-The changed queue family index selection code will now try to find a queue family that supports both graphics and compute. 
+수정된 큐 패밀리 인덱스 선택 코드는 이제 그래픽스와 컴퓨트를 모두 지원하는 큐 패밀리를 찾게 됩니다.
 
-We can then get a compute queue from this queue family in `createLogicalDevice`:
+`createLogicalDevice`에서는 이 큐 패밀리로부터 컴퓨트 큐를 얻습니다:
 
 ```c++
 vkGetDeviceQueue(device, indices.graphicsAndComputeFamily.value(), 0, &computeQueue);
 ```
 
-## The compute shader stage
+## 컴퓨트 셰이더 스테이지
 
-In the graphics samples we have used different pipeline stages to load shaders and access descriptors. Compute shaders are accessed in a similar way by using the `VK_SHADER_STAGE_COMPUTE_BIT` pipeline. So loading a compute shader is just the same as loading a vertex shader, but with a different shader stage. We'll talk about this in detail in the next paragraphs. Compute also introduces a new binding point type for descriptors and pipelines named `VK_PIPELINE_BIND_POINT_COMPUTE` that we'll have to use later on.
+그래픽스 예제에서 셰이더를 로드하는 부분과 기술자에 접근하는 별도의 파이프라인 스테이지가 있었습니다. 컴퓨트 셰이더 또한 비슷한 방법으로 `VK_SHADER_STAGE_COMPUTE_BIT` 파이프라인을 통해 접근됩니다. 따라서 컴퓨트 셰이더를 로드하는 것 또한 정점 셰이더를 로드하는 것과 동일하지만, 셰이더 스테이지가 다를 뿐입니다. 다음 부분에서 이 내용에 대해 자세히 알아볼 것입니다. 컴퓨트는 또한 기술자와 파이프라인에 `VK_PIPELINE_BIND_POINT_COMPUTE`라는 새로운 바인딩 포인트를 필요로 하고, 이를 사용할 예정입니다.
 
-## Loading compute shaders
+## 컴퓨트 셰이더 로딩
 
-Loading compute shaders in our application is the same as loading any other other shader. The only real difference is that we'll need to use the `VK_SHADER_STAGE_COMPUTE_BIT` mentioned above.
+우리 프로그램에서 컴퓨트 셰이더를 로딩하는 것은 다른 셰이더 로딩과 다를 바 없습니다. 차이점은 위에서 이야기한대로 `VK_SHADER_STAGE_COMPUTE_BIT`를 사용해야 한다는 것 뿐입니다.
 
 ```c++
 auto computeShaderCode = readFile("shaders/compute.spv");
@@ -185,25 +186,25 @@ computeShaderStageInfo.pName = "main";
 ...
 ```
 
-## Preparing the shader storage buffers
+## 셰이더 스토리지 버퍼 준비
 
-Earlier on we learned that we can use shader storage buffers to pass arbitrary data to compute shaders. For this example we will upload an array of particles to the GPU, so we can manipulate it directly in the GPU's memory.
+이전에 임의의 데이터를 컴퓨트 셰이더에 넘기기 위해 셰이더 스토리지 버퍼를 사용해야 한다는 것을 배웠습니다. 이 예제에서 파티클의 배열을 GPU로 넘겨서 GPU의 메모리에서 직접 이 데이터들을 조작할 수 있도록 할 것입니다.
 
-In the [frames in flight](03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md) chapter we talked about duplicating resources per frame in flight, so we can keep the CPU and the GPU busy. First we declare a vector for the buffer object and the device memory backing it up:
+[여러 프레임의 사용](03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md) 챕터에서 프레임별 리소스를 복제하는 방법을 통해 CPU와 GPU 연산을 동시에 수행할 수 있도록 하였습니다. 먼저 버퍼 객체를 위한 벡터와 이를 지원하는 장치 메모리를 선언합니다:
 
 ```c++
 std::vector<VkBuffer> shaderStorageBuffers;
 std::vector<VkDeviceMemory> shaderStorageBuffersMemory;
 ```
 
-In the `createShaderStorageBuffers` we then resize those vectors to match the max. number of frames in flight:
+`createShaderStorageBuffers`에서 이 벡터의 크기를 최대값에 맞게 설정합니다. 사용하는 프레임의 개수입니다:
 
 ```c++
 shaderStorageBuffers.resize(MAX_FRAMES_IN_FLIGHT);
 shaderStorageBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
 ```
 
-With this setup in place we can start to move the initial particle information to the GPU. We first initialize a vector of particles on the host side:
+이제 초기 파티클 정보를 GPU로 넘겨줄 수 있습니다. 먼저 호스트 쪽에서 파티클의 벡터를 초기화 합니다:
 
 ```c++
     // Initialize particles
@@ -224,7 +225,7 @@ With this setup in place we can start to move the initial particle information t
 
 ```
 
-We then create a [staging buffer](04_Vertex_buffers/02_Staging_buffer.md) in the host's memory to hold the initial particle properties:
+그리고 [스테이징 버퍼](04_Vertex_buffers/02_Staging_buffer.md)를 호스트의 메모리에 만들어 초기 파티클 속성을 저장합니다:
 
 ```c++
     VkDeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT;
@@ -239,7 +240,7 @@ We then create a [staging buffer](04_Vertex_buffers/02_Staging_buffer.md) in the
     vkUnmapMemory(device, stagingBufferMemory);
 ```    
 
-Using this staging buffer as a source we then create the per-frame shader storage buffers and copy the particle properties from the staging buffer to each of these:
+이 스테이징 버퍼를 소스로 해서 프레임별 셰이더 스토리지 버퍼를 만들고 파티클 속성을 각각의 스테이징 버퍼에 복사합니다:
 
 ```c++
     for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -250,9 +251,9 @@ Using this staging buffer as a source we then create the per-frame shader storag
 }
 ```
 
-## Descriptors
+## 기술자
 
-Setting up descriptors for compute is almost identical to graphics. The only difference is that descriptors need to have the `VK_SHADER_STAGE_COMPUTE_BIT` set to make them accessible by the compute stage:
+컴퓨트를 위한 기술자를 설정하는 것은 그래픽스에서와 거의 동일합니다. 유일한 차이점은 기술자가 `VK_SHADER_STAGE_COMPUTE_BIT`가 설정되어 있어서 컴퓨트 스테이지에서 접근 가능해야 한다는 것입니다:
 
 ```c++
 std::array<VkDescriptorSetLayoutBinding, 3> layoutBindings{};
@@ -264,13 +265,13 @@ layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
 ...
 ```
 
-Note that you can combine shader stages here, so if you want the descriptor to be accessible from the vertex and compute stage, e.g. for a uniform buffer with parameters shared across them, you simply set the bits for both stages:
+셰이더 스테이지를 여기에서 결합할 수 있기 때문에 정점과 컴퓨트 스테이지에서 기술자를 접근 가능하게 하고 싶다면 (예를 들자면 유니폼 버퍼가 양 셰이더에서 매개변수를 공유하게 하고 싶다면) 두 스테이지를 모두 설정하면 됩니다:
 
 ```c++
 layoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
 ```
 
-Here is the descriptor setup for our sample. The layout looks like this:
+우리의 예제에 대한 기술자 설정은 이와 같습니다. 레이아웃은 아래와 같습니다:
 
 ```c++
 std::array<VkDescriptorSetLayoutBinding, 3> layoutBindings{};
@@ -302,11 +303,11 @@ if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &computeDescriptor
 }
 ```
 
-Looking at this setup, you might wonder why we have two layout bindings for shader storage buffer objects, even though we'll only render a single particle system. This is because the particle positions are updated frame by frame based on a delta time. This means that each frame needs to know about the last frames' particle positions, so it can update them with a new delta time and write them to it's own SSBO:
+이 설정을 보면 셰이더 스토리지 버퍼 객체에 대해 왜 두 레이아웃 바인딩이 있는지 의문이 드실겁니다. 파티클 시스템은 하나만 렌더링 하는데도요. 그 이유는 파티클의 위치가 프레임별 시간에 따라 갱신될 것이기 때문입니다. 즉 각 프레임에서 이전 프레임에서의 파티클 위치를 알아야 하고, 그 위치들을 프레임간 소요 시간(delta time)을 기반으로 갱신한 뒤 자신 SSBO에 쓰게 됩니다:
 
 ![](/images/compute_ssbo_read_write.svg)
 
-For that, the compute shader needs to have access to the last and current frame's SSBOs. This is done by passing both to the compute shader in our descriptor setup. See the `storageBufferInfoLastFrame` and `storageBufferInfoCurrentFrame`: 
+이렇게 하려면 컴퓨트 셰이더가 이전과 현재 프레임의 SSBO에 접근 가능해야 합니다. 기술자 설정 과정에서 셰이더에 둘 다 넘겨주면 됩니다. `storageBufferInfoLastFrame`과 `storageBufferInfoCurrentFrame`를 살펴 봅시다:
 
 ```c++
 for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
@@ -348,7 +349,7 @@ for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
 }
 ```
 
-Remember that we also have to request the descriptor types for the SSBOs from our descriptor pool:
+기술자 풀에서 SSBO의 기술자 타입을 요청해야 하는 것을 잊지 마세요:
 
 ```c++
 std::array<VkDescriptorPoolSize, 2> poolSizes{};
@@ -358,11 +359,11 @@ poolSizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
 poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT) * 2;
 ```
 
-We need to double the number of `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER` types requested from the pool by two because our sets reference the SSBOs of the last and current frame.
+풀에서 요청한 `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER` 타입의 숫자를 두 배 해서 이전과 현재 프레임의 SSBO의 참조할 수 있도록 합니다.
 
-## Compute pipelines
+## 컴퓨트 파이프라인
 
-As compute is not a part of the graphics pipeline, we can't use `vkCreateGraphicsPipelines`. Instead we need to create a dedicated compute pipeline with `vkCreateComputePipelines` for running our compute commands. Since a compute pipeline does not touch any of the rasterization state, it has a lot less state than a graphics pipeline:
+컴퓨트는 그래픽스 파이프라인에 속하지 않으므로 `vkCreateGraphicsPipelines`를 사용할 수 없습니다. 대신 `vkCreateComputePipelines`를 사용해 적절한 파이프라인을 만들어 컴퓨트 명령을 수행해야 합니다. 컴퓨트 파이프라인은 래스터화 상태와는 상관이 없으므로 그래픽스 파이프라인보다는 상태가 훨씬 적습니다:
 
 ```c++
 VkComputePipelineCreateInfo pipelineInfo{};
@@ -375,7 +376,7 @@ if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr,
 }
 ```
 
-The setup is a lot simpler, as we only require one shader stage and a pipeline layout. The pipeline layout works the same as with the graphics pipeline:
+설정은 훨씬 간단한데, 하나의 셰이더 스테이지와 파이프라인 레이아웃만 있으면 되기 때문입니다. 파이프라인 레이아웃 작업은 그래픽스 파이프라인과 동일합니다:
 
 ```c++
 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
@@ -388,29 +389,29 @@ if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &computePipelin
 }
 ```
 
-## Compute space
+## 컴퓨트 공간(space)
 
-Before we get into how a compute shader works and how we submit compute workloads to the GPU, we need to talk about two important compute concepts: **work groups** and **invocations**. They define an abstract execution model for how compute workloads are processed by the compute hardware of the GPU in three dimensions (x, y, and z).
+컴퓨트 셰이더가 어떻게 동작하고, 어떻게 GPU에 컴퓨트 작업을 제출하는지 알아보기 전에, 두 가지 중요한 개념에 대해 먼저 이야기 해보겠습니다. 이는 **작업 그룹(work groups)**과 **호출(invocations)** 입니다. 이들은 컴퓨트 작업이 GPU의 컴퓨트 하드웨어에서 어떻게 처리되는지를 3차원(x,y,z)으로 추상화한 실행 모델을 정의합니다.
 
-**Work groups** define how the compute workloads are formed and processed by the the compute hardware of the GPU. You can think of them as work items the GPU has to work through. Work group dimensions are set by the application at command buffer time using a dispatch command.
+**작업 그룹**은 컴퓨트 작업이 어떻게 구성되고 GPU의 컴퓨트 하드웨어에서 처리되는지를 정의합니다. 이를 GPU가 작업해야 하는 작업 아이템이라고 생각할 수 있습니다. 작업 그룹의 차원은 응용 프로그램의 명령 버퍼 시점에서 디스패치(dispatch) 명령에 의해 설정됩니다.
 
-And each work group then is a collection of **invocations** that execute the same compute shader. Invocations can potentially run in parallel and their dimensions are set in the compute shader. Invocations within a single workgroup have access to shared memory.
+각 작업 그룹은 동일한 컴퓨트 셰이더를 실행하는 **호출**들의 집합입니다. 호출은 잠재적으로 병렬 실행되며 그 차원은 컴퓨트 셰이더에 설정합니다. 동일한 작업 그룹에 속한 호출들은 공유 메모리에 접근할 수 있습니다.
 
-This image shows the relation between these two in three dimensions:
+아래 그림은 3차원으로 이 두 가지 개념의 관계를 보여줍니다:
 
 ![](/images/compute_space.svg)
 
-The number of dimensions for work groups (defined by `vkCmdDispatch`) and invocations depends (defined by the local sizes in the compute shader) on how input data is structured. If you e.g. work on a one-dimensional array, like we do in this chapter, you only have to specify the x dimension for both.
+(`vkCmdDispatch`에 의해 정의되는) 작업 그룹의 차원과 (컴퓨트 셰이더에서 로컬 크기로 정의되는) 호출의 차원은 입력 데이터가 어떻게 구성되어 있는지에 의존적입니다. 예를 들어 여러분이 1차원 배열에 대해 작업을 수행한다면 (이 챕터의 예제처럼), 두 가지 모두 x 차원만 명시하면 됩니다. 
 
-As an example: If we dispatch a work group count of [64, 1, 1] with a compute shader local size of [32, 32, ,1], our compute shader will be invoked 64 x 32 x 32 = 65,536 times.
+예를 들어: 작업 그룹 [64, 1, 1]개를 컴퓨트 셰이더의 로컬 크기 [32, 32, 1]로 디스패치 하면 컴퓨트 셰이더는 총 64 x 32 x 32 = 65,536 번 호출됩니다.
 
-Note that the maximum count for work groups and local sizes differs from implementation to implementation, so you should always check the compute related `maxComputeWorkGroupCount`, `maxComputeWorkGroupInvocations` and `maxComputeWorkGroupSize` limits in `VkPhysicalDeviceLimits`.
+작업 그룹과 로컬 크기는 구현마다 다르므로 `VkPhysicalDeviceLimits`에 정의된 `maxComputeWorkGroupCount`, `maxComputeWorkGroupInvocations`, `maxComputeWorkGroupSize`를 항상 확인해야 합니다.
 
-## Compute shaders
+## 컴퓨트 셰이더
 
-Now that we have learned about all the parts required to setup a compute shader pipeline, it's time to take a look at compute shaders. All of the things we learned about using GLSL shaders e.g. for vertex and fragment shaders also applies to compute shaders. The syntax is the same, and many concepts like passing data between the application and the shader are the same. But there are some important differences.
+이제 컴퓨트 셰이더 파이프라인과 관련한 설정에 대해 모두 배웠으니 이제 컴퓨트 셰이더 자체를 살펴 봅시다. 지금까지 GLSL 셰이더에 대해서 배웠던, 예를 들면 정점 셰이더와 프래그먼트 셰이더에 관한 것들이 컴퓨트 셰이더에도 적용됩니다. 문법 또한 동일하고 응용 프로그램에서 세이더로 데이터를 넘기는 방법도 같습니다. 하지만 중요한 차이도 몇 가지 있습니다.
 
-A very basic compute shader for updating a linear array of particles may look like this:
+일차원 배열로 저장된 파티클을 갱신하는 간단한 컴퓨트 셰이더는 아래와 같습니다:
 
 ```glsl
 #version 450
@@ -447,22 +448,23 @@ void main()
 }
 ```
 
-The top part of the shader contains the declarations for the shader's input. First is a uniform buffer object at binding 0, something we already learned about in this tutorial. Below we declare our Particle structure that matches the declaration in the C++ code. Binding 1 then refers to the shader storage buffer object with the particle data from the last frame (see the descriptor setup), and binding 2 points to the SSBO for the current frame, which is the one we'll be updating with this shader.
+최상단에는 셰이더의 입력을 정의하는 부분이 있습니다. 첫 번째로 0에 바인딩된 유니폼 버퍼 객체가 있는데, 이미 배웠던 내용입니다. 그 아래는 파티클 구조체에 대한 선언이 있고 이는 C++쪽의 선언과 매칭됩니다. 아래 1에 바인딩한 것은 셰이더 스토리지 버퍼 객체로 이전 프레임의 파티클 데이터이고(기술자 설정 부분 참고), 2는 현재 프레임의 SSBO의 바인딩 포인트이며 셰이더에서 갱신할 부분입니다.
 
-An interesting thing is this compute-only declaration related to the compute space:
+흥미로운 부분은 컴퓨트 공간과 관련된, 컴퓨트에서만 활용되는 선언입니다:
 
 ```glsl
 layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
 ```
-This defines the number invocations of this compute shader in the current work group. As noted earlier, this is the local part of the compute space. Hence the `local_` prefix. As we work on a linear 1D array of particles we only need to specify a number for x dimension in `local_size_x`.
 
-The `main` function then reads from the last frame's SSBO and writes the updated particle position to the SSBO for the current frame. Similar to other shader types, compute shaders have their own set of builtin input variables. Built-ins are always prefixed with `gl_`. One such built-in is `gl_GlobalInvocationID`, a variable that uniquely identifies the current compute shader invocation across the current dispatch. We use this to index into our particle array.
+위 코드가 이 컴퓨트 셰이더가 작업 그룹 안에서 호출될 개수를 정의합니다. 전에 이야기한 것처럼 이 부분은 컴퓨트 공간의 로컬 부분입니다. 따라서 `local_` 접두어가 붙습니다. 우리는 파티클에 대한 1차원 배열을 사용하기 때문에 x 차원인 `local_size_x`에만 숫자를 명시해 줍니다.
+
+`main` 함수에서는 이전 프레임의 SSBO를 읽어와 현재 프레임의 SSBO에 파티클의 위치를 갱신해 줍니다. 다른 셰이더 타입과 비슷하게 컴퓨트 셰이더도 내장 입력 변수를 가지고 있습니다. 내장 변수는 항상 `gl_` 접두어를 가지고 있습니다. 내장 변수 중 하나로 `gl_GlobalInvocationID`가 있는데 현재 디스패치에 대한 현재 컴퓨트 셰이더 호출의 ID를 가지고 있는 변수입니다. 우리는 이 값을 파티클 배열의 인덱스로 사용합니다.
 
-## Running compute commands 
+## 컴퓨트 명령 실행
 
-### Dispatch
+### 디스패치
 
-Now it's time to actually tell the GPU to do some compute. This is done by calling `vkCmdDispatch` inside a command buffer. While not perfectly true, a dispatch is for compute as a draw call like `vkCmdDraw` is for graphics. This dispatches a given number of compute work items in at max. three dimensions.
+이제 실제로 GPU가 컴퓨트 작업을 하도록 할 차례입니다. 이는 명령 버퍼에서 `vkCmdDispatch`를 호출하여 수행합니다. 완전히 같진 않지만, 그래픽스에서 드로우를 수행하기 위해 `vkCmdDraw`를 호출하는 것이 컴퓨트에서는 디스패치와 대응됩니다. 이를 통해 (최대) 3차원으로 주어진 개수만큼 컴퓨트 작업 아이템을 디스패치합니다.
 
 ```c++
 VkCommandBufferBeginInfo beginInfo{};
@@ -486,13 +488,13 @@ if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
 }
 ```
 
-The `vkCmdDispatch` will dispatch `PARTICLE_COUNT / 256` local work groups in the x dimension. As our particles array is linear, we leave the other two dimensions at one, resulting in a one-dimensional dispatch. But why do we divide the number of particles (in our array) by 256? That's because in the previous paragraph we defined that every compute shader in a work group will do 256 invocations. So if we were to have 4096 particles, we would dispatch 16 work groups, with each work group running 256 compute shader invocations. Getting the two numbers right usually takes some tinkering and profiling, depending on your workload and the hardware you're running on. If your particle size would be dynamic and can't always be divided by e.g. 256, you can always use `gl_GlobalInvocationID` at the start of your compute shader and return from it if the global invocation index is greater than the number of your particles.
+`vkCmdDispatch`는 x 차원에 대해 `PARTICLE_COUNT / 256`개의 로컬 작업 그룹을 디스패치합니다. 우리의 파티클 배열은 1차원이므로 나머지 두 차원은 1로 두었고, 이는 1차원 디스패치를 의미합니다. 그런데 왜 (배열 내의) 파티클 개수를 256으로 나누는 것일까요? 이는 이전 장에서 우리가 작업 그룹 내의 각 컴퓨트 셰이더가 256번씩 호출되도록 정의했기 때문입니다. 따라서 4096개의 파티클이 있다면, 16개의 작업 그룹을 디스패치 하는데 각 작업 그룹 내에서는 256번의 컴퓨트 셰이더 호출이 수행되는 것입니다. 이러한 숫자들을 올바로 정의하는 것은 필요한 작업량과 실행되는 하드웨어에 따라 때때로 수정과 확인이 필요합니다. 만일 파티클 개수가 동적으로 변해서 항상 256으로 나누어 떨어지지 않는다면 컴퓨트 셰이더 시작 지점에서 `gl_GlobalInvocationID`를 사용해 파티클 개수보다 큰 글로벌 호출 인덱스를 갖는 경우 바로 반환하도록 할 수 있습니다.
 
-And just as was the case for the compute pipeline, a compute command buffer contains a lot less state than a graphics command buffer. There's no need to start a render pass or set a viewport.
+컴퓨트 파이프라인에서와 마찬가지고 컴퓨트 명령 버퍼도 그래픽스 명령 버퍼보다 훨씬 적인 상태만 가지고 있습니다. 렌더 패스를 시작하거나, 뷰포트를 설정하는 등의 작업은 필요 없습니다.
 
-### Submitting work
+### 작업 제출
 
-As our sample does both compute and graphics operations, we'll be doing two submits to both the graphics and compute queue per frame (see the `drawFrame` function):
+우리 예제는 컴퓨트와 그래픽스 연산을 모두 가지고 있으므로 매 프레임마다 그래픽스와 컴퓨트 큐에 모두 제출을 수행해야 합니다(`drawFrame` 함수 참고):
 
 ```c++
 ...
@@ -505,17 +507,17 @@ if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) !
 }
 ```
 
-The first submit to the compute queue updates the particle positions using the compute shader, and the second submit will then use that updated data to draw the particle system.
+첫 번째의 컴퓨트 큐로의 제출은 컴퓨트 셰이더를 통해 파티클의 위치를 갱신하고, 두 번째의 제출은 갱신된 데이터로 파티클 시스템을 그립니다.
 
-### Synchronizing graphics and compute
+### 그래픽스와 컴퓨트의 동기화
 
-Synchronization is an important part of Vulkan, even more so when doing compute in conjunction with graphics. Wrong or lacking synchronization may result in the vertex stage starting to draw (=read) particles while the compute shader hasn't finished updating (=write) them (read-after-write hazard), or the compute shader could start updating particles that are still in use by the vertex part of the pipeline (write-after-read hazard).
+동기화는 Vulkan에서 중요한 부분 중 하나로, 컴퓨트와 그래픽스를 동시에 사용할 때는 더 중요해집니다. 동기화가 없거나 잘못된 경우 컴퓨트 셰이더가 갱신을 끝내기(=쓰기) 전에 정점 스테이지가 그리기(=읽기)가 시작되거나 정점에서 사용되고 있는 부분의 파티클을 컴퓨트 셰이더가 갱신하기 시작한다거나 하는 문제가 발생할 수 있습니다.
 
-So we must make sure that those cases don't happen by properly synchronizing the graphics and the compute load. There are different ways of doing so, depending on how you submit your compute workload but in our case with two separate submits, we'll be using [semaphores](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores) and [fences](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Fences) to ensure that the vertex shader won't start fetching vertices until the compute shader has finished updating them.
+따라서 이러한 경우가 발생하지 않도록 적절히 그래픽스와 컴퓨트 간에 동기화를 수행해야만 합니다. 수행하는 방법은 컴퓨트 작업을 어떻게 제출했느냐에 따라 여러 방법이 있을 수 있는데 우리의 경우 두 개의 제출이 분리되어 있으므로 [세마포어](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores)와 [펜스](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Fences) 를 사용해 컴퓨트 셰이더가 갱신을 끝내기 전에는 정점 셰이더가 데이터 읽기를 수행하지 않도록 할 것입니다.
 
-This is necessary as even though the two submits are ordered one-after-another, there is no guarantee that they execute on the GPU in this order. Adding in wait and signal semaphores ensures this execution order.
+두 제출 과정에 선후 관계가 있어도 이러한 동기화가 필요한데, GPU에서 이렇게 제출된 순서대로 실행된다는 보장이 없기 때문입니다. 대기 및 시그널 상태 세마포어를 사용하면 실행 순서를 보장할 수 있습니다.
 
-So we first add a new set of synchronization primitives for the compute work in `createSyncObjects`. The compute fences, just like the graphics fences, are created in the signaled state because otherwise, the first draw would time out while waiting for the fences to be signaled as detailed [here](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Waiting-for-the-previous-frame):
+먼저 `createSyncObjects`에서 컴퓨트 작업을 위한 동기화 요소들을 추가할 것입니다. 그래픽스 펜스와 마찬가지로 컴퓨트 펜스는 시그널 상태로 생성될 것인데, 그렇지 않으면 첫 번째의 그리기 호출이 펜스가 시그널 상태가 될때까지 계속 기다리다 타임아웃이 되기 때문입니다. 자세한 내용은 [여기](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Waiting-for-the-previous-frame)를 참고하세요:
 
 ```c++
 std::vector<VkFence> computeInFlightFences;
@@ -539,7 +541,8 @@ for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
     }
 }
 ```
-We then use these to synchronize the compute buffer submission with the graphics submission:
+
+그러고 나서 컴퓨트 버퍼의 제출과 그래픽스 제출을 동기화 하는데 이들을 사용합니다:
 
 ```c++
 // Compute submission
@@ -589,17 +592,17 @@ if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) !
 }
 ```
 
-Similar to the sample in the [semaphores chapter](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores), this setup will immediately run the compute shader as we haven't specified any wait semaphores. This is fine, as we are waiting for the compute command buffer of the current frame to finish execution before the compute submission with the `vkWaitForFences` command.
+[세마포어 챕터](03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md#page_Semaphores)에서의 예제와 비슷하게 이렇게 설정하면 대기 세마포어가 없기 때문에 컴퓨트 셰이더가 곧바로 실행됩니다. 문제는 없는것이 `vkWaitForFences` 커맨드를 통한 컴퓨트의 제출 이전에 현재 프레임의 컴퓨트 커맨드 버퍼의 실행이 끝나는 것을 기다리고 있기 때문입니다.
 
-The graphics submission on the other hand needs to wait for the compute work to finish so it doesn't start fetching vertices while the compute buffer is still updating them. So we wait on the `computeFinishedSemaphores` for the current frame and have the graphics submission wait on the `VK_PIPELINE_STAGE_VERTEX_INPUT_BIT` stage, where vertices are consumed.
+그래픽스 관련 제출은 컴퓨트 작업이 끝나기를 기다려야 하므로 컴퓨트 버퍼가 갱신하는 도중에는 정점을 가져오지 말아야 합니다. 따라서 그래픽스의 제출은 현재 프레임의 `computeFinishedSemaphores`를 기다리면서 정점이 사용되는 `VK_PIPELINE_STAGE_VERTEX_INPUT_BIT` 스테이지에서 대기하도록 해야 합니다.
 
-But it also needs to wait for presentation so the fragment shader won't output to the color attachments until the image has been presented. So we also wait on the `imageAvailableSemaphores` on the current frame at the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage.
+또한 표시도 기다려야 하는데 이미지가 표시되기 전에 프래그먼트 셰이더가 생상 출력을 내지 않도록 하기 위함입니다. 따라서 현재 프레임의 `imageAvailableSemaphores`를 `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` 스테이지에서 기다려야 합니다.
 
-## Drawing the particle system
+## 파티클 시스템 그리기
 
-Earlier on, we learned that buffers in Vulkan can have multiple use-cases and so we created the shader storage buffer that contains our particles with both the shader storage buffer bit and the vertex buffer bit. This means that we can use the shader storage buffer for drawing just as we used "pure" vertex buffers in the previous chapters.
+이전 내용에서 Vulkan에서 버퍼는 여러 사용법을 가질 수 있다는 것을 배웠고, 셰이더 스토리지 버퍼를 만들고 여기에 저장된 파티클들이 셰이더 스토리지 버퍼와 정점 버퍼로 활용될 수 있도록 설정했습니다. 다시말해 셰이더 스토리지 버퍼를 전에 사용했던 "순수한" 정점 버퍼처럼 그리기를 위해 사용할 수 있다는 뜻입니다.
 
-We first setup the vertex input state to match our particle structure:
+먼저 정점 입력 상태를 파티클 구조체와 매칭기켜줍니다:
 
 ```c++
 struct Particle {
@@ -623,9 +626,9 @@ struct Particle {
 };
 ```
 
-Note that we don't add `velocity` to the vertex input attributes, as this is only used by the compute shader.
+정점 입력 어트리뷰트에 `velocity`는 추가하지 않은 것에 유의하세요. 이 값은 컴퓨트 셰이더에서만 사용하기 때문입니다.
 
-We then bind and draw it like we would with any vertex buffer:
+그리고 일반적인 정점 버퍼처럼 바딩인들 하고 그리기를 수행합니다:
 
 ```c++
 vkCmdBindVertexBuffers(commandBuffer, 0, 1, &shaderStorageBuffer[currentFrame], offsets);
@@ -633,16 +636,16 @@ vkCmdBindVertexBuffers(commandBuffer, 0, 1, &shaderStorageBuffer[currentFrame],
 vkCmdDraw(commandBuffer, PARTICLE_COUNT, 1, 0, 0);
 ```
 
-## Conclusion
+## 결론
 
-In this chapter, we learned how to use compute shaders to offload work from the CPU to the GPU. Without compute shaders, many effects in modern games and applications would either not be possible or would run a lot slower. But even more than graphics, compute has a lot of use-cases, and this chapter only gives you a glimpse of what's possible. So now that you know how to use compute shaders, you may want to take look at some advanced compute topics like:
+이 장에서, 우리는 컴퓨트 셰이더를 사용해 CPU의 작업을 GPU로 이전하는 방법을 배웠습니다. 컴퓨트 셰이더가 없었다면 현대 게임 및 응용 프로그램들의 몇몇 효과는 불가능했거나 훨씬 느렸을 것입니다. 그래픽스 용도 이외에도 컴퓨트는 다양한 사용 방법이 존재합니다. 이 챕터는 그 가능성 중 아주 일부분을 보여드렸을 뿐입니다. 이제 컴퓨트 셰이더를 사용하는 방법을 알게 되셨으니 컴퓨트 관련한 고급 토픽들을 알아보고 싶으실겁니다:
 
 - Shared memory
 - [Asynchronous compute](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/performance/async_compute)
 - Atomic operations
 - [Subgroups](https://www.khronos.org/blog/vulkan-subgroup-tutorial)
 
-You can find some advanced compute samples in the [official Khronos Vulkan Samples repository](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/api).
+고급 컴퓨트 기능에 대한 예제는 [공식 Khronos Vulkan Samples 레포지토리](https://github.com/KhronosGroup/Vulkan-Samples/tree/master/samples/api)에서 찾아보실 수 있습니다.
 
 [C++ code](/code/31_compute_shader.cpp) /
 [Vertex shader](/code/31_shader_compute.vert) /
diff --git a/kr/kr_glossary.md b/kr/kr_glossary.md
index 7cfdf0fb..879bd1f1 100644
--- a/kr/kr_glossary.md
+++ b/kr/kr_glossary.md
@@ -110,4 +110,6 @@
 - 표면: Face
 - 라이팅: Lighting
 - 머티리얼: Material
-- 상세도: Level of Detail
\ No newline at end of file
+- 상세도: Level of Detail
+- 헤드리스: Headless
+- 파티클: Particle
\ No newline at end of file

From db544d4c187f22ba8e58fb5bacc37fa8feb0c9e8 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 20 Mar 2024 12:59:02 +0900
Subject: [PATCH 46/47] kr translate 90 faq

---
 kr/90_FAQ.md | 39 +++++++++++++++------------------------
 1 file changed, 15 insertions(+), 24 deletions(-)

diff --git a/kr/90_FAQ.md b/kr/90_FAQ.md
index 3378362a..e8bad5c4 100644
--- a/kr/90_FAQ.md
+++ b/kr/90_FAQ.md
@@ -1,40 +1,31 @@
-This page lists solutions to common problems that you may encounter while
-developing Vulkan applications.
+이 페이지에서는 Vulkan 응용 프로그램 개발 도중 마주치게 되는 흔한 문제들에 대한 해결법을 알려 드립니다.
 
-## I get an access violation error in the core validation layer
+## core 검증 레이어에서 access violation error가 발생해요
 
-Make sure
-that MSI Afterburner / RivaTuner Statistics Server is not running, because it
-has some compatibility problems with Vulkan.
+MSI Afterburner / RivaTuner Statistics Server가 실행되고 있진 않은지 확인하세요. Vulkan과의 호환성 문제가 있습니다.
 
-## I don't see any messages from the validation layers / Validation layers are not available
+## 검증 레이어에서 아무런 메시지가 보이지 않아요 / 검증 레이어를 사용할 수 없어요
 
-First make sure that the validation layers get a chance to print errors by keeping the
-terminal open after your program exits. You can do this from Visual Studio by running
-your program with Ctrl-F5 instead of F5, and on Linux by executing your program from
-a terminal window. If there are still no messages and you are sure that validation
-layers are turned on, then you should ensure that your Vulkan SDK is correctly
-installed by following the "Verify the Installation" instructions [on this page](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html). Also ensure that your SDK version is at least 1.1.106.0 to support the `VK_LAYER_KHRONOS_validation` layer.
+프로그램 종료시에 터미널이 열려있게 해서 검증 레이어가 오류를 출력할 수 있도록 하세요. 비주얼 스튜디오에서는 F5 대신 Ctrl-F5로 실행하면 되고, 리눅스에서는 터미널 윈도우에서 프로그램을 실행하면 됩니다. 여전히 아무 메시지가 나오지 않으면 검증 레이어가 활성화 되었는지 확인하시고, Vulkan SDK가 제대로 설치되었는지를 [이 페이지](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html)의 "설치 확인" 안내에 따라 확인해 보세요. 또한 SDK의 버전이 1.1.106.0 이상이어야 `VK_LAYER_KHRONOS_validation` 레이어가 지원됩니다.
 
-## vkCreateSwapchainKHR triggers an error in SteamOverlayVulkanLayer64.dll
+## SteamOverlayVulkanLayer64.dll에서 vkCreateSwapchainKHR 오류가 발생해요
 
-This appears to be a compatibility problem in the Steam client beta. There are a
-few possible workarounds:
-    * Opt out of the Steam beta program.
-    * Set the `DISABLE_VK_LAYER_VALVE_steam_overlay_1` environment variable to `1`
-    * Delete the Steam overlay Vulkan layer entry in the registry under `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers`
+Steam 클라이언트 베타에 호환성 문제가 있습니다. 해결 방법은 몇 가지가 있습니다:
+    * Steam 베타 프로그램 탈되하기
+    * `DISABLE_VK_LAYER_VALVE_steam_overlay_1` 환경 변수를 `1`로 설정하기
+    * `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` 아래 레지스트리에서 Steam 오버레이 Vulkan 레이어를 삭제하기
 
-Example:
+예시:
 
 ![](/images/steam_layers_env.png)
 
-## vkCreateInstance fails with VK_ERROR_INCOMPATIBLE_DRIVER
+## VK_ERROR_INCOMPATIBLE_DRIVER가 나오며 vkCreateInstance가 실패해요
 
-If you are using MacOS with the latest MoltenVK SDK then `vkCreateInstance` may return the `VK_ERROR_INCOMPATIBLE_DRIVER` error. This is because [Vulkan SDK version 1.3.216 or newer](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html) requires you to enable the `VK_KHR_PORTABILITY_subset` extension to use MoltenVK, because it is currently not fully conformant.
+MacOS에서 최신 MoltenSDK를 사용 중이시라면 `vkCreateInstance`가 `VK_ERROR_INCOMPATIBLE_DRIVER` 오류를 반환할 수 있습니다. 이는 [Vulkan SDK 버전 1.3.216 이상](https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html)에서는 MoltenSDK를 사용하려면 `VK_KHR_PORTABILITY_subset` 확장을 활성화해야 하기 때문인데, 현재는 적합성이 완전히 검토되지 않았기 때문입니다.
 
-You have to add the `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` flag to your `VkInstanceCreateInfo` and add `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME` to your instance extension list.
+`VkInstanceCreateInfo`에 `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR` 플래그를 추가해야 하고 `VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME`를 인스턴스 확장 리스트에 추가해야 합니다.
 
-Code example:
+코드 예시:
 
 ```c++
 ...

From 990a8f95934bfd4162c6849591737d98ed8437a6 Mon Sep 17 00:00:00 2001
From: Hyungki Kim <diskhkme@gmail.com>
Date: Wed, 20 Mar 2024 13:13:08 +0900
Subject: [PATCH 47/47] kr translate sync up to date

---
 kr/00_Introduction.md                           | 17 +++++++++++++++++
 .../01_Shader_modules.md                        |  2 +-
 .../04_Swap_chain_recreation.md                 |  8 ++++----
 ...d => 00_Descriptor_set_layout_and_buffer.md} | 10 +++++-----
 .../01_Descriptor_pool_and_sets.md              |  6 +++---
 .../02_Combined_image_sampler.md                |  2 +-
 6 files changed, 31 insertions(+), 14 deletions(-)
 rename kr/05_Uniform_buffers/{00_Descriptor_layout_and_buffer.md => 00_Descriptor_set_layout_and_buffer.md} (91%)

diff --git a/kr/00_Introduction.md b/kr/00_Introduction.md
index 6feb29f5..a885b72c 100644
--- a/kr/00_Introduction.md
+++ b/kr/00_Introduction.md
@@ -58,3 +58,20 @@
 이후 튜토리얼을 따라가다 문제가 있다면, 먼저 FAQ에 동일한 문제가 이미 해결된 적이 있는지부터 확인해 보세요. 그러고 나서도 문제를 해결하지 못했다면, 관련된 챕터의 코멘트 섹션에 편하게 질문을 남겨 주세요.
 
 미래의 고성능 그래픽스 API에 뛰어들 준비가 되셨나요? [출발해 봅시다!](!kr/Overview)
+
+## License
+
+Copyright (C) 2015-2023, Alexander Overvoorde
+
+The contents are licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/),
+unless stated otherwise. By contributing, you agree to license
+your contributions to the public under that same license.
+
+The code listings in the `code` directory in the source repository are licensed 
+under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
+By contributing to that directory, you agree to license your contributions to
+the public under that same public domain-like license.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.
\ No newline at end of file
diff --git a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
index 07da4582..b9baa66b 100644
--- a/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
+++ b/kr/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md
@@ -10,7 +10,7 @@ GLSL은 C 스타일 문법을 가진 셰이더 언어입니다. GLSL로 작성
 
 ## 정점 셰이더
 
-정점 셰이더는 각 입력 정점을 처리합니다. 정점 셰이더는 월드 공간 좌표, 색상, 법선과 텍스처 좌표같은 입력 데이터를 어트리뷰트로 받습니다. 출력은 클립 좌표(clip coordinate) 위치와 프래그먼트 셰이더로 전달할 색상과 텍스처 좌표와 같은 어트리뷰트 들입니다. 이 값들은 래스터화 단계에서 여러 프래그먼트에 걸쳐 부드럽게 변하도록(smooth gradient) 보간됩니다.
+정점 셰이더는 각 입력 정점을 처리합니다. 정점 셰이더는 모델 공간 좌표, 색상, 법선과 텍스처 좌표같은 입력 데이터를 어트리뷰트로 받습니다. 출력은 클립 좌표(clip coordinate) 위치와 프래그먼트 셰이더로 전달할 색상과 텍스처 좌표와 같은 어트리뷰트 들입니다. 이 값들은 래스터화 단계에서 여러 프래그먼트에 걸쳐 부드럽게 변하도록(smooth gradient) 보간됩니다.
 
 *클립 좌표*는 정점 셰이더에서 도출된 4차원 벡터로 벡터를 마지막 구성요소의 값으로 나눔으로써 *정규화된 장치 좌표(normalized device coordinate)*로 변환됩니다. 정규화된 장치 좌표계는 [동차 좌표(homogeneous coordinates)](https://en.wikipedia.org/wiki/Homogeneous_coordinates)로, 아래 그림과 같이 프레임버퍼와 맵핑되는 [-1, 1]x[-1, 1] 좌표계입니다:
 
diff --git a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
index e63d3196..4f14ddd6 100644
--- a/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
+++ b/kr/03_Drawing_a_triangle/04_Swap_chain_recreation.md
@@ -42,12 +42,12 @@ void recreateSwapChain() {
 
 ```c++
 void cleanupSwapChain() {
-    for (size_t i = 0; i < swapChainFramebuffers.size(); i++) {
-        vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr);
+    for (auto framebuffer : swapChainFramebuffers) {
+        vkDestroyFramebuffer(device, framebuffer, nullptr);
     }
 
-    for (size_t i = 0; i < swapChainImageViews.size(); i++) {
-        vkDestroyImageView(device, swapChainImageViews[i], nullptr);
+    for (auto imageView : swapChainImageViews) {
+        vkDestroyImageView(device, imageView, nullptr);
     }
 
     vkDestroySwapchainKHR(device, swapChain, nullptr);
diff --git a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/kr/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.md
similarity index 91%
rename from kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
rename to kr/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.md
index 895a4253..a3524ac5 100644
--- a/kr/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md
+++ b/kr/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.md
@@ -4,11 +4,11 @@
 
 Vulkan에서 이를 처리하는 올바른 방법은 *리소스 기술자(resource descriptor)*를 사용하는 것입니다. 기술자는 셰이더가 버퍼나 이미지와 같은 리소스에 자유롭게 접근하게 해 주는 방법입니다. 우리는 변환 행렬을 가지고 있는 버퍼를 설정하고 정점 셰이더가 기술자를 통해 이에 접근할 수 있도록 할 것입니다. 기술자의 사용은 세 부분으로 이루어져 있습니다:
 
-* 파이프라인 생성 시점에 기술자 레이아웃 명시
+* 파이프라인 생성 시점에 기술자 집합 레이아웃 명시
 * 기술자 풀로부터 기술자 집합(set) 할당
 * 렌더링 시점에 기술자 바인딩
 
-*기술자 레이아웃*은 파이프라인에서 접근할 리소스의 타입을 명시하고, 이는 렌더 패스에서 접근할 어태치먼트의 타입을 명시하는 것과 비슷합니다. *기술자 집합*은 기술자에 바인딩될 버퍼나 이미지를 명시하는데, 이는 프레임버퍼가 렌더 패스 어태치먼트에 바인딩될 실제 이미지 뷰를 명시하는 것과 비슷합니다. 이후에 기술자 집합은 정점 버퍼나 프레임버퍼와 유사하게 그리기 명령에 바인딩됩니다.
+*기술자 집합 레이아웃*은 파이프라인에서 접근할 리소스의 타입을 명시하고, 이는 렌더 패스에서 접근할 어태치먼트의 타입을 명시하는 것과 비슷합니다. *기술자 집합*은 기술자에 바인딩될 버퍼나 이미지를 명시하는데, 이는 프레임버퍼가 렌더 패스 어태치먼트에 바인딩될 실제 이미지 뷰를 명시하는 것과 비슷합니다. 이후에 기술자 집합은 정점 버퍼나 프레임버퍼와 유사하게 그리기 명령에 바인딩됩니다.
 
 기술자에는 다양한 종류가 있는데 이 챕터에서는 유니폼 버퍼 객체(uniform buffer object, UBO)를 사용할 것입니다. 다른 타입의 기술자에 대해서는 나중 챕터에서 알아볼 것이고, 사용 방법은 동일합니다. 정점 셰이더에서 사용하려고 하는 데이터가 아래와 같은 C 구조체 형식의 데이터라고 해 봅시다:
 
@@ -61,7 +61,7 @@ void main() {
 }
 ```
 
-`uniform`, `in`, `out` 선언의 순서는 상관 없다는 것에 유의하십시오. `binding` 지시자는 어트리뷰트의 `location` 지시자와 유사합니다. 이 바인딩을 기술자 레이아웃에서 참조할 것입니다. `gl_Potision`이 있는 라인은 클립 공간에서의 위치를 계산하기 위해 변환 행렬들을 사용하는 것으로 수정되었습니다. 2D 삼각형의 경우와는 다르게, 클립 공간 좌표의 마지막 요소는 `1`이 아닐 수 있습고, 이는 최종적으로 정규화된 장치 좌표계(normalized device coordinate)로 변환될 때 나눠지게 됩니다. 원근 투영을 위해 이러한 과정이 수행되고, 이를 *perspective division*이라고 합니다. 이로 인해 가까운 물체가 멀리 있는 물체보다 크게 표현됩니다.
+`uniform`, `in`, `out` 선언의 순서는 상관 없다는 것에 유의하십시오. `binding` 지시자는 어트리뷰트의 `location` 지시자와 유사합니다. 이 바인딩을 기술자 집합 레이아웃 에서 참조할 것입니다. `gl_Potision`이 있는 라인은 클립 공간에서의 위치를 계산하기 위해 변환 행렬들을 사용하는 것으로 수정되었습니다. 2D 삼각형의 경우와는 다르게, 클립 공간 좌표의 마지막 요소는 `1`이 아닐 수 있습고, 이는 최종적으로 정규화된 장치 좌표계(normalized device coordinate)로 변환될 때 나눠지게 됩니다. 원근 투영을 위해 이러한 과정이 수행되고, 이를 *perspective division*이라고 합니다. 이로 인해 가까운 물체가 멀리 있는 물체보다 크게 표현됩니다.
 
 ## 기술자 집합 레이아웃
 
@@ -150,7 +150,7 @@ pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
 
 이 부분에 왜 여러개의 기술자 집합 레이아웃을 명시할 수 있게 되어있는지 궁금하실겁니다. 하나의 기술자 집합 레이아웃에 이미 모든 바인딩 정보가 들어있는데도 말이죠. 다음 챕터에서 기술자 풀과 기술자 집합을 살펴보면서 이에 대한 이야기를 해 보도록 하겠습니다.
 
-기술자 레이아웃은 새로운 그래픽스 파이프라인이 생성될 동안, 즉 프로그램 종료 시까지 유지되어야 합니다:
+기술자 집합 레이아웃은 새로운 그래픽스 파이프라인이 생성될 동안, 즉 프로그램 종료 시까지 유지되어야 합니다:
 
 ```c++
 void cleanup() {
@@ -314,6 +314,6 @@ UBO를 이런 방식으로 사용하는 것은 자주 바뀌는 값을 셰이더
 
 다음 챕터에서는 `VkBuffer`들을 유니폼 버퍼 기술자에 바인딩하는 기술자 집합에 대해 알아볼 것입니다. 이를 통해 셰이더가 이러한 변환 데이터에 접근할 수 있게 될 것입니다.
 
-[C++ code](/code/22_descriptor_layout.cpp) /
+[C++ code](/code/22_descriptor_set_layout.cpp) /
 [Vertex shader](/code/22_shader_ubo.vert) /
 [Fragment shader](/code/22_shader_ubo.frag)
diff --git a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
index 2bbd1c0e..efcb2de2 100644
--- a/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
+++ b/kr/05_Uniform_buffers/01_Descriptor_pool_and_sets.md
@@ -1,6 +1,6 @@
 ## 서론
 
-이전 장에서의 기술자 레이아웃은 바인딩 될 수 있는 기술자의 타입을 명시합니다. 이 장에서는 각 `VkBuffer` 리소스를 위한 기술자 집합을 만들어서 유니폼 버퍼 기술자에 바인딩할 것입니다.
+이전 장에서의 기술자 집합 레이아웃은 바인딩 될 수 있는 기술자의 타입을 명시합니다. 이 장에서는 각 `VkBuffer` 리소스를 위한 기술자 집합을 만들어서 유니폼 버퍼 기술자에 바인딩할 것입니다.
 
 ## 기술자 풀
 
@@ -77,7 +77,7 @@ void createDescriptorSets() {
 }
 ```
 
-기술자 집합의 할당은 `VkDescriptorSetAllocateInfo` 구조체를 사용합니다. 어떤 기술자 풀에서 할당할 것인지, 기술자 집합을 몇 개나 할당할 것인지, 기반이 되는 기술자 레이아웃이 무엇인지 등을 명시합니다:
+기술자 집합의 할당은 `VkDescriptorSetAllocateInfo` 구조체를 사용합니다. 어떤 기술자 풀에서 할당할 것인지, 기술자 집합을 몇 개나 할당할 것인지, 기반이 되는 기술자 집합 레이아웃이 무엇인지 등을 명시합니다:
 
 ```c++
 std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout);
@@ -314,7 +314,7 @@ struct UniformBufferObject {
 
 ## 다중 기술자 집합
 
-몇몇 구조체와 함수 호출에서 눈치 채실 수 있듯이, 다중 기술자 집합을 동시에 바인딩 하는 것이 가능합니다. 이 경우 각 기술자 집합에 대해 파이프라인 레이아웃 생성시에 기술자 레이아웃을 생성해야 합니다. 셰이더에서는 특정 기술자 집합을 아래와 같이 참조해야 합니다:
+몇몇 구조체와 함수 호출에서 눈치 채실 수 있듯이, 다중 기술자 집합을 동시에 바인딩 하는 것이 가능합니다. 이 경우 각 기술자 집합에 대해 파이프라인 레이아웃 생성시에 기술자 집합 레이아웃을 생성해야 합니다. 셰이더에서는 특정 기술자 집합을 아래와 같이 참조해야 합니다:
 
 ```c++
 layout(set = 0, binding = 0) uniform UniformBufferObject { ... }
diff --git a/kr/06_Texture_mapping/02_Combined_image_sampler.md b/kr/06_Texture_mapping/02_Combined_image_sampler.md
index bff4550c..82ec8282 100644
--- a/kr/06_Texture_mapping/02_Combined_image_sampler.md
+++ b/kr/06_Texture_mapping/02_Combined_image_sampler.md
@@ -2,7 +2,7 @@
 
 유니폼 버퍼 튜토리얼에서 처음으로 기술자에 대해 알아봤었습니다. 이 챕터에서는 새로운 종류의 기술자인 *결합된 이미지 샘플러(combined image sampler)* 에 대해 알아보겠습니다. 이 기술자를 사용해서 셰이더로부터 우리가 만든 샘플러 객체를 거쳐 이미지 리소스에 접글할 수 있게 됩니다.
 
-먼저 기술자 레이아웃, 기술자 풀, 기술자 집합이 결합된 이미지 샘플러와 같은 것을 포함할 수 있도록 수정할 것입니다. 그 이후에 `Vertex`에 텍스처 좌표를 추가하고 프래그먼트 셰이더를 수정하여 정점 색상을 보간하는 것이 아니라 텍스처로부터 색상값을 읽어오도록 할 것입니다.
+먼저 기술자 집합 레이아웃, 기술자 풀, 기술자 집합이 결합된 이미지 샘플러와 같은 것을 포함할 수 있도록 수정할 것입니다. 그 이후에 `Vertex`에 텍스처 좌표를 추가하고 프래그먼트 셰이더를 수정하여 정점 색상을 보간하는 것이 아니라 텍스처로부터 색상값을 읽어오도록 할 것입니다.
 
 ## 기술자 수정