<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Visual Studio</title>
    <link>https://emacser.tistory.com/</link>
    <description>snailgod86@tukorea.ac.kr</description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 05:43:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>emacser</managingEditor>
    <image>
      <title>Visual Studio</title>
      <url>https://tistory1.daumcdn.net/tistory/5048453/attach/b97bf5f6a03944819dfeec2030939cad</url>
      <link>https://emacser.tistory.com</link>
    </image>
    <item>
      <title>Intrusive Linked List vs Extrusive Linked List</title>
      <link>https://emacser.tistory.com/88</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Intrusive Linked List&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원소 자체가 노드의 역할을 겸한다. 아래 이미지처럼 원소가 되는 구조체 타입 자체가 다음 노드에 대한 포인터를 멤버로 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Extrusive Linked List보다 추가/삭제에 성능적 이점이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YJp8M/dJMcadHtht8/lHMy9D9eHQDak3HSeOtdr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YJp8M/dJMcadHtht8/lHMy9D9eHQDak3HSeOtdr0/img.png&quot; data-alt=&quot;https://www.data-structures-in-practice.com/intrusive-linked-lists/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YJp8M/dJMcadHtht8/lHMy9D9eHQDak3HSeOtdr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYJp8M%2FdJMcadHtht8%2FlHMy9D9eHQDak3HSeOtdr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;235&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.data-structures-in-practice.com/intrusive-linked-lists/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 언리얼 엔진에서는 TIntrusiveLinkedList, TIntrusiveDoubleLinkedList로 구현되어 있다. TIntrusiveLinkedList를 상속받은 구조체/클래스를 작성하여 사용할 수 있다. TIntrusiveDoubleLinkedList는 TIntrusiveDoubleLinkedListNode를 상속받은 구조체/클래스 작성 후, TIntrusiveDoubleLinkedList&amp;lt;FMyClass&amp;gt; List; 처럼 선언하여 사용한다. TInstrusiveLinkedList는 FOpenGLView, FMetalResourceViewBase 등에서 사용되고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Extrusive Linked List&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원소 외부에 이전/다음 노드에 대한 포인터를 저장하는 방식이다. Instrusive Linked List와 달리 데이터 구조를 변경하지 않고, TLinkedList&amp;lt;FMyClass&amp;gt; List; 와 같이 바로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Intrusive Linked List보다 사용이 편리하고 별도 구조체/클래스를 정의하지 않아도 되는 장점이 있으나, 원소를 감싸는 노드에 대한 추가 메모리를 할당해야 하고, 다음 노드에 대한 포인터가 원소가 위치한 메모리 영역과 떨어져 있는 노드 메모리 영역에 존재하므로 캐시 지역성이 떨어진다.&lt;/p&gt;</description>
      <category>Computer Engineering</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/88</guid>
      <comments>https://emacser.tistory.com/88#entry88comment</comments>
      <pubDate>Tue, 13 Jan 2026 11:59:50 +0900</pubDate>
    </item>
    <item>
      <title>프로그래밍 하면서 자주 할법한 실수 3가지</title>
      <link>https://emacser.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;1. for문에서 continue 대신 return 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1749027032095&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (int32 i = 0; i &amp;lt; NumElements; ++i)
{
    if (Elements[i] &amp;gt; 0)
    {
        // continue로 처리해야할 로직
        return;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Guard Clause 패턴에서 !나 == false 누락&lt;/p&gt;
&lt;pre id=&quot;code_1749027172324&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (IsValid(Object)) /* !IsValid(Object) 혹은 IsValid(Object) == false 이어야 할 로직 */
{
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Array 등의 컨테이너 타입을 리턴 타입, 파라미터로 사용할 때 &amp;amp; (Refenrece) 누락&lt;/p&gt;
&lt;pre id=&quot;code_1749027452620&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const TArray&amp;lt;FMyElement&amp;gt; GetMyElements() const { return MyElements; }
// 리턴 타입에 &quot;&amp;amp;&quot; 누락

void ProcessElement(const TArray&amp;lt;FMyElement&amp;gt; InElements);
// 함수 파라미터에 &quot;&amp;amp;&quot; 누락&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Engineering</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/87</guid>
      <comments>https://emacser.tistory.com/87#entry87comment</comments>
      <pubDate>Wed, 4 Jun 2025 18:03:23 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] DX12의 CreateCommittedResource() 와 같은 동작을 Vulkan에서 하려면?</title>
      <link>https://emacser.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;많은 DX12 예제에서 버퍼 등의 리소스를 생성할 때 ID3D12Device::CreateCommittedResource() 함수를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734943769848&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ID3D12Device::CreateCommittedResource(d3d12.h) - Win32 apps&quot; data-og-description=&quot;힙이 전체 리소스를 포함할 만큼 크고 리소스가 힙에 매핑되도록 리소스와 암시적 힙을 모두 만듭니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uD8JI/hyXSrYDXSP/eksZybKLeFfUUsoC7iXl01/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommittedresource&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uD8JI/hyXSrYDXSP/eksZybKLeFfUUsoC7iXl01/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ID3D12Device::CreateCommittedResource(d3d12.h) - Win32 apps&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;힙이 전체 리소스를 포함할 만큼 크고 리소스가 힙에 매핑되도록 리소스와 암시적 힙을 모두 만듭니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS 문서를 보면, ID3D12Device::CreateCommittedResource() 함수는 생성될 리소스를 포함하는 크기의 암시적 힙(Implicit Heap)과 리소스를 생성한다고 되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vulkan을 배웠을 때는 이렇게 메모리와 리소스 할당을 같이 해주는 함수의 도움 없이 직접 아래의 과정을 거쳤어야 했기 때문에, 이런것들이 모여서 DX12와 Vulkan의 난이도 차이를 만드는 구나... 라는 생각을 했었다. (물론 Vulkan의 높은 난이도의 주범은 파편화와 스케줄링이라고 알고 있다.)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;(Buffer Resource 생성시) &lt;br /&gt;1. vkCreateBuffer() 함수 호출로 버퍼 객체를 생성한다.&lt;br /&gt;2. vkGetMemoryRequirements() 함수로 Buffer Resource를 담기 위해 갖춰야 할 메모리의 정보를 가져온다.&lt;br /&gt;3. vkAllocateMemory로 Requirements로부터 가져온 정보(Size, Memory Type 등)를 바탕으로 메모리를 할당한다.&lt;br /&gt;4. vkBindMemory로 버퍼 리소스를 메모리에 바인딩한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 DX12에서는 메모리 할당과 리소스 생성을 별개로 해줄 수 없는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙을 미리 만들고 해당 힙에 리소스를 배치시키고 싶을 때는 ID3D12Device::CreatePlacedResource() 함수를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 CreateHeap() 함수로 먼저 Heap을 만들고 생성한 힙 객체의 포인터를 ID3D12Device::CreatePlacedResource() 함수에 넘겨주면 된다.&lt;/p&gt;</description>
      <category>Graphics</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/85</guid>
      <comments>https://emacser.tistory.com/85#entry85comment</comments>
      <pubDate>Mon, 23 Dec 2024 17:27:37 +0900</pubDate>
    </item>
    <item>
      <title>[DX12] D3D12Multithreading 예제 분석</title>
      <link>https://emacser.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Microsoft에서 제공하는 DirectX-Graphics-Samples에서 어떻게 Multithreaded Rendering을 구현했는지 분석해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로젝트를 빌드해서 씬이 어떻게 구성되어 있는지 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;1074&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYqgqM/btsLtaCaKFm/hzZDgjQzpVZfKMnkYxHQKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYqgqM/btsLtaCaKFm/hzZDgjQzpVZfKMnkYxHQKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYqgqM/btsLtaCaKFm/hzZDgjQzpVZfKMnkYxHQKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYqgqM%2FbtsLtaCaKFm%2FhzZDgjQzpVZfKMnkYxHQKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1915&quot; height=&quot;1074&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;1074&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방이 하나 있고 그 안에 여러가지의 오브젝트들이 배치되어 있으며, 가운데 문어를 담은 수조(?)가 있는 형태의 씬이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;광원은 여러개이고 수조가 위치한 중심을 기준으로 원을 그리며 돌고 있는 것으로 추정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상하좌우 키를 눌러 카메라를 수조를 중심으로 회전시킬 수 있고, Space로 광원 회전을 Play/Pause 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 흐름은 DXSample을 상속받은 클래스인 D3D12Multithreading 클래스에서 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급했던 광원부터 어떻게 구성되어있는지 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D3D12Multithreading::LoadAssets() 함수에서 광원에 대한 정보를 초기화하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2p4SS/btsLrZI1wqr/7JYwfNuuYyrPYEFm3zMoc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2p4SS/btsLrZI1wqr/7JYwfNuuYyrPYEFm3zMoc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2p4SS/btsLrZI1wqr/7JYwfNuuYyrPYEFm3zMoc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2p4SS%2FbtsLrZI1wqr%2F7JYwfNuuYyrPYEFm3zMoc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1210&quot; height=&quot;494&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NumLights는 stdafx.h에 3으로 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 모든 광원이 같은 값을 가지고 시작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;633&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxci9t/btsLsZ2zdsX/FDfzkVrFHDWdd5imKHhWk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxci9t/btsLsZ2zdsX/FDfzkVrFHDWdd5imKHhWk0/img.png&quot; data-alt=&quot;한눈에 보기 편하도록 줄바꿈을 살짝 변경했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxci9t/btsLsZ2zdsX/FDfzkVrFHDWdd5imKHhWk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxci9t%2FbtsLsZ2zdsX%2FFDfzkVrFHDWdd5imKHhWk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1483&quot; height=&quot;633&quot; data-origin-width=&quot;1483&quot; data-origin-height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한눈에 보기 편하도록 줄바꿈을 살짝 변경했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 D3D12Multithreading::OnUpdate() 함수에서 각 광원마다 위치가 달라지게 되는데, index가 홀수인 광원과 짝수인 광원이 서로 다른 방향으로 회전한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Y축을 기준으로 회전하여 Eye, At, Up 벡터를 계산하고, View 행렬을 다시 만들어 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 예제를 분석하는데 있어 중요한 부분이 아니므로 빠르게 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방과 수조, 상자들을 비롯한 오브젝트들은 어떤 방식을 사용해 배치했을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 씬 배치를 분석하기 전에 애셋들을 어떻게 로드하는지부터 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 D3D12Multithreading::LoadAssets() 함수로 돌아간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5p0i1/btsLurRMhkb/XGqAV4uey7KeI7PzZrVY50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5p0i1/btsLurRMhkb/XGqAV4uey7KeI7PzZrVY50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5p0i1/btsLurRMhkb/XGqAV4uey7KeI7PzZrVY50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5p0i1%2FbtsLurRMhkb%2FXGqAV4uey7KeI7PzZrVY50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1445&quot; height=&quot;105&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;씬 구성을 위한 애셋들을 SampleAssets::DataFileName 경로에서 불러오고 있다. 이 경로는 SquidRoom.h에서 L&quot;SquidRoom.bin&quot;으로 정의하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReadDataFromFile은 단순히 파일을 읽어서 Byte Array로 저장할 뿐이므로 pAssetData가 어떻게 쓰이는지만 추적하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적인 씬 로드에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vertex Buffer, Index Buffer, Texture를 순서대로 생성하고 있는데, 먼저 Vertex Buffer를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1935&quot; data-origin-height=&quot;763&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xiwgL/btsLtHt70TN/tRkAfpREhkmGhkdk7Cyyb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xiwgL/btsLtHt70TN/tRkAfpREhkmGhkdk7Cyyb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xiwgL/btsLtHt70TN/tRkAfpREhkmGhkdk7Cyyb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxiwgL%2FbtsLtHt70TN%2FtRkAfpREhkmGhkdk7Cyyb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1935&quot; height=&quot;763&quot; data-origin-width=&quot;1935&quot; data-origin-height=&quot;763&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pAssetData에 미리 정의된 Offset 만큼 더하여 버텍스 데이터가 위치한 주소를 구하는 로직으로 보아, 각 애셋마다 Offset을 미리 특정 헤더에 정의해놓고 애셋 데이터의 시작 포인터에 더하여 애셋이 실제로 위치한 주소를 구하여 사용하는 방식임을 알 수 있다. 실제로 애셋 로드에 사용되는 Offset들이 SquidRoom.h 파일에 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의문인 점은 실제로 그려지는 메시가 여러개임에도 Vertex Buffer는 단 하나만 생성하고 있는 것인데, (바로 다음에 생성하는 Index Buffer도 단 하나만 생성하고 있다) 실제로 Draw Call이 일어나는 D3D12Multithreading::WorkerThread() 함수를 살펴보면 이유를 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dF2JD0/btsLt4JsrUZ/PiY36weqedkH5Q7JIgJ9N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dF2JD0/btsLt4JsrUZ/PiY36weqedkH5Q7JIgJ9N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dF2JD0/btsLt4JsrUZ/PiY36weqedkH5Q7JIgJ9N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdF2JD0%2FbtsLt4JsrUZ%2FPiY36weqedkH5Q7JIgJ9N0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1754&quot; height=&quot;467&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene pass의 코드를 보면, (Shadow pass도 마찬가지이므로 어느쪽을 봐도 상관없다) InstanceCount가 1이므로 인스턴싱은 하지 않고 있고, SampleAssets::Draw 배열을 순회하며 DrawIndexInstanced() 함수를 호출하고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 사용되는 SampleAssets::Draw 배열은 SquidRoom.h에 아래와 같이 정의되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V26UY/btsLu3iE5Gs/fAkgnOIyDfkf6jK74XuBE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V26UY/btsLu3iE5Gs/fAkgnOIyDfkf6jK74XuBE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V26UY/btsLu3iE5Gs/fAkgnOIyDfkf6jK74XuBE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV26UY%2FbtsLu3iE5Gs%2FfAkgnOIyDfkf6jK74XuBE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1219&quot; height=&quot;731&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째부터 각각 DiffuseTextureIndex, NormalTextureIndex, SpecularTextureIndex, IndexStart, IndexCount, VertexBase이다. 오브젝트마다 서로 다른 Vertex Data, Index Data, Texture를 사용하여 렌더링할 수 있도록 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;씬을 잘 살펴보면 같은 종류의 메시가 여러개 배치되어 있는 것을 볼 수 있는데, RenderDoc으로 뜯어보면 버텍스 데이터 자체가 같은 종류의 메시끼리 묶어져 있는 형태로 되어있음을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vks6W/btsLupfnR0g/VNJMh4xbKQD8GPLnTKkRl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vks6W/btsLupfnR0g/VNJMh4xbKQD8GPLnTKkRl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vks6W/btsLupfnR0g/VNJMh4xbKQD8GPLnTKkRl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvks6W%2FbtsLupfnR0g%2FVNJMh4xbKQD8GPLnTKkRl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;313&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/78VFE/btsLuIlvdd0/rM46ngEB5FVnc8pOKEnY1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/78VFE/btsLuIlvdd0/rM46ngEB5FVnc8pOKEnY1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/78VFE/btsLuIlvdd0/rM46ngEB5FVnc8pOKEnY1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F78VFE%2FbtsLuIlvdd0%2FrM46ngEB5FVnc8pOKEnY1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;425&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 RenderDoc의 Mesh Viewer로 본 실제 메시의 형태이고, 아래 이미지가 실제로 렌더 타겟에 그려진 이미지이이다. 빨간색 동그라미로 표시된 기둥들이 해당 드로우 콜에서 그려진 메시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴싱을 사용하지 않고 하나의 드로우 콜로 그리기 위해 이러한 방법을 채택한 것으로 보인다. 멀티스레딩에 대한 예제이므로 멀티스레딩과 관련 없는 기능 구현은 최소화하여 학습자가 본 샘플의 주제에 집중할 수 있도록 돕기 위함일까...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multithreaded Rendering에 대해 설명하기 전에 마지막으로 FrameResource 클래스에 대해 미리 설명하는 게 좋을것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2023&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcFt2Y/btsLw67oHE1/5HFwPKsOK6cXBXqEZ0vx4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcFt2Y/btsLw67oHE1/5HFwPKsOK6cXBXqEZ0vx4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcFt2Y/btsLw67oHE1/5HFwPKsOK6cXBXqEZ0vx4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcFt2Y%2FbtsLw67oHE1%2F5HFwPKsOK6cXBXqEZ0vx4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2023&quot; height=&quot;998&quot; data-origin-width=&quot;2023&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 처리중인 프레임이 여러개인 경우 렌더링에 필요한 정보를 프레임마다 가지고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FrameResource는 한 프레임을 렌더링하는데 필요한 모든 객체들을 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 객체들이 멤버로 있지만 핵심적으로 알아야 할것은 m_batchSubmit의 크기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NumContexts * 2 + CommandListCount 라고 되어 있는데, NumContexts * 2는 각 쓰레드마다 Shadow Pass, Scene Pass를 위한 Command List 2개씩 가지고 있기 때문이고, CommandListCount는 stdafx.h에 3으로 정의되어 있는데, 메인 쓰레드에서 사용하는 Pre, Mid, Post Command List를 위한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Command List가 m_batchSubmit 배열에 어떤 순서로 배치되어 있는지도 미리 알아두면 분석에 도움이 될것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1780&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1HGj/btsLvUmCD3P/kXHgnoVA9DwmkWyo1nUEg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1HGj/btsLvUmCD3P/kXHgnoVA9DwmkWyo1nUEg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1HGj/btsLvUmCD3P/kXHgnoVA9DwmkWyo1nUEg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1HGj%2FbtsLvUmCD3P%2FkXHgnoVA9DwmkWyo1nUEg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1780&quot; height=&quot;182&quot; data-origin-width=&quot;1780&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FrameResource의 생성자의 맨 마지막 부분이다. 메인 쓰레드에서 사용하는 Pre, Mid Command List가 순서대로 들어가고, 그다음 각 쓰레드의 Shadow Pass에서 사용할 Command List, Scene Pass에서 사용할 Command List를 넣은 뒤, 맨 마지막에 Post Command List가 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 Multithreaded Rendering을 어떻게 구현했는지에 대해 분석해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레딩 관련 로직은 SINGLETHREADED 매크로가 꺼져 있어야 동작하고, stdafx.h에 FALSE로 정의되어 있기 때문에 아무것도 수정하지 않았다면 기본적으로 멀티 스레드로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 D3D12Multithreading::LoadContexts() 함수에서 출발한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NumContexts는 NumLights와 마찬가지로 stdafx.h에 3으로 정의되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2gHMa/btsLxGaL4wO/KnsBhKK8vSiqK9YsXDPLCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2gHMa/btsLxGaL4wO/KnsBhKK8vSiqK9YsXDPLCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2gHMa/btsLxGaL4wO/KnsBhKK8vSiqK9YsXDPLCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2gHMa%2FbtsLxGaL4wO%2FKnsBhKK8vSiqK9YsXDPLCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;556&quot; height=&quot;564&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화 로직은 쓰레드 동기화에 사용할 이벤트를 생성하는 것에서부터 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SetEvent() 함수로 이벤트를 발생시키고, WaitForSingleEvent(), WaitForMultipleObjects() 함수로 이벤트 발생을 기다린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링에 사용할 쓰레드를 만들어주는데 __beginthreadex의 세번째 인자인 StartAddress(쓰레드에서 main의 역할을 할 함수의 주소 라고 생각하면 편하다) 에 threadwrapper::thunk() 함수가 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvYHwg/btsLtbvFXP9/z9y0YddDArgI7vkYAC5RRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvYHwg/btsLtbvFXP9/z9y0YddDArgI7vkYAC5RRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvYHwg/btsLtbvFXP9/z9y0YddDArgI7vkYAC5RRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvYHwg%2FbtsLtbvFXP9%2Fz9y0YddDArgI7vkYAC5RRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;147&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thunk 함수 내에서 호출하는 D3D12Multithreading::WorkerThread() 함수가 Multithreaded Rendering의 핵심적인 역할을 하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 메인 스레드에서 호출되는 D3D12Multithreading::OnRender() 함수로 가보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPFz2w/btsLwMiponY/ExTlXoZ5lQEWCYARkqZlf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPFz2w/btsLwMiponY/ExTlXoZ5lQEWCYARkqZlf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPFz2w/btsLwMiponY/ExTlXoZ5lQEWCYARkqZlf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPFz2w%2FbtsLwMiponY%2FExTlXoZ5lQEWCYARkqZlf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;325&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프레임을 렌더링하기 시작하면 맨 처음 BeginFrame() 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btGiRD/btsLx5uqiJS/9pVVQlOGhGHSKtT2NNpra0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btGiRD/btsLx5uqiJS/9pVVQlOGhGHSKtT2NNpra0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btGiRD/btsLx5uqiJS/9pVVQlOGhGHSKtT2NNpra0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtGiRD%2FbtsLx5uqiJS%2F9pVVQlOGhGHSKtT2NNpra0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1293&quot; height=&quot;343&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID3D12GraphicsCommandList::ResourceBarrier() 함수 호출 부분이 너무 길어 추가로 줄바꿈을 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FrameResource::Init() 함수에서는 이전에 사용했던 렌더링 리소스들을 초기화하는데, 중요하지 않은 부분이라 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 프레임에 대한 모든 렌더링 과정이 끝났을 때 Render Target Resource의 상태가 PRESENT로 변경되었을 것이므로 다시 RENDER_TARGET으로 돌려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Target View, Depth Stencil View를 초기화해주고 Command List를 닫는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uFLzv/btsLwGP6xtJ/QsKMhOGXo9lLAV8d8Ogdw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uFLzv/btsLwGP6xtJ/QsKMhOGXo9lLAV8d8Ogdw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uFLzv/btsLwGP6xtJ/QsKMhOGXo9lLAV8d8Ogdw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuFLzv%2FbtsLwGP6xtJ%2FQsKMhOGXo9lLAV8d8Ogdw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;115&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 BeginRenderFrame 이벤트를 활성화한다. 이 이벤트는 WorkerThread() 함수의 시작 부분에서 Wait하는데, 잠깐 제쳐두고 MidFrame(), EndFrame() 함수에 대해서 먼저 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEFxqj/btsLwJTxyLt/he32ha4SJouCEyrq4PlNOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEFxqj/btsLwJTxyLt/he32ha4SJouCEyrq4PlNOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEFxqj/btsLwJTxyLt/he32ha4SJouCEyrq4PlNOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEFxqj%2FbtsLwJTxyLt%2Fhe32ha4SJouCEyrq4PlNOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;134&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MidFrame() 함수에서는 FrameResource::SwapBarriers() 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdYKru/btsLxiOJ926/6tEarNImoKEiKUZVplHbF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdYKru/btsLxiOJ926/6tEarNImoKEiKUZVplHbF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdYKru/btsLxiOJ926/6tEarNImoKEiKUZVplHbF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdYKru%2FbtsLxiOJ926%2F6tEarNImoKEiKUZVplHbF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;163&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FrameResource::SwapBarriers() 함수에서는 Shadow Texture의 상태를 전환한다. Shadow Pass에서는 Depth Buffer 용도로 사용했지만 Scene Pass에서는 픽셀 쉐이더에서 쓰이는 텍스쳐 리소스여야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwapBarriers() 함수를 호출하고 BeginFrame()과 마찬가지로 Command List를 닫는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Pn0E/btsLwGvH2qQ/XMeqIoRfRGpNABedluXbO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Pn0E/btsLwGvH2qQ/XMeqIoRfRGpNABedluXbO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Pn0E/btsLwGvH2qQ/XMeqIoRfRGpNABedluXbO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Pn0E%2FbtsLwGvH2qQ%2FXMeqIoRfRGpNABedluXbO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;248&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EndFrame() 함수는 사실 두번째 Command List 제출(ID3D12CommandQueue::ExecuteCommandLists() 함수 호출) 이전이라면 언제 호출해도 상관 없는데, 해당 예제에서는 MidFrame() 함수 바로 뒤에 호출하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene Pass까지 모두 완료된 후 실행되는 Command List를 만든다. FrameResource::Finish() 함수에서는 Shadow Texture의 상태를 PIXEL_SHADER_RESOURCE에서 DEPTH_WRITE로 전환하고, Render Target의 상태를 PRESENT로 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;22&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZDc8G/btsLxrxYIU2/KZGA5RwILXyKMie6SAxOtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZDc8G/btsLxrxYIU2/KZGA5RwILXyKMie6SAxOtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZDc8G/btsLxrxYIU2/KZGA5RwILXyKMie6SAxOtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZDc8G%2FbtsLxrxYIU2%2FKZGA5RwILXyKMie6SAxOtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;22&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;22&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Shadow Pass를 위한 작업들을 완료하고 Scene Pass로 넘어갈 준비가 되었으므로 모든 쓰레드가 Shadow Pass에서 사용할 Command List를 만들 때까지 기다린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qCfWB/btsLxDkJzsB/ftKGzyjvH4q9nJY69TJuLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qCfWB/btsLxDkJzsB/ftKGzyjvH4q9nJY69TJuLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qCfWB/btsLxDkJzsB/ftKGzyjvH4q9nJY69TJuLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqCfWB%2FbtsLxDkJzsB%2FftKGzyjvH4q9nJY69TJuLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;145&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;D3D12Multithreading::WorkerThread() 함수로 가보면 인자로 들어오는 threadIndex가 유효할때만 실행되도록 되어 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 언급했던 것처럼 BeginRenderFrame 이벤트가 발생할 때까지 기다린다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;855&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbRkJz/btsLwdzssT6/Tbzp9F6ANbb8m5NvtCT9HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbRkJz/btsLwdzssT6/Tbzp9F6ANbb8m5NvtCT9HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbRkJz/btsLwdzssT6/Tbzp9F6ANbb8m5NvtCT9HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbRkJz%2FbtsLwdzssT6%2FTbzp9F6ANbb8m5NvtCT9HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;855&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;855&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BeginRenderFrame 이벤트가 발생하면 Shadow Pass에서 사용될 Command List를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FrameResource에 각 쓰레드를 위한 Command List가 미리 생성되어 있어 자신의 Thread Index로 참조하여 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 SetCommonPipelineState() 함수를 호출한다. SetCommandPipelineState()는 Shadow Pass와 Scene Pass에서 공통적으로 해줘야 하는 일들을 수행하는 함수이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JfXTW/btsLwJ5IZoc/aBzvVFvDSubXaeVLypAKDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JfXTW/btsLwJ5IZoc/aBzvVFvDSubXaeVLypAKDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JfXTW/btsLwJ5IZoc/aBzvVFvDSubXaeVLypAKDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJfXTW%2FbtsLwJ5IZoc%2FaBzvVFvDSubXaeVLypAKDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1344&quot; height=&quot;608&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 FrameResource::Bind() 함수에서는 Scene Pass와 Shadow Pass의 동작이 달라진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD5sJO/btsLzqY0r9M/aYtLOU5FKrEj9pftotuLk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD5sJO/btsLzqY0r9M/aYtLOU5FKrEj9pftotuLk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD5sJO/btsLzqY0r9M/aYtLOU5FKrEj9pftotuLk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD5sJO%2FbtsLzqY0r9M%2FaYtLOU5FKrEj9pftotuLk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1205&quot; height=&quot;427&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shadow Pass에서는 픽셀 쉐이더가 어떻게 동작하든 상관 없이 Depth Buffer에 어떤 값이 들어가는지가 중요하기 때문에 필요 없는 디스크립터들은 null resource로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 shadow pass에서는 라이트 기준의 가상의 Camera Space 기준으로 버텍스 위치를 연산하기 때문에 별개의 Constant Buffer를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 Shadow Pass에서는 Render Target은 필요 없고 Depth값을 Shadow Texture에 저장해야 하기 때문에 RenderTarget을 nullptr, Depth Stencil Descriptor를 m_shadowDepthView로 설정한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sh8An/btsLw2Zy8gv/QfuvT7hMwYkVdkZk1KJDTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sh8An/btsLw2Zy8gv/QfuvT7hMwYkVdkZk1KJDTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sh8An/btsLw2Zy8gv/QfuvT7hMwYkVdkZk1KJDTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsh8An%2FbtsLw2Zy8gv%2FQfuvT7hMwYkVdkZk1KJDTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;42&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 WorkerThread() 함수로 돌아와서 diffuse, normal 텍스쳐 역시 필요 없기에 null resource로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;101&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0dKpi/btsLybH92Gw/twyQu9Q72lxxE2cprHBpD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0dKpi/btsLybH92Gw/twyQu9Q72lxxE2cprHBpD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0dKpi/btsLybH92Gw/twyQu9Q72lxxE2cprHBpD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0dKpi%2FbtsLybH92Gw%2FtwyQu9Q72lxxE2cprHBpD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;920&quot; height=&quot;101&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;101&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업들이 끝나면 실질적은 Draw Call을 해주게 되는데, 이 예제에서는 전체 메시중 자신의 Thread Index의 배수 인덱스에 위치한 메시에 대해서만 Draw Call을 해주는 것으로 쓰레드간 작업 분담을 구현하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnoQmM/btsLxsjpjJ0/ikNm2OLk1c1t9W60kS1pa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnoQmM/btsLxsjpjJ0/ikNm2OLk1c1t9W60kS1pa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnoQmM/btsLxsjpjJ0/ikNm2OLk1c1t9W60kS1pa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnoQmM%2FbtsLxsjpjJ0%2FikNm2OLk1c1t9W60kS1pa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;449&quot; height=&quot;70&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shadow Pass를 위한 모든 작업들이 완료되면 메인 쓰레드에 현재 쓰레드의 Shadow Pass 작업이 끝났음을 알린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OnRender() 함수로 돌아가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8YWnc/btsLzc0TvNd/tD6dH6iG3L63TcfwmBTxq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8YWnc/btsLzc0TvNd/tD6dH6iG3L63TcfwmBTxq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8YWnc/btsLzc0TvNd/tD6dH6iG3L63TcfwmBTxq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8YWnc%2FbtsLzc0TvNd%2FtD6dH6iG3L63TcfwmBTxq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;114&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shadow Pass까지의 모든 Command List가 만들어졌으므로 Pre, Mid, Shadow Pass 0, Shadow Pass 1, Shadow Pass 2 순서대로 Command List들을 제출한다. Command List를 어떤 순서를 만들었는지와는 상관 없이 인자로 넣은 Command List 배열의 순서에 따라 실행 순서가 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 메인스레드는 모든 Worker Thread가 Scene Pass에 필요한 Command List를 만들 때까지 기다린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BEcZa/btsLwJe3EzE/6yKOQOmWo84vXPKKMcjyVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BEcZa/btsLwJe3EzE/6yKOQOmWo84vXPKKMcjyVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BEcZa/btsLwJe3EzE/6yKOQOmWo84vXPKKMcjyVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBEcZa%2FbtsLwJe3EzE%2F6yKOQOmWo84vXPKKMcjyVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;994&quot; height=&quot;167&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker Thread로 돌아왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 하는 일은 Shadow Pass에서와 마찬가지로 SetCommonPipelineState() 함수를 호출하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Shadow Pass때와는 다르게 Render Target View와 Depth Stencil View가 필요하므로 FrameResource::Bind() 함수 호출 시에 인자로 넣어준다. (Bind() 함수에서는 &lt;i&gt;pCommandList-&amp;gt;OMSetRenderTargets(1, pRtvHandle, FALSE, pDsvHandle);&lt;/i&gt; 로 처리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MIFR5/btsLyDLca4K/4YyRtRZdZpyxU1xQ8ikPL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MIFR5/btsLyDLca4K/4YyRtRZdZpyxU1xQ8ikPL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MIFR5/btsLyDLca4K/4YyRtRZdZpyxU1xQ8ikPL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMIFR5%2FbtsLyDLca4K%2F4YyRtRZdZpyxU1xQ8ikPL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1045&quot; height=&quot;218&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 Shadow Pass때와는 다르게 Pixel Shader에서 Texture들을 필요로 하므로 null resource가 아닌 실제 텍스쳐 리소스들을 담은 Descriptor Table을 사용한다. 메쉬마다 다른 텍스쳐를 사용하므로 루프마다 Descriptor Table을 설정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 Thread Index의 배수 인덱스에 존재하는 메쉬에 대해서만 Draw Call을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tH1NP/btsLx7MxDCF/Uzb8RSnEzO5LR2vl5poQiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tH1NP/btsLx7MxDCF/Uzb8RSnEzO5LR2vl5poQiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tH1NP/btsLx7MxDCF/Uzb8RSnEzO5LR2vl5poQiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtH1NP%2FbtsLx7MxDCF%2FUzb8RSnEzO5LR2vl5poQiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;20&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker Thread에서 해야 하는 모든 작업이 끝났고 메인 쓰레드에 작업이 완료되었음을 알린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lMS3Q/btsLwtwDF4T/rr3RoKnv7hBtl2If6xTQk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lMS3Q/btsLwtwDF4T/rr3RoKnv7hBtl2If6xTQk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lMS3Q/btsLwtwDF4T/rr3RoKnv7hBtl2If6xTQk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlMS3Q%2FbtsLwtwDF4T%2Frr3RoKnv7hBtl2If6xTQk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;69&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;69&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분도 너무 길어서 줄바꿈을 추가로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 제출했던 5개의 Command List들을 제외하고, Scene Pass 0, Scene Pass 1, Scene Pass 2, Post 총 4개의 Command List들을 제출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfbjlw/btsLwItxreK/KNjB6YsUlb4cNwAcPOENJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfbjlw/btsLwItxreK/KNjB6YsUlb4cNwAcPOENJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfbjlw/btsLwItxreK/KNjB6YsUlb4cNwAcPOENJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfbjlw%2FbtsLwItxreK%2FKNjB6YsUlb4cNwAcPOENJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;263&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼포먼스 측정을 위한 코드다. 중요하지 않으므로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5x2ku/btsLx7eKVhC/gFd252bHJOjKHB4e7k8H8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5x2ku/btsLx7eKVhC/gFd252bHJOjKHB4e7k8H8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5x2ku/btsLx7eKVhC/gFd252bHJOjKHB4e7k8H8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5x2ku%2FbtsLx7eKVhC%2FgFd252bHJOjKHB4e7k8H8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;595&quot; height=&quot;164&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 GPU에 렌더 타겟을 디스플레이에 표시하도록 명령하는 IDXGISwapChain3::Present() 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Present() 함수 또한 Command Queue에 명령을 추가하는 방법으로 GPU에 간접적으로 명령을 전달하기 때문에 Present() 함수까지 호출한 뒤 fence의 값을 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여태까지 살펴보았던 로직을 플로우 차트로 정리해 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;D3D12Multithreading.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PfgWD/btsLAunNLqq/SFwLcKbe0uvH0dPsjjUtEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PfgWD/btsLAunNLqq/SFwLcKbe0uvH0dPsjjUtEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PfgWD/btsLAunNLqq/SFwLcKbe0uvH0dPsjjUtEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPfgWD%2FbtsLAunNLqq%2FSFwLcKbe0uvH0dPsjjUtEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;934&quot; data-filename=&quot;D3D12Multithreading.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Graphics</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/84</guid>
      <comments>https://emacser.tistory.com/84#entry84comment</comments>
      <pubDate>Sun, 22 Dec 2024 16:36:54 +0900</pubDate>
    </item>
    <item>
      <title>[UE] Android에서 Unreal Insights 사용하기</title>
      <link>https://emacser.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Android로 배포한 언리얼 앱을 Unreal Insights로 분석하는 방법은 두가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &quot;-tracefile=[파일이름]&quot; 명령 인수로 디바이스 저장소에 .utrace 파일 생성 후 PC로 복사하여 분석하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &quot;-tracehost=127.0.0.1&quot; 로 Unreal Insights와 직접 연결하여 분석하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 1번과 2번 모두 adb push를 사용하기 때문에 USB 디버깅이 켜져 있어야 하고, 갤럭시 기준으로 개발자 메뉴에 있는 &quot;OEM 잠금 해제&quot;를 켜줘야 한다. (몇년전에 커스텀롬을 하기 위해 recovery mode로 진입하여 XDA에서 만든 부트로더 잠금 해제 툴로 벽돌을 감수하고 잠금 해제를 했었는데, 이제는 개발자 메뉴에서 딸깍이 가능한 것이 신기했다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RcUDj/btsLvxRkFPO/C3YjB4qVxprRwJMpQD00Jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RcUDj/btsLvxRkFPO/C3YjB4qVxprRwJMpQD00Jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RcUDj/btsLvxRkFPO/C3YjB4qVxprRwJMpQD00Jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRcUDj%2FbtsLvxRkFPO%2FC3YjB4qVxprRwJMpQD00Jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;536&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;adb는 Android Studio에서 기본으로 설치해주는 것을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경로는 AppData/Local/Android/Sdk/platform-tools&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간편하게 사용하고 싶으면 해당 경로를 환경변수 Path에 추가해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 앱 수정 없이 명령 인수를 넣어서 실행하려면 UECommandline.txt라는 파일을 디바이스의 앱 데이터 디렉터리에 넣어주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UECommandline.txt에 원하는 명령 인수를 작성한 다음 adb push로 Android/data/[앱 이름]/UnrealGame/[프로젝트명] 디렉터리로 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 방법으로 진행했을 때 utrace 파일이 생성되지 않는 경우가 있을 수 있다. OEM 잠금 해제도 하고 USB 디버깅 연결 후 adb device로 연결되어 있는것을 확인 한 뒤, 앱에 파일 쓰기 권한도 줬는데도 불구하고 utrace 파일 생성이 안되어서 2번 방법으로 진행했다. (정상적인 경우라면 Saved/Profiling 디렉터리 하위에 utrace 파일이 생성되었어야 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 2번 방법으로 진행한다면, adb로 TCP 트래픽을 리다이렉트 해주어야 한다. 사용할 명령어는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1734111581085&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;adb reverse tcp:1981 tcp:1981&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트번호는 언리얼 엔진 버전에 따라 다를 수 있다. 인터넷에서 검색했을때 나온 자료로는 1980이었는데, 내 경우(5.3.2)에는 1981이어서 곤혹을 치뤘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인사이트 연결에 실패했는데 포트번호를 모르겠다면 로그를 보면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1734112816256&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Log file open, 12/12/24 20:27:32
LogTrace: Initializing trace...
LogCore: Display: Requested channels: 'task,log,cpu,gpu,frame,memtag,net,loadtime,stats'
LogAndroid: Case APP_CMD_WINDOW_RESIZED
LogAndroid: Case APP_CMD_WINDOW_REDRAW_NEEDED
LogTrace: Error: ConnectError Connecting to host (127.0.0.1:1981): 'Connection refused'
LogCore: Warning: Unable to trace to host '127.0.0.1'
LogCore: Error: Trace failed to connect (trace host: 127.0.0.1)!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 로그 맨 앞부분에 Trace 관련 로그들이 찍히는데, 여기서 찍히는 포트 번호를 보고 adb reverse 명령어를 다시 입력해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 명령 인수가 들어갔다면 언리얼 앱을 다시 켜주면 utrace 파일이 작성되거나 인사이트와 연결이 가능해지게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 직접 엔진을 빌드하거나 엔진 버전업이 있었던 프로젝트에만 해당된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OBY4z/btsLjF3kxx3/KCWmYFPFpRuuO8wVojeNR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OBY4z/btsLjF3kxx3/KCWmYFPFpRuuO8wVojeNR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OBY4z/btsLjF3kxx3/KCWmYFPFpRuuO8wVojeNR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOBY4z%2FbtsLjF3kxx3%2FKCWmYFPFpRuuO8wVojeNR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;119&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;녹화한 trace 파일에 이렇게 플랫폼, 프로젝트명 등의 정보가 표시되지 않는 경우가 있는데 이건 Unreal Insights의 버전이 너무 오래되어 호환이 안되는 상황이다. 해당 trace 파일을 선택하여 분석을 시작해도 타임라인에 아무것도 나오지 않을 것이다. 이럴때는 엔진 디렉터리의 UE.sln 솔루션을 VS로 열어서 Programs 하위의 UnrealInsights 프로젝트를 찾아 빌드해주면 된다.&lt;/p&gt;</description>
      <category>Game Engine</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/83</guid>
      <comments>https://emacser.tistory.com/83#entry83comment</comments>
      <pubDate>Fri, 13 Dec 2024 17:36:18 +0900</pubDate>
    </item>
    <item>
      <title>[Vulkan] Framebuffer의 재활용에 대하여</title>
      <link>https://emacser.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Framebuffer를 만들때는 Renderpass를 요구하는데, 그렇다면 Framebuffer는 렌더패스에 종속적인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 렌더패스가 같은 Framebuffer를 사용할 수 없는가? 라는 의문이 들어 검색을 좀 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Vulkan Specification을 봤을 때, Framebuffer나 PipelineState같이 렌더패스를 사용하여 생성되는 객체들은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Renderpass Compability를 만족하면 공유해서 사용할 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재사용이 가능한 것은 알겠는데, Framebuffer를 여러 렌더 패스가 공유하게 하면 얼마나 성능 향상을 기대할 수 있을까도 궁금해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/vulkan/comments/bzee0u/how_important_is_reuse_of_framebuffers_and/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.reddit.com/r/vulkan/comments/bzee0u/how_important_is_reuse_of_framebuffers_and/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734024499212&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;From the vulkan community on Reddit&quot; data-og-description=&quot;Explore this post and more from the vulkan community&quot; data-og-host=&quot;www.reddit.com&quot; data-og-source-url=&quot;https://www.reddit.com/r/vulkan/comments/bzee0u/how_important_is_reuse_of_framebuffers_and/&quot; data-og-url=&quot;https://www.reddit.com/r/vulkan/comments/bzee0u/how_important_is_reuse_of_framebuffers_and/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cZHoRW/hyXKohWkxs/llcl9UdVWaUKJuPyhkDvC0/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584,https://scrap.kakaocdn.net/dn/cGEoO1/hyXKoCdvU2/IOiaUN0YSc24A02DSLrggk/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/vulkan/comments/bzee0u/how_important_is_reuse_of_framebuffers_and/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.reddit.com/r/vulkan/comments/bzee0u/how_important_is_reuse_of_framebuffers_and/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cZHoRW/hyXKohWkxs/llcl9UdVWaUKJuPyhkDvC0/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584,https://scrap.kakaocdn.net/dn/cGEoO1/hyXKoCdvU2/IOiaUN0YSc24A02DSLrggk/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;From the vulkan community on Reddit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explore this post and more from the vulkan community&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.reddit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문은 캐싱 시스템을 만드는 대신 특정 프레임에 필요한 디스크립 셋과 프레임 버퍼를 프레임 시작시에 한꺼번에 만드는 방식이 실현 가능한지, 성능적으로 고려해야 할 사항은 뭐가 있는지 물어보는 것이었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답변을 종합해보자면 데스크탑 기준으로 Framebuffer 생성 비용은 매우 싸고, Descriptor Set 생성 비용은 비싸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일에서는 데스크탑에서보다 Framebuffer 생성 비용이 비싸 미리 필요한 만큼만 프레임버퍼를 생성하는 것이 의미가 있으나 Descrptor Set에 비해 구현 난이도가 높다고 한다.&lt;/p&gt;</description>
      <category>Graphics</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/82</guid>
      <comments>https://emacser.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 13 Dec 2024 02:23:15 +0900</pubDate>
    </item>
    <item>
      <title>[UE] Mesh Drawing Pipeline</title>
      <link>https://emacser.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=uykuLOgfcok&amp;amp;t=900s&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=uykuLOgfcok&amp;amp;t=900s&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=uykuLOgfcok&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/t2E9g/hyXGNOSn7M/nCruuz8D5GspVlmcEzma60/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bfZul0/hyXGC0R8ER/dQ451MBkDlMRcPykhJZjF0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;언리얼 서밋 프리미엄 | 2019 | 최신 언리얼 엔진 렌더러의 진화 - 렌더링 종속성 그래프, 메시 드&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/uykuLOgfcok&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유용한 영상이 있어 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절반 정도의 분량만 시청했는데 언리얼의 메시 렌더링 파이프라인을 이해하는데 큰 도움이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하자면 SceneProxy에서 같은 버텍스, 머티리얼을 사용하는 메시들의 묶음인 FMeshBatch를 생성하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MeshPassProcessor에서는 AddMeshBatch 함수를 오버라이드해 FMeshBatch를 MeshDrawCommand로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MeshDrawCommand는 Draw Call에 필요한 모든 정보를 가지고 있으며, 예시로 Shader Binding, Vertex Steam, Index Buffer, PSO 캐시 ID 등이 있다.&lt;/p&gt;</description>
      <category>Game Engine</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/81</guid>
      <comments>https://emacser.tistory.com/81#entry81comment</comments>
      <pubDate>Mon, 2 Dec 2024 11:59:45 +0900</pubDate>
    </item>
    <item>
      <title>[Vulkan] Cubemap Texture 로드시 주의사항들</title>
      <link>https://emacser.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;Vulkan에서 Descriptor를 추가하여 사용할 수 있도록 Texture를 로드하여 준비하는 과정은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Staging Buffer를 생성한다. (이미지에 픽셀 데이터를 복사하기 위한 임시 버퍼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. vkMapMemory() 함수로 Staging Buffer의 가상 주소를 포인터에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. memcpy 함수로 이미지를 Staging Buffer의 메모리에 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. vkUnmapMemory() 함수로 매핑한 가상 주소를 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Image를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. vkCmdPipelineBarrier() 함수로 이미지의 레이아웃을 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL로 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. vkCmdCopyBufferToImage() 함수로 버퍼 안의 데이터를 이미지로 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 이미지의 레이아웃을 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL로 변경한다.&lt;br /&gt;(6-8의 과정은 Command Buffer를 생성하여 진행하게 되는데, 이때 생성하는 Command Buffer를 One-time Command Buffer 라고 부르기도 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cubemap Texture를 로드할 경우에는 아래 작업들이 추가로 요구되는데, 검색으로 찾기 쉽지 않은 정보라서 기록해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Image 생성시 Flags에 VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT 추가하기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ccYe/btsKLbwDQLo/JreyrJRRygEa3exN4Opeb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ccYe/btsKLbwDQLo/JreyrJRRygEa3exN4Opeb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ccYe/btsKLbwDQLo/JreyrJRRygEa3exN4Opeb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ccYe%2FbtsKLbwDQLo%2FJreyrJRRygEa3exN4Opeb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;255&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Image View 생성시 viewType은 VK_IMAGE_VIEW_TYPE_CUBE으로, layerCount는 6으로 설정하기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuw2oO/btsKL1mByo8/jMkONBV0pLJbHCLQajsSpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuw2oO/btsKL1mByo8/jMkONBV0pLJbHCLQajsSpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuw2oO/btsKL1mByo8/jMkONBV0pLJbHCLQajsSpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcuw2oO%2FbtsKL1mByo8%2FjMkONBV0pLJbHCLQajsSpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;195&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Image Barrier 생성시에도 layerCount 값을 6으로 설정하기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sxvNK/btsKL9517La/Noht8D5OkVGtKDcUuOaHDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sxvNK/btsKL9517La/Noht8D5OkVGtKDcUuOaHDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sxvNK/btsKL9517La/Noht8D5OkVGtKDcUuOaHDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsxvNK%2FbtsKL9517La%2FNoht8D5OkVGtKDcUuOaHDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;239&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. CopyRegion 설정시에도 동일한 layerCount 값을 넣어주기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuT1az/btsKLbpNe3Z/ToquUnw0OQRhZVDrnEK960/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuT1az/btsKLbpNe3Z/ToquUnw0OQRhZVDrnEK960/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuT1az/btsKLbpNe3Z/ToquUnw0OQRhZVDrnEK960/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuT1az%2FbtsKLbpNe3Z%2FToquUnw0OQRhZVDrnEK960%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;201&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Graphics</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/80</guid>
      <comments>https://emacser.tistory.com/80#entry80comment</comments>
      <pubDate>Mon, 18 Nov 2024 01:32:33 +0900</pubDate>
    </item>
    <item>
      <title>[Vulkan] Tangent, Bitangent, Normal 시각화</title>
      <link>https://emacser.tistory.com/79</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u98Wr/btsKEd7hg9P/U65qClUkE7rttFIFIN4j9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u98Wr/btsKEd7hg9P/U65qClUkE7rttFIFIN4j9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u98Wr/btsKEd7hg9P/U65qClUkE7rttFIFIN4j9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu98Wr%2FbtsKEd7hg9P%2FU65qClUkE7rttFIFIN4j9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;525&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Geometry Shader를 사용하여 TBN 행렬의 각 요소를 시각화하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 사용된 쉐이더 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1731129429032&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 450

struct Light
{
    vec3 position;
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 attenuation;
    float shininess;
};

layout(std140, binding = 0) uniform TransformBufferObject
{
    mat4 view;
    mat4 projection;
    vec3 cameraPosition;
} transformBuffer;

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inTexCoord;
layout(location = 3) in vec3 inTangent;

layout(location = 4) in mat4 inModel;
layout(location = 8) in mat4 inModelView;
layout(location = 12) in mat4 inNormalMatrix;

layout(location = 0) out vec3 outNormal;
layout(location = 1) out vec3 outTangent;
layout(location = 2) out vec3 outBitangent;

void main()
{
    mat3 normalMatrix = mat3(inNormalMatrix);

    vec4 position = inModelView * vec4(inPosition, 1.0);
    gl_Position = transformBuffer.projection * position;

    outNormal = normalize(vec3(transformBuffer.projection * vec4(normalMatrix * inNormal, 0.0)));
    outTangent = normalize(vec3(transformBuffer.projection * vec4(normalMatrix * inTangent, 0.0)));
    outBitangent = normalize(cross(outNormal, outTangent));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1731129613889&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 450

layout (triangles) in;
layout (line_strip, max_vertices = 18) out;

layout (location = 0) in vec3 normals[];
layout (location = 1) in vec3 tangents[];
layout (location = 2) in vec3 bitangents[];

layout (location = 0) out vec3 outColor;

const float MAGNITUDE = 0.04;

void GenerateLine(int index, vec3 offsets[3], vec3 color)
{
	gl_Position = gl_in[index].gl_Position;
	outColor = color;
	EmitVertex();
	gl_Position = gl_in[index].gl_Position + vec4(offsets[index], 0.0) * MAGNITUDE;
	outColor = color;
	EmitVertex();
	EndPrimitive();
}

void main()
{
	for (int i = 0; i &amp;lt; 3; ++i)
	{
		GenerateLine(i, normals, vec3(1.0, 1.0, 0.0));
		GenerateLine(i, tangents, vec3(0.0, 0.0, 1.0));
		GenerateLine(i, bitangents, vec3(1.0, 0.0, 0.0));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1731129629588&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#version 450

layout(location = 0) in vec3 inColor;

layout(location = 0) out vec4 outColor;

void main()
{
    outColor = vec4(inColor, 1.0);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Graphics</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/79</guid>
      <comments>https://emacser.tistory.com/79#entry79comment</comments>
      <pubDate>Sat, 9 Nov 2024 14:20:44 +0900</pubDate>
    </item>
    <item>
      <title>[UE] Unreal Editor Viewport의 BufferedRT</title>
      <link>https://emacser.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실제 게임 플레이시에는 BackBuffer에 직접 씬을 렌더링하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unreal Editor에서 편집중인 월드를 뷰포트에 그릴 때는 BackBuffer에 그리지 않고 BufferedRT라는 텍스쳐를 렌더 타겟으로 하여 그린 후 Slate UI 그릴때 BufferedRT 텍스쳐를 가져와서 뷰포트에 그린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RenderDoc에서 뜯어본 BufferedRT 텍스쳐&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lrHy1/btsKzzXefVB/Gw07dVPpTkFnOYw0xEnWe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lrHy1/btsKzzXefVB/Gw07dVPpTkFnOYw0xEnWe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lrHy1/btsKzzXefVB/Gw07dVPpTkFnOYw0xEnWe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlrHy1%2FbtsKzzXefVB%2FGw07dVPpTkFnOYw0xEnWe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;974&quot; height=&quot;278&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Game Engine</category>
      <author>emacser</author>
      <guid isPermaLink="true">https://emacser.tistory.com/78</guid>
      <comments>https://emacser.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 6 Nov 2024 18:22:16 +0900</pubDate>
    </item>
  </channel>
</rss>