02 Feb 2022
취미로 프로그래밍을 할 수 있을 정도로 회복된 요즘, 다시 Vulkan 튜토리얼을 보면서 복습 중입니다.
예제를 돌려보면서 어떤 식으로 내가 원하는 형태로 추상화를 할 수 있을까 고민하다가,
Vulkan 내 오브젝트 간의 의존성이 어떻게 되는지를 정리를 해보는데,
‘그런 일을 누군가는 이미 하지 않았을까’ 싶어서 검색을 좀 했더니 좋은 글이 나오더군요.
공부하는 차원에서 오랜만에 번역을 해봤습니다.
원문 보기: https://gpuopen.com/learn/understanding-vulkan-objects
Vulkan 오브젝트 이해하기
Vulkan API를 배우는 중요한 부분은 (다른 API와 마찬가지로) 어떤 종류의 오브젝트가 정의되어 있는지,
이들이 무엇을 나타내고 서로 어떻게 연관되어 있는지 이해하는 것입니다. 이를 돕기 위해서 우리는
모든 Vulkan 오브젝트들과 이들의 관계성, 특히 어떤 오브젝트로부터 다른 오브젝트를 만드는지에 대한
순서를 보여주는 다이어그램을 만들었습니다.
각각의 Vulkan 오브젝트는 Vk
라는 앞글자가 붙은 특정 타입의 값입니다. 이 앞글자는
다이어그램의 간소화를 위해 생략하였으며 이는 함수 이름 앞에 붙은 vk
도 마찬가지입니다.
예를 들면 다이어그램의 Sampler는 VkSampler
라는 Vulkan 오브젝트 타입이 있다는 의미입니다.
이러한 타입들은 포인터나 보통의 숫자처럼 취급되어서는 안됩니다. 이 값들을 어떤 방식으로든
해석하려고 하면 안됩니다. 그저 이들을 명확한 핸들로서 취급하고, 함수의 파라미터로 넘기고,
당연하겠지만 필요 없어질 경우 제거하는 것만 잊지 않으면 됩니다. 녹색 바탕의 오브젝트들은
자체 타입이 없고, 대신 부모 오브젝트 내의 uint32_t
타입 정수형 인덱스 값으로 표현됩니다.
QueryPool의 Query 같은 것들이 그렇습니다.
화살표가 붙은 실선은 생성 순서를 나타냅니다. 예를 들어 DescriptorSet을 만들기 위해서는 이미
만들어진 DescriptorPool을 지정해야 합니다. 다이아몬드가 붙은 실선은 구성(composition)을
나타내는데, 이는 그 오브젝트가 부모 오브젝트에 이미 존재하므로 별도로 만들 필요가 없이 그냥
가져올 수 있다는 뜻입니다. 예를 들면 Instance 오브젝트로부터 PhysicalDevice 오브젝트들을
열거할 수 있습니다. 점선은 그 밖의 관계를 의미합니다. CommandBuffer에 대한 다양한 명령들을
제출하는 것이 그렇습니다.
이 다이어그램은 크게 세 부분으로 나뉘어져 있습니다. 각 부분은 빨간색으로 표시된 메인 오브젝트를
가지고 있습니다. 다른 모든 오브젝트들은 이 메인 오브젝트로부터 직접적으로 혹은 간접적으로
생성됩니다. 예를 들면 Sampler를 생성하는 vkCreateSampler
함수는 VkDevice
를 첫 번째
파라미터로 받습니다. 간소화를 위해서 메인 오브젝트와의 관계성은 다이어그램에 그리지 않았습니다.

모든 오브젝트들에 대하여 간략한 설명을 해보겠습니다:
- Instance는 첫번째로 만들게 되는 오브젝트입니다. 여러분의 어플리케이션과
vulkan 런타임 간의 연결을 나타내므로 어플리케이션 당 오직 하나만 있어야 합니다.
또한 Instance는 vulkan 사용에 필요한 어플리케이션용 상태값을 모두 저장하므로,
Instance를 생성할 때는 여러분에게 필요한 모든 layer와 extension을 지정해야 합니다.
- PhysicalDevice는 그래픽 카드 같이 vulkan을 사용할 수 있는 특정 장치를 나타냅니다.
Instance로부터 이들을 열거한 뒤에 venderID, deviceID, 그리고 지원하는 기능이나
속성, 한계값 등을 질의할 수 있습니다.
- PhysicalDevice는 사용가능한 QueueFamily들을 열거할 수 있습니다.
Graphics queue를 주로 사용하겠지만, Compute나 Transfer 용도만 제공하는 추가 큐를
사용할 수도 있습니다
- PhysicalDevice는 또한 장치 내 Memory Heap과 Memory Type을 열거할 수 있습니다.
Memory Heap은 RAM의 특정한 풀(pool)을 나타냅니다. 이는 여러분의 메인보드 상에 있는 시스템
RAM이나, 특정 그래픽 카드 상에 있는 비디오 RAM의 특정 메모리 공간, 혹은 구현체가 노출하는
그밖의 호스트/디바이스에 특화된 메모리를 추상화합니다. 메모리를 할당할 때는 Memory Type을
지정해야 합니다. 호스트에서 접근 되어야 한다던지, CPU/GPU 상의 값이 일치해야 한다던지,
혹은 캐시가 되어야 한다던지 등 할당할 메모리에 대한 요구사항을 지정합니다.
디바이스 드라이버에 따라서 이 타입들 간의 임의 조합이 있을 수 있습니다.
- Device는 논리적 장치 혹은 열린(opened) 장치로 생각할 수 있습니다.
다른 모든 오브젝트들을 만들 준비가 되어 있는, 초기화된 vulkan 장치를 나타내는
메인 오브젝트입니다. DirectX의 Device 오브젝트와 비슷한 개념입니다. Device를 만드는
동안에는 활성화하고 싶은 특성을 지정할 필요가 있는데, 이중에는 anisotropic texture
filtering 같은 기본적인 것도 있습니다. 또한 사용하고자 하는 모든 큐의 개수와 각
큐의 QueueFamily들을 기술해줘야 합니다.
- Queue는 장치에서 실행될 명령들의 큐를 나타내는 오브젝트입니다.
GPU에서 수행되는 모든 실질적인 작업들은 CommandBuffer를 채우고
vkQueueSubmit
함수를
이용하여 Queue에 제출하는 방식으로 요청됩니다. Graphics queue와 Compute queue 등
여러개의 큐가 있다면, 서로 다른 CommandBuffer를 각각에게 제출할 수 있습니다.
이러한 방식을 올바르게 사용한다면 상당한 성능 향상을 끌어낼 수 있는 비동기 컴퓨팅이 가능해집니다.
- CommandPool은 CommandBuffer를 할당하기 위해 사용되는 단순한 오브젝트입니다.
특정한 QueueFamily에 연결되어 있습니다.
- CommandBuffer는 특정한 CommandPool로부터 할당됩니다. 이것은 논리적 장치에서 수행될
다양한 명령들을 담을 버퍼를 나타냅니다. CommandBuffer에 대한 다양한 함수들을 호출할 수 있는데,
이 함수들은 모두
vkCmd
로 시작합니다. 이 명령들은 CommandBuffer가 Queue에 제출되고
Device에 의해 처리될 때 수행되어야 할 작업에 대한 순서, 타입, 파라미터를 지정하는데 쓰입니다.
- Sampler는 특정한 이미지에 바인딩되지 않고, 필터링 모드 (nearest / linear) 혹은
어드레싱 모드 (repeat / cleamp-to-edge) 같은 단순한 상태값 묶음으로 보면 됩니다.
- 장치 메모리를 점유하는 리소스는 Buffer와 Image 두 가지 종류가 있습니다.
Buffer는 비교적 단순한 편으로, 바이트 단위로 표현된 길이값을 갖는 바이너리 데이터를
위한 컨테이너입니다. 한편 Image는 픽셀 묶음을 표현하는 것으로 다른 그래픽스 API에서는
텍스처라고 알려져 있습니다. 이미지를 생성하는데는 더 많은 파라미터가 필요합니다.
1D, 2D 혹은 3D가 될 수 있고, (
R8G8B8A8_UNORM
이나 R32_SFLOAT
같이) 다양한 픽셀
포맷을 가질 수도 있고, 여러 개의 배열 레이어나 MIP 레벨 혹은 둘 다 갖기 위해서
여러 장의 이미지로 구성될 수도 있습니다. 이미지는 직접 접근할 수 있는 픽셀들의 단순한
선형 구조로만 구성되지는 않기 때문에, 별도의 오브젝트로 만들어집니다. 이미지는
그래픽 드라이버에 의해 관리되는 구현 별로 특화된 내부 포맷값 (tiling과 layout 등)을
가질 수 있습니다.
- 특정 길이의 버퍼 혹은 특정 차원의 이미지를 생성하는 것이 자동으로 메모리 할당을
해주지는 않습니다. 여러분에 의해 수동으로 수행되어야 하는 3단계 프로세스가 있습니다.
VulkanMemoryAllocator
같은 라이브러리를 사용할 수도 있습니다.
- DeviceMemory를 할당한다
- Buffer 혹은 Image를 생성한다
vkBindBufferMemory
나 vkBindImageMemory
함수를 사용하여 둘을 바인딩한다
- DeviceMemory를 생성해야하는 이유가 바로 이 때문입니다. DeviceMemory는
(PhysicalDevice에서 지원하는) 특정한 메모리 타입으로부터 특정 바이트 길이로 할당된
메모리 블럭을 나타냅니다. Buffer와 Image 별로 각각 DeviceMemory를 할당하면 안됩니다.
대신 큰 메모리 덩어리를 할당하여 그 일부를 Buffer와 Image에 대입시켜야 합니다.
할당은 비용이 큰 연산이고, PhysicalDevice로부터 질의해서 알아낼 수 있는 정보 중에는
최대 할당 개수 한계값도 있습니다.
- 모든 이미지에 대해 DeviceMemory를 할당하고 바인딩할 의무가 있는 중에서 하나의 예외가 있다면
그것은 Swapchain의 생성 부분입니다. 이것은 여러분의 운영체제에서 그리고 있는 화면 혹은 윈도우 내에
최종 이미지를 표시하는데 사용되는 개념입니다. Swapchain을 만드는 방법은 플랫폼에 따라 다릅니다.
시스템 API를 사용하여 초기화된 윈도우를 이미 가지고 있다면 먼저 SurfaceKHR 오브젝트를
생성해야 합니다. 이는 Instance 오브젝트 뿐만 아니라 몇가지 시스템 종속적인 파라미터를
필요로 합니다. 예를 들면 Windows에서는
HINSTANCE
와 HWND
같은 것들이지요.
SurfaceKHR 오브젝트는 윈도우의 Vulkan식 표현이라고 생각할 수 있습니다.
- 이로부터 SwapchainKHR를 생성할 수 있습니다. 이 오브젝트는 Device가 필요합니다.
이것은 Surface에 나타나게 될 여러 장의 이미지 (더블 혹은 트리플 버퍼링을 사용하는) 를
나타냅니다. Swapchain이 가지고 있는 Image를 쿼리할 수 있습니다. 이 이미지들은
시스템에 의해 할당된 메모리를 이미 가지고 있습니다.
- Buffer와 Image은 언제나 직접적으로 렌더링에 사용되지는 않습니다. 그 위에 view라고
불리는 또다른 레이어가 있습니다. 데이터베이스의 view와 같은 것으로 생각하면 됩니다.
내부 데이터 셋을 원하는 방식으로 보는데 사용될 수 있는 파라미터 셋인 셈이죠.
BufferView는 특정 버퍼를 기반으로 만들어지는 오브젝트입니다. 버퍼 데이터의
일부분만 보도록 제한하기 위해서 오프셋과 범위를 생성 중에 지정할 수 있습니다.
마찬가지로 ImageView도 특정 이미지를 참조하는데 쓰는 파라미터 셋입니다.
여기서는 별도의 (호환되는) 픽셀 포맷, 스위즐링 (swizzling), MIP 레벨이나 배열
레이어의 특정 범위 제한을 통하여 해석된 픽셀값에 접근하도록 할 수 있습니다.
- 쉐이더가 이러한 리소스들 (Buffer, Image 및 Sampler)에 접근하는 방법은
Descriptor를 통하는 것입니다. Descriptor는 단일 개체로 존재하지 않고
언제나 DescriptorSet의 형태로 그룹을 이룹니다. 그러나 DescriptorSet을 생성하기
전에 DescriptorSetLayout을 생성하여 레이아웃을 지정해야 하는데, 이는
DescriptorSet의 탬플릿과 같은 것입니다. 예를 들어 3D 지오메트리를 그리기 위해
렌더링 패스에 사용되는 쉐이더가 다음과 같은 것들을 요구할 수도 있습니다:
Binding slot |
Resource |
0 |
vertex shader stage에서 활용할 유니폼 버퍼 (DirectX의 constant buffer) |
1 |
fragment shader stage에서 사용할 유니폼 버퍼 |
2 |
샘플링된 이미지 |
3 |
fragment shader stage에서 사용할 샘플러 |
- DescriptorPool도 생성할 필요가 있습니다. 이것은 DescriptorSet을 할당하기 위한 단순한
오브젝트입니다. DescriptorPool을 생성할 때는 여러분들이 할당할 Descriptor의 종류와 최대 갯수를
지정해야 합니다.
- 마지막으로 DescriptorSet을 할당합니다. 이를 할당하려면 DescriptorPool과 DescriptorSetLayout
둘 다 필요합니다. DescriptorSet은 실제 Descriptor가 가지고 있는 메모리를 나타내고, Descriptor가
특정한 Buffer, BufferView, Image, 혹은 Sampler를 가리키도록 설정할 수 있습니다.
vkUpdateDescriptorSet
함수를 이용하면 이런 설정을 할 수 있습니다.
- 몇몇 DescriptorSet들은 렌더링 명령에 사용되기 위해 CommandBuffer에 능동적인 셋으로 바인딩 됩니다.
이를 위해는
vkCmdBindDescriptorSets
함수를 사용합니다. 이 함수는 또다른 오브젝트인
PipelineLayout을 필요로 하는데, 그 이유는 여러 개의 DescriptorSet이 바인딩될 수도 있고
Vulkan이 예상해야 하는 DescriptorSet이 몇 개고 어떤 타입인지 미리 알아야 하기 때문입니다.
PipelineLayout은 어떤 타입의 DescriptorSet이 CommandBuffer에 바인딩 될 것인지에 대한 측면에서
렌더링 파이프라인의 설정을 나타냅니다. 이로부터 DescriptorSetLayout을 생성합니다.
- 다른 그래픽스 API에서는 immediate mode 접근법을 취하여 다음에 뭐가 나오면 되는지 그냥 그리면
됩니다. Vulkan에서는 그게 불가능합니다. 대신 매 프레임의 렌더링을 사전에 계획하고
Pass와 Subpass들로 조직화해야 할 필요가 있습니다. Subpass는 별도의 오브젝트는 아니므로
여기서 다루지는 않겠지만, Vulkan의 렌더링 시스템에서 중요한 부분입니다. 다행히도 여러분의 작업을
준비할 때 모든 세부사항을 다 알 필요는 없습니다. 예를 들자면 Queue에 제출할 때 렌더링할 삼각형의
개수를 지정할 수 있는데, Vulkan의 RenderPass를 정의할 때 제일 중요한 부분은 Pass에서 사용하게
될 Attachment의 픽셀 포맷과 갯수입니다.
- Attachment란 여러분들이 렌더 타겟 (렌더링으로부터 출력된 결과로 사용될 이미지) 이라고 알고 있을지도
모르는 것의 Vulkan식 이름입니다. 여기서는 특정한 이미지를 지정할 필요는 없고, 픽셀 포맷만
기술해주면 됩니다. 예를 들면 단순한 렌더링 패스의 경우
R8G8B8A8_UNORM
포맷의 컬러 Attachment와
D16_UNORM
포맷의 깊이/스텐실 Attachment를 사용할 수도 있습니다. Pass가 시작될 때 이 Attachment들의
내용물을 보존해야 하는지, 버릴지, 아니면 클리어할 지도 여기서 지정합니다.
- Framebuffer (SwapchainKHR과 혼동하지 마세요) 는 Attachment (렌더 타겟) 으로 사용될 수 있는
실질적인 이미지에 대한 링크를 나타냅니다. Framebuffer 오브젝트는 RenderPass와 ImageView의 묶음을
지정하여 만들게 됩니다. 물론 Framebuffer내 이미지 갯수와 픽셀 포맷은 RenderPass에 지정된 것과
일치해야 합니다. Framebuffer는 Image 위에 있는 또다른 레이어고 기본적으로 특정한 RenderPass의
렌더링 동안 여러 ImageView들이 Attachment로 바인딩 되도록 그룹화합니다. 어떤 RenderPass의 렌더링을
시작할 때마다
vkCmdBeginRenderPass
함수를 호출하고 여기에 Framebuffer를 넘기게 됩니다.
- Pipeline은 위에서 나열할 대부분의 오브젝트들을 묶어놓은 큰 것입니다. 이것은 전체 파이프라인의
설정을 나타내고 매우 많은 파라미터를 가지고 있습니다. 이 파라미터 중 하나가 PipelineLayout입니다 -
Descriptor와 Push constant들의 구성을 정의하죠. ComputePipeline과 GraphicsPipeline 두 가지 종류가
있습니다. ComputePipeline이 좀 더 단순한 편인데, 이쪽이 지원하는 것은 모두 컴퓨팅만 하는 프로그램
(종종 Compute 쉐이더 라고 불리는 것) 이기 때문입니다. GraphicsPipeline이 훨씬 복잡한데,
이쪽은 vertex, fragment, geometry, compute, 그리고 만약 가능하다면 tessellation, 거기에
더해 버텍스 속성, 기본 도형의 토폴로지 종류, 후면 제거, 블랜딩 모드와 같은 모든 파라미터를 감싸는
것이기 때문에 그렇습니다. 이 모든 파라미터들은 예전의 오래된 그래픽스 API (DirectX 9, OpenGL)에서는
개별적인 설정 값으로 존재했었고, 그후에 API가 발전하면서 (DirectX 10, 11) 좀 더 작은 개수의
상태 오브젝트들로 그룹화 되었는데, 지금 Vulkan 같은 현대적인 API에서는 하나의 거대하고 불변하는
오브젝트로 구워져야 하게 되었습니다. 렌더링 하는 동안 필요로하는 각각의 다른 파라미터 셋마다
새로운 Pipeline을 생성해야합니다. 그런 뒤에
vkCmdBindPipeline
함수를 호출하여 CommandBuffer내에
지금 사용하려는 Pipeline을 지정할 수 있습니다.
- Vulkan에서 쉐이더 컴파일은 여러 단계로 진행됩니다. Vulkan은 GLSL이나 HLSL같은 고수준 쉐이딩
언어를 지원하지 않습니다. 대신 Vulkan은 SPIR-V라 불리는 중간 형태의 포맷을 받아들이는데,
이 포맷은 어떤 고수준 언어에서든 생성할 수 있습니다. SPIR-V로 되어있는 데이터로 채워진 버퍼가
ShaderModule를 생성하는데 사용됩니다. 이 오브젝트는 쉐이더 코드 조각을 나타내는 것으로,
일부가 컴파일된 형태일 가능성이 있지만, 아직 GPU가 실행할 수 있는 형태의 것은 아닙니다.
Pipeline을 만들때 여러분들이 사용할 각 쉐이더 스테이지 (vertex, tessellation control,
tessellation evaluation, geometry, fragment, 혹은 compute) 에 대하여 ShaderModule과
함께 엔트리 포인트 함수의 이름 (“main” 같은) 을 지정해야 합니다.
- PipelineCache라고 불리는 헬퍼 오브젝트도 있는데, 파이프라인 생성 속도를 높이는 데
사용될 수 있습니다. Pipeline 생성시에 추가적으로 넘길 수 있는 단순한 오브젝트이지만,
메모리 사용량 감소와 파이프라인 컴파일 시간을 통한 성능 향상에 실제로 도움을 줍니다.
드라이버가 몇가지 중간 데이터를 저장하는데 이것을 내부적으로 사용할 수 있으므로,
비슷한 파이프라인들을 생성하는 것이 더 빨라질 가능성이 생깁니다. 또한 PipelineCache
오브젝트의 상태값을 바이너리 버퍼에 저장하거나 혹은 불러올 수 있고, 디스크에 저장했다가
다음번 어플리케이션 실행 때 이를 사용할 수도 있습니다. 이러한 사용법을 추천드립니다!
- Query는 Vulkan의 또다른 종류의 오브젝트입니다. GPU에 의해 쓰여진 특정한 숫자값을
읽어오는데 사용됩니다. Occlusion 쿼리 (여러분에게 어떤 픽셀이 렌더링 되었는지 아닌지
알려주는 것, 즉 쉐이딩 전 후 테스트를 모두 통과하여 프레임에 쓰여졌는지에 대한 것) 혹은
Timestamp 쿼리 (GPU 하드웨어 카운터로부터 얻은 타임스탬프 값) 등 서로 다른 종류의 쿼리가
있습니다. Query는 자체 타입이 없는데, 이는 QueryPool 내부에 상주하면서
uint32_t
인덱스값으로만 표현되기 때문입니다. QueryPool 타입과 저장할 쿼리의 개수를 지정함으로써
생성할 수 있습니다. 그러고 나면 vkCmdBeginQuery
, vkCmdEndQuery
혹은 vkCmdWriteTimestamp
같은 함수를 사용하여 CommandBuffer에 명령을 제기하는 것으로 사용 가능합니다.
- 마지막으로 동기화를 위해 사용되는 Fence, Semaphore, Event 같은 오브젝트들이
있습니다. Fence는 어떤 작업이 끝났음을 호스트에게 알려줍니다. 기다리거나 폴링하거나
혹은 호스트에 의해 수동으로 시그널 초기화가 될 수 있습니다. 자체 명령 함수는 가지고
있지 않고
vkQueueSubmit
함수를 통해 넘겨집니다. 제출된 큐가 해당 팬스를 끝내면
시그널 처리가 됩니다.
- Semaphore는 설정 파라미터 없이 만들어집니다. 이것은 여러 큐들 사이에서의 리소스
접근을 제어하는데 사용됩니다. CommandBuffer 제출의 일부분으로서 시그날 혹은 웨이트될 수 있고
vkQueueSubmit
호출을 통해서도 할 수 있고, 한쪽 큐 (예를 들면 compute) 에서 시그널되고
다른 쪽 큐 (예를 들면 graphics) 에서 이를 기다리게 할 수도 있습니다.
- Event 또한 설정 파라미터 없이 만들어집니다.
vkCmdSetEvent
, vkResetEvent
, 그리고
vkCmdWaitEvents
함수를 이용하여 CommandBuffer에 제출된 개별 명령에 대해 GPU 상에서
시그널 혹은 웨이트를 걸 수 있습니다. 또한 하나 혹은 여러 개의 CPU 스레드로부터
vkGetEventStatus
함수 호출을 폴링하는 것으로 셋, 리셋, 웨이트할 수도 있습니다.
GPU 의 특정 지점에서 동기화가 필요하거나 렌더 패스 내에서 Subpass 의존성이 사용될 경우
vkCmdPipelineBarrier
함수를 이용할 수도 있습니다.
이제 여러분은 GPU가 모든 프레임에서 실질적인 작업을 수행하도록 하기 위해서 (가능한 병렬적으로!)
Vulkan 함수를 어떻게 호출해야 하는지 배울 필요가 있겠습니다. 그러면 현대적인 GPU가 제공하는
훌륭하고 유연한 연산 성능을 모두 활용할 여러분만의 방식을 갖게 될 것입니다.
Comment count
31 Dec 2021
많은 일이 있었던 한 해를 마지막날 정리해봅니다…
1월~2월
- 그간 꾸려왔던 스타트업 사정이 최악의 상황이 되면서, 어떻게든 다른 먹고 살 길을 만들어야 했었습니다…
- 배운게 도둑질이라고, 코딩 과외를 서너개 하기 시작함니다.
- 그 와중에 선배의 부탁으로 대학교의 OpenGL 강의를 맡게됨니다…
- 대학교가 매우 먼거리에 있었으므로 처음 연락받았을 때는 당연히 거절을 하려 했으나,
코시국의 도움으로(?) 영상 강의가 되어 강의를 할 수 있게 되었습니다
- 강의 준비를 틈틈히 하면서 회사 일들 정리를 하기 시작함니다
3월~6월
- 학기가 시작되고 처음으로 해본 영상 강의는, “내가 얼마나 말을 잘 못하는가”를 알게 해준 시간이었다 생각함니다
- 3시간 분량의 영상을 찍고, 편집하는데 높은 퀄리티를 보장하려면 정말 어마어마한 시간이 걸리겠구나… 싶었습니다
- 스크립트를 자동으로 뽑아주는 소프트웨어가 있어서 그걸 써보려고 했지만, 제 발음이 안좋아서 +
전공용어는 AI가 알아듣기 힘들어서 써먹을 수가 없었습니다 ㅜㅜ
- 하다보니 편집을 할 타이밍에 저도 모르게 “짝” 하고 박수 소리를 넣어 표시하는 저를 발견합니다
- 그래서 방송하시는 분들이 영상 찍기 전에 박수 한번 쳐달라고 하는구나… 하고 자연스레 알게됨니다
- 당연히 4학년 과목인줄 알고 준비했던 컴퓨터 그래픽스 수업은… 2학년 과목이었습니다
- 매우 욕심넘치는 커리큘럼덕에 수업을 들었던 2학년 학생들은 정말 죽을 고생을 했을 것입니다…
- 얼마나 욕심이 넘쳤나면, 마지막에는 저도 잘 모르는 PBR을 다루었습니다…
- 회사에서는 웹 + webrtc + three.js 기반의 프로젝트를 진행하고 있었습니다
- 홀로 front/backend를 모두 만드는 풀 스택 개발자분들 존경함니다… 그 정신없는 걸 어찌한담
7월~8월
- 회사에는 우선 2개월 동안 논다고 얘기하고 무기한 무급 휴직에 돌입했습니다
- 그게 가능했던 것이, 3월부터 6월까지한 강의에 대한 월급이 3~8월까지 6개월 분으로 쪼개져서 들어왔기 때문에,
7~8월에 약간의 급여를 받을 수 있었거든요 :D
- 2013년 초였던가, 통으로 1달 놀았던 그 시절을 떠올리며 즐거이 놀려고 하다가…
- 평소에 테니스를 치는 저를 카메라로 찍어보고 “세상에 이렇게 몸을 못움직일수가” 라고 회상했던 3월쯔음의
기억을 떠올려봅니다
- 이 타이밍 아니면 기회가 없을거 같아서 난생 처음 PT를 받아보게 됨니다…
- 그래도 테니스를 쳐오던게 쓸모가 있었는지, PT 선생님이 시키는게 그리 무리같지는 않았습니다
- 그리고 백수처럼 지낼 수 있게 되다보니, 하루 종일 요리하고 - 먹고 - 운동하고를 반복할 수 있었습니다
- 그 결과 2개월만에 대략 7키로 정도를 감량하였습니다 (!)
9월~12월
- 9월이 되기 전, 슬슬 백수 하면서 새로운 직장을 알아봐야겠구나… 라고 생각한 즈음, 오퍼가 들어와서
새 직장에 무사히 이직했습니다
- 멋대로 살아온터라 어느 회사의 직원이 된 적이 없었으므로, 자연스레 사회 초년생의 마인드가 되어
회사에 과연 잘 적응할 수 있을까 염려하면서 출퇴근을 함니다
- 그러나 사람이 고민하는 일은 결국 다 비슷하다는 만고의 진리를 깨달으면서, 이전 회사에서 고민해왔던
일들과 비슷한 일들이 제 앞에 떨어지는 상황이 되어, 빠르게 적응하게 되었슴니다…
- 다시 월급이 들어오기 시작하면서, 집에 있던 구질구질한 옷이며 가구를 싹 내다버렸습니다
- 뭔가를 확 내다버리는 것은 뭔가를 확 지르는 것과 비슷한 수준의 쾌락을 주는 것 같습니다 (?)
- 회사를 다니기 시작하니 운동할 시간이 점점 줄어들까 겁이 나서, 유래없이 비싼 아령을 한 쌍 구입해
매일 조금씩 운동을 함니다
- 12월에는 거의 8년만에 건강검진을 받았는데, 매우 막 산거 치고는 나름 좋게좋게 넘어간 결과지를
받았습니다
- 특히 체지방률이 정상인 수준으로 돌아왔다는 것이 매우 뿌듯함니다
한 해를 보내면서
- 직업이 바뀌고, 몸이 바뀌고, 집의 환경이 바뀌고 하면서 다양한 전환점이 된 해라는 생각이 듭니다
- 이제는 진짜 건강을 생각하지 않으면 안되는 나이가 되었음을 느낍니다
- 특히 요즘 별 이유없이 발가락이 아파서 더 그렇습니다…
- 아, 스스로 많이 회복되었다고 느낀 것중 하나는, 2019년 이후로 일 외에는 코드에 전혀 손을 대고
있지 않았었는데, 요즘에는 다시 슬슬 이것저것 시도를 해보게 되었다는 점?
- 내년에는 더 기운내서 회사 일도 재밌게 하고 취미 프로젝트도 재밌게 하고 싶습니다
Comment count
05 Aug 2019
- 2015년 6월 12일 첫번째 정리
- 2019년 2월 24일 업데이트
- 2019년 8월 5일 업데이트
- 2021년 12월 31일 업데이트
잠 안오고 심심해서 써보는 나의 프로그래밍 로그
- 1990년, 처음으로 MS-Dos와 GW-Basic를 접해봄.
- 1992년, 마이컴이라는 잡지에서 Turbo-C가 짱 좋다는 이야기를 접함.
- 1993년, 마이컴이라는 잡지에 실린 ‘나의 자랑 나의 프로그램’이라는 코너에 실린 C 프로그램을
Turbo-C에서 하나하나 타이핑 해봄. 그러나 C 문법에 대해 1도 모르던 꼬꼬마 린델은 or 연산자
|와 표 그릴때나 쓰던 확장 아스키 코드를 헷갈려 하며 무수한 에러가 나는 장면만 봄. 당연히 영어도
할 줄 몰랐으니 에러를 고치는 방법 따윈 몰라서 포기.
- 1996년인가 쯤, PC통신을 하면서 Game13H라는 라이브러리를 알게되어 화면에 점 하나 찍는걸 해보고
즐거워함. (아마도 내 기억에 13H라는건 320x240x256Color 모드를 의미했던걸로 기억)
- 1997년 겨울, 걸출한 프로그래머 친구를 만나 같이 게임을 만들려고 하면서 프로그래밍에 손놓고 그림만
그리기 시작.
- 2000년, 대학교 입학 후 Visual C 6.0을 접함. void main()으로 시작하여 printf()가 난무하는
콘솔 프로그램이나 숙제로 낼 수 있을 정도로만 겨우겨우 만들수 있게됨.
- 2001년, C의 포인터를 이해하는데 머리를 쥐어짬.
- 2002년, malloc()이라는 함수를 처음 써봄. 그리고 처음으로 ‘잘못된 연산에 의해 프로그램을 종료합니다…‘를
남발하는 숙제용 프로그램 완성.
- 2003년 봄, 파일 구조라는 수업을 들으면서 처음으로 파일 입출력이라는걸 억지로 해봄.
- 역시 2003년 봄, 처음으로 Visual C에 Breakpoint라는 기능이 있다는 걸 알게 됨. 또한 포인터로 선언된 값에
0xCCCCCCCC가 들어있으면 그건 잘못된 거라는 사실도 처음 알게됨 (!).
- 2003년 가을, DB 수업을 들으면서 PHP를 처음 접해봄. 무리하게 프로젝트를 해보았으나 결과는 GG…
- 2004년 봄, 컴퓨터 그래픽스 수업을 들으며 OpenGL을 처음 접해봄. 처음으로 콘솔창이 아닌 그림 그리는 화면을
윈도우에 띄워봄…
- 2004년 1학기 끝나갈 무렵, 학기말 프로젝트에 처음으로 C++ 클래스를 도입해 봄. 그러나 왜 C++을 써야하는지 그
필요성은 전혀 못느끼고 말았음…
- 2004년 1학기 끝나갈 무렵, 학기말 프로젝트 프로그램의 간지와 인터페이스를 위해 Win32 프로그래밍이라는걸 해봄.
- 2005년 석사 입학한 뒤, 다관절 강체 물리 엔진을 C 구조체와 함수로만 만들려고 시도. 그래서 모든 함수의 첫 파라미터가
C 구조체인 상황이 시작되고… 처음으로 C++ 클래스를 써볼껄 하면서 후회함. 아무튼 이걸 만들면서 왜 헤더 파일에는
ifndef 매크로를 붙이는지 이해하기 시작.
- 2005년, OpenGL 삼각형으로 구, 원통 같은걸 직접 그려보기 시작.
- 2005년, 모션 캡처 데이터 BVH 파일을 읽어들여 구와 원통 같은걸로 모션 캡처 애니메이션을 그려보기 시작.
- 2005년, Matlab을 처음 접해보고 행렬 연산의 편의성면에서 C와는 비교할 수 없는 편리함을 느끼고 핥기 시작.
- 2006년, 대학원 수업에서 스케치 기반 인터페이스 수업을 들으면서 나만의 선형대수 라이브러리가 있어야겠단 생각이 들어
모두 inline으로 점철된 선형대수 라이브러리를 만들게 됨. 그 뒤로 대충 7년간 잘 씀…
- 2006년, OpenCV라는걸 처음 접해보고, 이걸로 사진을 읽어서 이것저것 할 수 있다는 것을 알게됨.
- 2007년, Matlab 핥기의 정점에 도달… 모션 캡처 로더와 모션 뷰어, 물리 엔진을 모두 Matlab으로만 구현해보게됨…
- 2006년인가 2007년인가 쯔음… 컴파일러를 만드는 컴파일러가 있단 사실을 구체적으로 알게됨. JavaCC라는 녀석을 프로젝트
때문에 사용해보게 됨. AST를 만들고 이걸 방문하는 Visitor 인터페이스에 대한 개념을 배우게 되면서 자연스럽게 추상 클래스와
구체 클래스간의 차이 및 쓸모를 알게 됨.
- 2006년인가 2007년에 컴파일러 수업을 들을 때, 처음으로 스택 메모리와 힙 메모리의 차이를 알게됨.
- 2006년인가 2007년에 컴파일러 수업을 들을 때, 자바를 줄기차게 쓰면서 “자바의 클래스 변수는 선언한다고 초기화 되지 않는다”는
사실을 처음 알게됨.
- 2008년, 모션 시그널 필터를 구현하면서 처음으로 클래스 상속이란걸 제대로 써보게 됨.
- 2009년 언젠가, 데이터 형이 다르지만 같은 짓을 하는 코드를 대충 6번정도 작성하다가 템플릿이란 거에 손을 대보게 됨.
컴파일하면서 썡고생 경험.
- 2009년인가 2010년인가, OpenGL로 직접 UI 버튼이니 슬라이더니 하는 것들을 구현해 봄.
- 2009년인가 2010년인가, Qt를 처음 접하고 기존에 MFC가 정말 프로그래밍하기 개떡같았다는 걸 뼈저리게 느낌.
- 2010년인가 2011년, 처음으로 svn이니 git이니 하는 형상관리툴의 존재를 알게됨. 그러나 익숙해지는데는 어마어마한
시간이 지난 후…
- 2011년인가 2012년인가 python을 처음으로 접해봄. 쩌는 텍스트 처리 능력을 보고 앞으로 C/C++ 가지로 파서 만들지
말아야겠다고 다짐… (했으나 걸핏하면 어딨는지 못찾겠어서 obj 로더같은거는 그냥 C로 파서 만들기를 수차례 반복)
- 2013년, inline c 함수 기반의 선형대수 라이브러리를 몽땅 클래스 + 연산자 오버로딩으로 개조함. 이때 연산자 오버로딩을
처음 제대로 써봄. 그리고 변수에 붙은 const, 함수에 붙은 const가 어떤 의미인지 제대로 이해하기 시작. 처음으로
‘설계때부터 프로그래머의 사용방식을 적절히 제한하는 것이 왜 유용한지’ 이해하게 됨.
- 2013년, kinect SDK를 써보면서 다시는 쓰고 싶지 않았던 win32 프로그래밍에 또 손을 댐. 그렇지만 이번에는 맥에서도
돌려보고 싶은 욕구를 위해 모든 c파일에 include 를 떡칠하는 우를 범하지 않게 됨.
- 2013년, 안드로이드 프로그래밍을 처음 접함.
- 2013년, 싱글톤이라는 패턴 클래스를 처음 만들어보고, 왜 이렇게 만드는게 편리한지 뼈저리게 깨달음.
- 2014년, 예전에 공부한 클래스 상속 / 템플릿을 이용해 모션 데이터를 처리하는 프로그래밍을 하기 시작.
- 2014년, RFL이라는 혼자 쓸 목적의 라이브러리 프로젝트를 만들면서 문서화를 깔짝대봄
- 2014년, 전신 모션 트래킹을 위한 패턴 매칭 기반의 트래커 문제를 풀어봄. 먼저 CPU로 풀면 얼마나 느린지
확인하고 (초당 3프레임?), 이걸 OpenCL을 이용하여 가속화 하는 작업을 2015년에 진행함
- 2015년, OpenCL 코드를 잘못 놀리면 어떻게 되는지 뼈아프게 느낌. macos에서 OpenCL 코드를 잘못 놀리면
커널 패닉이 일어나는 것을 보고 신기해함 (…)
- 2015년, OpenCL로 kinect fusion 비스무리한 것을 구현하기 위해서 3D TSDF (Truncated Signed Distance
Field)를 만들어내는 알고리즘 구현을 해봄. 이와 동시에 rigid ICP를 구현.
- 2015년, XML 기반 serialization을 하는 낡은 코드를 드러내고 JSONCpp를 붙여봄
- 2015년, Freetype을 이용하여 직접 vector font를 읽어 OpenGL로 렌더링하는 짓을 해봄.
처음에는 Freetype에서 제공하는 rasterizer를 이용해서 texture를 굽고 OpenGL로 이 텍스처를
뿌리다가, 확대하면 생기는 깍두기를 보면서 회의를 느낌…
- 2015년, 그러다가 존경해 마지않는 Blinn 선생이 quadratic Bezier curve의 경우 convex 혹은 concave
영역을 삼각형으로 던지면 채워넣어주는 쉐이더를 만들수 있음을 알려주는 논문을 읽고 이걸로 폰트 렌더러를 직접 구현해봄.
회사에서 이걸 보고 예능 자막 스타일 짤방을 만드는 앱으로 발전시키기 시작, Boodl이라는 앱을 만들게 됨…
- 2015년, 크로스 플랫폼이란걸 해보고 싶어서 같은 C++ 소스 코드를 유지하는 프로젝트를 visual studio용,
xcode용 등등으로 따로 만들어대다가 너무 불편해서 cmake를 건드려보기 시작함
- 2016년, C++ 프로젝트를 js로 컴파일하는 방법이 있다하여 emscripten이라는 녀석을 이용해보기 시작함.
다양한 삽질 끝에 브라우저에서 직접 만든 C++ 렌더러 돌리기 성공.
- 2017년 6월경, 러스트 기본 문서가 2판으로 업데이트 된 것을 보고 읽기 시작하다가 내용이 친절하고 재밌어서
원문 저장소를 포크하고 원저자에게 한국어로 번역하겠다고 이슈를 만들어 알리고 작업 시작. 2018년 10월 즈음에
appendix를 제외한 대부분의 문서 번역이 완료됨.
- 2017년 8월경, 백엔드 및 인프라를 담당하던 친구들이 개인 사정으로 인해 회사를 그만두면서, 급하게 백엔드와
인프라를 땜빵하기 위한 공부를 시작. 급하게 공부한 백엔드 환경은 ruby-on-rails였고, 인프라는 aws였음…
- 2017년 12월경, 프론트엔드를 담당하던 친구가 역시 개인 사정으로 회사를 옮기면서, react-redux-saga
기반의 싱글 페이지 프론트엔드 만드는 법에 대해서도 공부를 하게 됨. 애초에 javascript를 딥하게 써본적도
없는데, 온갖 모던한 문법으로 작성된 redux-saga 코드는 머릿속을 엉키게 만들기에 딱 좋았음
- 2018년, aws의 기본적인 사용 방법을 알게 되면서 node 기반 백엔드를 약간 핥기 시작함.
- 2018년 12월, 회사의 모든 iOS 인력이 사라지면서 (…) swift를 꾸역꾸역 공부하기 시작함.
덕분에 RxSwift를 실컷 만져보게 됨.
- 2019년 초부터, es6 등등의 스펙에 맛들려서 거의 모든 js project 세팅을 babel로 돌리고,
async/await를 일반 콘솔용 프로그램에도 적극적으로 활용하기 시작함.
- 2019년 7월, 개인적으로 사용하려고 만든 binary-to-c-header 툴을 public으로 올렸더니
어떤 외국인 친구가 빌드 방법을 알려달라는 issue를 달아둠… 오픈 소스란 이런 것이구나?
- 2019년 8월, 난생 처음으로 오픈 소스 컨트리뷰팅을 해봄. bgfx라는 renderer에 vulkan backend를
기본 구현하여 pull request. 대략 4박 5일간의 리뷰 핑퐁 후에 머지됨. 나름 뿌듯한 경험.
- 2020년 3월, 영상 처리를 위해 gstreamer를 파보기 시작했으나… g-object라는 녀석이
함수 시그니처만 가지고서는 도저히 lifecycle을 알 수 없으며, 수많은 플러그인 중 상당수를
나몰라라 한다는 사실에 좌절
- 2020년, 결국 ffmpeg으로 갈아타는 대신, 구닥다리 패킷 처리를 어떻게 할 방법이 없을까 하다가
rxcpp를 사용해보기 시작
Comment count
09 Oct 2018
근 1년 넘는 세월동안 업데이트가 없었던 이 블로그. 어떤 일들을 일어났었는지
정리해보자.
러스트 문서 번역
- 저장소 링크는 여기
- 빌드된 html 버전은 여기
- 2017년 6월경, 러스트 기본 문서가 2판으로 업데이트 된 것을 보고 읽기 시작
- Carol과 Steve 두 사람이 쓴 2판 문서는 좀 장황하긴 하지만 너무나 친절하게
쓰여져 있어서 문득 아무 프로그래밍 언어라도 좀 다뤄본 사람이면 이 정도 설명은
충분히 이해할 수 있겠디고 생각함
- 2017년 7월 9일, 원문 저장소를 포크하고 원저자에게 한국어로 번역하겠다고
이슈를 만들어 알리고 작업 시작
- 그 뒤에 페북 러스트 커뮤니티에 도와줄 사람을 모집하여, 대략 5명 정도의
개발자와 함께 틈틈이 번역 시작
- 그리고 지금 2018년 10월초, 드디어 부록을 제외한 거의 모든 챕터의 번역이
끝남…
- 논문이나 좀 읽어댔지, 프로그래밍 기술 서적을 번역해 보는 것은 처음이어서
초반에는 고민하는 시간이 타이핑하는 시간보다 훨씬 길었던거 같음
- 다 번역되고 나면 아마도 새로 나온 2018년 에디션으로 업데이트를 해야
할 거 같음;;;
- 아무튼 러스트는 역시 참 재밌는 언어라고 생각. 러스트로 뭔가 본격 프로젝트
좀 해보고 싶다;
프론트엔드 / 백엔드 겉핥기
- 2017년 8월경, 백엔드 및 인프라를 담당하던 친구들이 개인 사정으로 인해
회사를 그만두면서, 급하게 백엔드와 인프라를 땜빵하기 위한 공부를 시작
- 당시 회사는 ruby-on-rails 기반의 백엔드, 영상 스트림 서버와 이를
보조해주는 go lang 기반의 에이전트 코드를 유지하고 있었음
- 급하게 ruby와 rails를 공부해서 어떻게든 땜빵 시작
- ruby는 보면 볼수록 신기한 언어라고 생각함. 문법의 자유로움 (나쁘게 말하면
근본없음) 의 매력이 해커를 부르는 이유가 아닐까 생각
- 자동 빌드 도구인 fastlane이나,
mac 개발자들의 친구 brew가
ruby를 쓰는 건, ruby의 자유로운 (혹은 근본없는) 문법 덕분에
DSL 만들기가 수월해서가 아닐까라고 생각함
- 여담으로 로드 테스터로 쓰는 jmeter의
시나리오 xml 편집을 힘들어하다가, 이걸 좀 더 편하게 쓸 방법이 없을까
하며 검색하다보니 ruby 코드로 jmeter 시나리오를 작성할 수 있는
ruby-jmeter란
놈도 있단걸 알게 됨… 역시 ruby를 자기네 언어인 마냥 DSL 처럼 쓰고
있는 예;
- 하지만 이걸 검색하다 같이 알게된 python 기반의 로드 테스트 프레임워크
locust를 보고, 이걸 써봤는데 jmeter보다
훨씬 사람같은 행동을 하는 테스트를 할 수 있었음. locust 강추함니다
- 한편 go lang은 접할 기회가 별로 없었고 그 당시까지만 해도 ‘뭐, 고랭은
가비지 콜렉터를 쓴다고? 그러면 JVM 기반인가?’ 라고 오해하고 있다가, 어쨌든
코드를 보면서 나름 재밌는 언어구나 라고 생각함. 하지만 코드 관리가 뭔가
아이튠즈 같다는 느낌을 지울수 없음
- 아무튼 허구헌날 싱글 스레드에 한 프레임당 30ms 이내에 실행이 끝나는 코드나
짜고있던 나한테 시동을 막 걸고 있는 대형 프로젝트의 백엔드 공부는 신기한
경험이었음
- 그러다가 2017년말, 프론트엔드를 담당하던 친구까지 회사를 옮기게 되면서
급하게 react-redux-saga 기반의 싱글 페이지 프론트엔드도 공부를 하게 됨
- 애초에 javascript를 딥하게 써본적도 없는데, 온갖 모던한 문법으로
작성된 redux-saga 코드는 머릿속을 엉키게 만들기에 딱 좋았음
소감
- 2018년 현재, 결국 나는 ruby-rails과 node-express 기반의 백엔드
코드도 볼 수 있게 됐고, 여전히 db와 query에 대해서는 잘 모르지만 postgres
/ mysql 같은 db에 연결하고 뭔가 얻어오는 것도 할 줄 알게 됐고,
react-redux-saga로 된 싱글 페이지 코드도 볼줄 알게 되었고,
간편한 콘솔 툴을 작성할려고 babel 기반으로 모던한 javascript를
쓸 수 있게 되었고, aws에서 ec2를 띄우고 ami를 굽고 elb를 설정하고
s3에 뭔가 올리고 cloudfront에 연결하여 내려받고 등등 기본적인 aws
사용법도 알게 됨
- react-redux-saga의 경험은 mvc도 별로 제대로 경험하지 못한 나한테는
신선한 경험이었음. 처음에는 ui 하나를 집어넣을때도 여러 코드를 건드려야
하는 귀찮음과 쉽지 않은 제어 흐름 떄문에 골치가 아팠지만, 점점 코드를
늘려나가다 보니 로직과 뷰와 컨테이너가 확실히 분리되는 모양새를 보고
왜 사람들이 리덕리덕거리나 알게 되었음
- ruby-on-rails와 node-express를 차례로 접하고나서 풀 스택
웹앱 프레임워크와 가벼운 웹앱 프레임워크를 비교할수도 있게 되었음…
rails는 ‘rails 안에 다 있음!’이 기조인건지 뭐든 찾으면 나와서
뭐든 빨리 만드는 일에는 적합하다는 생각이 들지만, 뭔짓을 하든 간에
가볍고 스무스하게 동작한다는 느낌은 영 들지 않고, 가벼운 프레임워크를
쓰면 ‘나머지는 알아서’의 느낌이 강하지만, 일단 할일은 충분히 잘 된다는
느낌이 듬
- 뭔가 많이 알게 되서 좋긴 하지만, 매우 바빠지고 피곤한건 어쩔수가 없구나;
Comment count
20 Aug 2017
러스트 개발 툴을 찾아서
러스트를 공부하면서 ‘언제쯤 괜찮은 IDE가 함께 나올까’에 대한 바램이 많이 있었더랬습니다.
러스트 1.0이 발표된 지 2년쯤이 지났고,
그동안 언어 내부에 많은 개선이 이루어져 현재 러스트 컴파일러의 버전은 1.19까지 올라갔지만,
왠지 IDE에 대한 공식적인 지원을 나서는 곳이 없었지요. 러스트에 관심을 갖고 공부하는 사람들은
왠지 다 vi 계열 혹은 emacs 계열의 에디터로 불편함없이 프로그래밍하는 고수들인가부다 하고 생각하고는
했습니다.
러스트 개발 환경을 구축해보려고 제가 써본 것들은
- Sublime text
- Visual Studio Community edition + Visual Rust
- JetBrains CLion + Intelli Rust
- Visual Studio Code (이하 VSCode) + Rust extension
정도가 있고, 현재는 VSCode를 계속 이용하고 있습니다.
Sublime text에서 제공하는 강력한 기능은 좋지만 텍스트 에디터로서의 기본적인 한계가 있었고,
Visual Studio는 좋았지만 윈도우 환경 외에서 잘 사용할 수 있을지 모르겠고
(최근 MacOSX용이 나왔지만
아직 안써봐서 어떤지 잘 모르겠습니다), CLion은 크로스 플랫폼이고 기능도 강력하긴 한데 살짝 느린 감이
있었지요 (이쪽도 본격적으로 러스트를 위한 IDEA 계열 에디터용 공식 플러그인을 만든다고 발표했습니다. 이것도 좀더 기다려봐야 할 것 같아요).
VSCode는 순전히 러스트 때문에 설치해본 것인데, Sublime text의 강력한 에디팅 기능을 포함하면서 매우 긴
길이의 텍스트에 대해서도 빠른 처리를 하는걸 보고 - 대략 15만줄 짜리도 금방 로딩되더군요 - 일반 텍스트
에디팅에서도 최근 가장 애용하는 에디터가 되었습니다. 심지어 공짜입니다; 저는 서브라임 정품도 구매해서 쓰고
있었는데!
VSCode + RLS
VSCode에서 사용할 수 있는 러스트용 extension 중에는 Rusty code라는 녀석과
Rust라는 녀석 두 가지가 있었는데, 이 둘은 racer와 rustfmt, rustsrc를 사용하여 코드 컴플리션과
리포맷팅 기능을 제공하고, 각종 cargo 관련 기능을 추가해 줍니다. 종종 사용하다가 보면 좌측 하단의
status bar에 racer가 crash되었다고 나오면, 재빨리 VSCode를 reload 해주면서 썼습니다 (…)
그러던 중, 최근 RLS 개발진중 한 분이 블로그에 홍보글을 썼더군요.
RLS는 Rust Language Server라고 하는 것으로, 백그라운드로 실행되면서 IDE나 에디터, 혹은 다른
툴에게 개발하고 있는 러스트 프로그램에 대한 정보를 제공하는 서버입니다. 글을 훑어보시면 여러가지
매력적인 기능이 있습니다. 마우스를 심볼 위에 호버링 시키면 해당 심볼의 선언부분이 나온다던지,
go to definition이라던지요.
이전에도 이 RLS는 계속 개발되고 있었고, VSCode extension 중에서 Rust를 이용하면
실험적인 feature로 RLS를 이용해볼지, 아니면 legacy 방식을 쓸지 결정하라고 물어봤었지요.
이때 한번 RLS를 써보려다가… 너무 버그가 심해서 (RLS 서버가 너무 픽픽 죽어서) 쓰질 못했었어요.
근데 블로그로 홍보를 하는 상황이 되었고, 심지어 이를 위해 직접 extension도 만들었다니까,
한번 써봐야되지 않겠습니까? 그래서 써봤습니다.
결론은, RLS analysis는 좀 오래 걸리긴 하지만, 매우 만족스럽다는 것이었습니다!
이제부터 어떤식으로 VSCode + RLS를 이용하여 개발 환경을 구축했는지에 대한 내용을
공유할까 합니다.
개발 환경 구축
Rustup 설치
- 상당히 많은 언어들이 그렇듯, 러스트도 버전 매니저가 있습니다. rustup이라고 부르는데요.
홈페이지에 가면 설치 방법이 한줄 나와 있습니다.
curl https://sh.rustup.rs -sSf | sh
- 혹시 윈도우 유저시라면, 홈페이지 하단에 other installation options라고 되어있는
링크를 타고 들어가셔서 설치 옵션을 보세요.
- rustup 없이도 러스트를 설치할 수 있지만, rustup을 이용하면 개발에 필요한 다양한
콤포넌트들을 쉽게 설치할 수 있으니, 이 방법을 이용하는 것을 강력히 추천합니다.
- rustup으로 설치를 마치셨다면, 기본적으로 현재 이용할 수 있는 stable 버전의 rustc
컴파일러 및 cargo 패키지 매니저, 그리고 러스트 문서와 표준 라이브러리가 설치되는걸
확인할 수 있습니다 (작성일 기준 1.19버전입니다).
- 덧: 만일 윈도우 유저라면, rustup을 이용하여 설치할 수 있는 환경이 두 가지입니다:
visual studio 기반 혹은 mingw 기반이죠. 원하시는데로 설치하시면 됩니다.
제 경험상으로는 visual studio community edition을 설치하고 visual studio
기반으로 설치하는 쪽이 디버깅 환경 등을 이용할때 좀 더 좋았습니다.

VSCode 설치
VSCode 내에 extension 설치
- VSCode에서 go to file (
ctrl + p
)를 열어서 ext install rust
이라고
입력하면, 검색어에 걸리는 몇 개의 extension이 좌측 사이드 탭에 뜹니다.
- 이중에서
Rust (rls)
라고 되어있는 extension을 설치하고 reload 하세요.

- 팁: command pallete (
ctrl + shift + p
)를 눌러서 reload를 검색하면
매우 빠르게 에디터 리로딩을 할 수 있어요.
러스트 프로젝트 만들고 열기
- 러스트 프로젝트를 열지 않고는 Rust (rls)의 세부 환경 세팅이 안될테니, hello world
프로젝트라도 하나 만들어봅시다.
- 터미널을 열고 다음을 입력하여 프로젝트를 만들고 VSCode로 엽니다:
$ cargo new hello_world --bin
$ code ./hello_world
rustup component 설치
- 프로젝트를 성공적으로 여셨다면, VSCode에 설치된 Rust(RLS) extension이 자동적으로 현재 상태를 감지하고,
필요한 rustup component를 설치하려고 할 것입니다.
- 먼저 설치하라고 하는 것은 nightly toolchain 입니다. 현재 RLS가 nightly 버젼에서만 돌아가기 때문이지요.
yes를 눌러서 설치를 진행합니다.

- 설치가 끝나고 나면, 다음으로 RLS를 설치하려고 할 것입니다. 역시 yes를 눌러서 설치를 진행합니다.

- 설치가 다 끝나고 나면, 기본적인 에디팅 환경 설정은 모두 끝난 것입니다. 좌측 하단에
RLS analysis: done
이라고 뜨면 동작을 하고 있다는 얘기입니다.

- 간략한 예제를 작성하면서 테스트 해보세요. 심볼에 마우스를 갖다대면 해당 심볼의 선언 부분이 나오고,
ctrl + LMB
을 누르면 해당 심볼의 정의 부분으로 이동합니다 (go to definition).
- 만약에 생각만큼 auto completion이나 go to definition 등이 잘 동작하지 않는다는 생각이 든다면,
VSCode를 reload 시켜보세요 (…) 종종 이게 제일 좋은 해결책일 경우가 있습니다. 다행스러운 것은 VSCode
reload가 정말 빠르게 실행된다는 것이죠 (…)
- 덧: 현재 설치된 rustup component를 보시려면 터미널에서
rustup component list
를 실행하여 볼 수 있습니다.
다만, 디폴트로 설정되어 있는 toolchain에 대한 component만 보일 것이므로, rustup default nightly
를 먼저
실행하셔야 제대로 된 리스트를 보실 수 있을 겁니다.
디버깅 환경 구축
Rust(rls) extension에서는 아직 공식적으로 디버거 연결 제공을 해주고 있지 않습니다. 대신,
gdb와 (windows 환경이라면 visual studio debugger) native debugger extension을
이용하면 대충 비스무리한 환경을 만들 수 있습니다.
gdb 설치
- 맥 유저시라면, gdb 대신 lldb가 기본 디버거라서 gdb가 따로 설치되어 있지 않을 수 있습니다.
brew를 이용해서 gdb를 설치해두시기 바랍니다.
- 현재의 macosx 버전에서는 gdb에 코드사인이 되어 있어야 정상적으로 동작합니다.
여기를 참고하셔서 gdb에
코드사인 작업을 하셔야 합니다.
- 또한 맥 유저라면 brew로 gdb을 설치한 뒤에 아래 메세지가 안내됩니다. 그대로 따라하셔서
gdb 기본 설정을 해줘야 합니다:
$ echo "set startup-with-shell off" >> ~/.gdbinit
VSCode native debug extension 설치
- 이 gdb를 VSCode에 연결해 주려면 Native Debug extension이 필요합니다.
ctrl + p
를 눌러서 명령창을 열고 ext install native-debug
를 입력하시면
좌측 사이드 탭에 Native Debug를 검색하실 수 있습니다. 이걸 인스톨합니다.

- reload를 하고 나서 이제 왼쪽 사이드 툴바에서 디버그 탭을 선택한 다음, 왼쪽 사이드 탭 상단에 있는
디버그 시작 아이콘을 누르면 새로운 디버깅 환경 세팅을 위한 메뉴가 뜹니다. 여기서 gdb를 선택해줍니다.

- 디버깅 환경을 선택하면 프로젝트 루트 디렉토리 밑에 있는
.vscode
라는 디렉토리 안에 launch.json
이라는
파일이 생기고, 여기에 자세한 디버깅 환경을 세팅할 수 있습니다. 우리가 해줘야 할 일은 target
에 디버깅용
실행 파일 이름을 적어주는 것입니다.
- 여기서 주의해야할 사항은, 현재 맥에서는 gdb을 이용한 디버깅이 왠지 최종 파일인
./target/debug/{project_name}
을 설정해주면 심볼 데이터를 잘 읽어들이지 못한다는 점입니다.
그럴때는 ./target/debug/dep/{project_name}_{뭔가 긴 해쉬코드}
파일을 찾아서 이 이름을 적어주세요.
혹시 같은 이름의 파일이 여러 개라면, cargo clean
후 cargo build
를 다시 실행시키면 현재 이용하고 있는
하나만 나올 것입니다:
{
"name": "Debug",
"type": "gdb",
"request": "launch",
"target": "./target/debug/deps/hello_world-51d02c6c0c01c7ba",
"cwd": "${workspaceRoot}"
}
- 이제 코드에 브레이크 포인트를 설정하고 디버그 모드를 실행시키면, 어느정도 동작하는 모습을 확인할 수 있습니다.
로컬 스코프의 변수를 자동으로 찾아서 채워주거나 하지는 않지만, 커서를 심볼에 갖다대면 현재 어떤 값을 갖고 있는지
확인할 수 있고, gdb에 익숙하신 분들이라면 debug console을 그대로 이용할 수도 있습니다:

- 덧: 그냥 gdb를 이용하여 심볼 안의 데이터를 보면 타입 이름이 전부 적혀있어서 엄청 지저분합니다;
다행히 이걸 조금이라도 더 깔끔하게 보여줄 방법이 있습니다.
~/.gdbinit
을 열어서 아래처럼 작성하고
저장하시면, gdb 상에서 좀더 깔끔하게 정리된 (그러나 여전히 json 비스무리해서 보기 그렇게 깔끔하진
않은) 데이터를 볼 수 있습니다! 아래에 ${여러분의_홈디렉토리}
부분과
nightly-x86_64-apple-darwin
부분 등 디렉토리 설정과 관련된 부분은 여러분의 상황에
맞게 고치셔야 합니다!
set startup-with-shell off
python
print "--Loading Rust pretty-printers--"
sys.path.insert(0, "${여러분의_홈디렉토리}/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/etc")
import gdb_rust_pretty_printing
gdb_rust_pretty_printing.register_printers(gdb)
end
정리
종종 VSCode + Rust(RLS)가 말을 안듣고 좀 불안하긴 하지만, 제가 이제까지 본 중 가장 쾌적한 러스트 개발 환경이
아닐까 싶습니다. VSCode랑 Rust(RLS)가 말을 안들으면 재빨리 reload하면 되고(…) 타이핑 할 때 느려지는 문제는
거의 없으며, Sublime text와 같은 강력한 멀티 커서 기능, 어느 정도 수준 이상으로 동작하는 코드 리팩터링 기능,
리포매팅 기능 등 필요한 건 어느정도 다 갖춘 상태입니다. 거기에 예쁘게 나오진 않지만 gdb를 이용해서 디버거 연결도
됩니다!
추후에 공식으로 나올 IntelliJ Rust도 기대하는 중입니다 후후후
Comment count