ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • jEngine(Vulkan, DX12)
    Graphics/Renderer Lab 2023. 11. 11. 01:05

     
    Renderer Lab 이라는 카테고리를 통해서 그래픽스 피쳐 개발을 위해 만들었던 소프트웨어들에 대한 기록을 남길 것입니다. 해당 기록들을 통해서 얻고자하는 것은 다음과 같습니다.
     - 만들었던 소프트웨어에 대해 잊은 경우 빠르게 리마인드 하여 그래픽스 피쳐 개발에 활용함
     - 소프트웨어 제작시 발생하는 고민들이나 코너 케이스를 히스토리로 남겨 토론하고 추적함

     

    최초 작성 : 2023-11-11
    마지막 수정 : 2024-02-07
    최재호

     

    Last Updated(2024-02-07) : 2.2.10. jResourceBarrierBatcher 추가

     

    목차

    1. 개요
    2. 렌더러 사용
      2.1. jEngine 소개
      2.2. 코드 구성
        2.2.1. Shader Resource Binding
          2.2.1.1. jShaderBinding
          2.2.1.2.  jShaderBindingLayout
          2.2.1.3.  jShaderBindingInstance
        2.2.2. Buffer
          2.2.2.1. jBuffer
          2.2.2.2. jRingBuffer
          2.2.2.3. jUniformBufferBlock
        2.2.3. Shader
          2.2.3.1. jShader Compile
          2.2.3.2. jShader Permutation
          2.2.3.3. Runtime hlsl update
        2.2.4. RenderPass
        2.2.5. PipelineState
          2.2.5.1. Fixed State
          2.2.5.2. Felxible State
        2.2.6. jObject, jRenderObject
        2.2.7. jDrawCommand
        2.2.8. jCommandBuffer
        2.2.9. jTexture

        2.2.10. jResourceBarrierBatcher
    3. Third party libraries
     

    1. 개요

    그래픽스 피쳐를 구현해보고 테스트하기 위해서는 가볍고 빠르게 결과를 확인할 수 있는 렌더러가 필요하다고 생각합니다. 왜냐하면 제가 그래픽스 피쳐의 구현으로 얻고자 하는 것은 기반 지식과 아이디어 습득에 있기 때문입니다. 이런 점을 고려해 보면 Low ~ High level 수준까지 모두 컨트롤 가능한 렌더러를 보유하고 있다는 것은 큰 이득이 된다고 생각합니다.
    이 글에서는 jEngine 의 소개와 사용하는 방법을 정리할 것입니다. jEngine 이 수정되면 이 페이지도 계속해서 갱신할 예정입니다. 아직 많이 개발하진 않았지만 개발 과정 동안 많이 배울 수 있으면 좋겠습니다.
     

    2. 렌더러 사용

    2.1. jEngine 소개

    - https://github.com/scahp/jEngine
    - jEngine 은 2가지 API, Vulkan 과 DX12 를 지원합니다. (-dx12, -vulkan 을 Commandline argument 로 API 를 변경)

     


    2.2. 코드 구성

    2.2.1. Shader Resource Binding

    2.2.1.1. jShaderBinding

    쉐이더 시그니쳐의 리소스 파라메터 한개를 정의합니다. 리소스 타입과 실제 리소스를 정의합니다.

     
    2.2.1.2.  jShaderBindingLayout

    Shader 파라메터 배열을 정의 하며, Register space 1 개를 차지합니다. 바인딩할 리소스를 반드시 가질 필요는 없으며 리소스 타입만 보유하면 됩니다.
     
    1). Vulkan
    VkDescriptorSetLayout 을 생성하는 단위가 됨.
    VkPipelineLayout 을 생성하고 관리함.
     
    2). DX12
    ID3D12RootSignature 를 생성하고 관리함.

     
    2.2.1.3.  jShaderBindingInstance

    실제 사용할 Resource(jShaderBindingResource) 를 가지고 있습니다. 동일한 Layout 이지만 다른 Resource 를 소유할 수 있기 때문에 jShaderBindingInstance 로 불립니다.
    렌더링을 위해서는 한개 이상의 jShaderBindingInstance 가 필요할 수 있습니다. 예를들면 RenderObject, Light 와 같이 여러 객체에 대한 단위로 jShaderBindingInstnace 를 가질 수 있습니다. 각각의 jShaderBindingInstance 는 고유의 Register space 를 할당받도록 하여 Register number 를 서로 침범하지 않도록 합니다.
     
    1). Vulkan
    VkDescriptorSet 을 생성하고 관리합니다. 필요한 경우 VkDescriptorSet 을 업데이트 하여 리소스를 변경합니다.
     
    2). DX12
    DX12 는 ShaderVisible DescriptorHeap 에 렌더링할 리소스의 Descriptor 를 복사하여 준비합니다. ShaderVisible DescriptorHeap 의 크기는 제한되어있기 때문에 일반적으로 리소스를 생성하면 Descriptor 는 Non-ShaderVisible DescriptorHeap 에 생성됨. (코드에서는 ShaderVisible 을 Online, Non-ShaderVisible 을 Offline 으로 부름.)
     

    [UML] jShaderBindingInstance - jShaderBindingLayout - jShaderBinding

     

    아래 코드는 jShaderBindingInstance 를 사용하여 hlsl 에 Resource Binding 하는 과정을 보여줍니다.

    // [C++ 코드 측]
    // 1. GBuffer 에 대한 ShaderBindingInstance 생성, Register space 0 에 할당됨.
    int32 BindingPoint = 0;
    jShaderBindingArray ShaderBindingArray;
    jShaderBindingResourceInlineAllocator ResourceInlineAllocator;
    
    for (int32 i = 0; i < _countof(GBuffer); ++i)
    {
        const jSamplerStateInfo* ShadowSamplerStateInfo = TSamplerStateInfo<ETextureFilter::LINEAR, ETextureFilter::LINEAR
            , ETextureAddressMode::CLAMP_TO_BORDER, ETextureAddressMode::CLAMP_TO_BORDER, ETextureAddressMode::CLAMP_TO_BORDER
            , 0.0f, 1.0f, Vector4(1.0f, 1.0f, 1.0f, 1.0f), false, ECompareOp::LESS>::Create();
    
        // Texture & SamplerState 에 대한 jShaderBinding 추가
        ShaderBindingArray.Add(BindingPoint++, 1, EShaderBindingType::TEXTURE_SAMPLER_SRV, EShaderAccessStageFlag::ALL_GRAPHICS
            , ResourceInlineAllocator.Alloc<jTextureResource>(GBuffer[i]->GetTexture(), ShadowSamplerStateInfo));
    }
    
    auto FirstShaderBindingInstance = g_rhi->CreateShaderBindingInstance(ShaderBindingArray, jShaderBindingInstanceType::SingleFrame);
    ...
    // 2. View 에 대한 ShaderBindingInstance 생성, Register space 1 에 할당됨.
    int32 BindingPoint = 0;
    jShaderBindingArray ShaderBindingArray;
    jShaderBindingResourceInlineAllocator ResourceInlineAllactor;
    
    // jUniformBuffer 에 대한 jShaderBinding 추가
    ShaderBindingArray.Add(BindingPoint++, 1, EShaderBindingType::UNIFORMBUFFER_DYNAMIC, EShaderAccessStageFlag::ALL_GRAPHICS
        , ResourceInlineAllactor.Alloc<jUniformBufferResource>(ViewUniformBufferPtr.get()), true);
    ShaderBindingArray.Add(BindingPoint++, 1, EShaderBindingType::TEXTURE_SAMPLER_SRV, EShaderAccessStageFlag::ALL_GRAPHICS
        , ResourceInlineAllocator.Alloc<jTextureResource>(InShadowMap, ShadowSamplerStateInfo));
    
    auto SecondShaderBindingInstance = g_rhi->CreateShaderBindingInstance(ShaderBindingArray, jShaderBindingInstanceType::SingleFrame);
    
    // 3. jShaderBindingInstanceArray 로 만듬. 이것을 jDrawCommand 에 넘기면 드로콜을 호출 할때 바인딩 해줌.
    jShaderBindingInstanceArray ShaderBindinInstanceArray;
    ShaderBindinInstanceArray.Add(FirstShaderBindingInstance.get());
    ShaderBindinInstanceArray.Add(SecondShaderBindingInstance.get());
    ...
    ------------------------------------------------------------------
    // [hlsl 코드 측]
    ...
    // GBuffer 에 대한 ShaderBinding, Register space 0 이 할당되어 있음.
    Texture2D GBuffer0 : register(t0, space0);
    SamplerState GBuffer0SamplerState : register(s0, space0);
    
    Texture2D GBuffer1 : register(t1, space0);
    SamplerState GBuffer1SamplerState : register(s1, space0);
    
    Texture2D GBuffer2 : register(t2, space0);
    SamplerState GBuffer2SamplerState : register(s2, space0);
    
    // View 에 대한 ShaderBinding, Register space 1 에 할당되어 있음.
    cbuffer ViewParam : register(b0, space1) { ViewUniformBuffer ViewParam; }
    Texture2D ShadowMap : register(t0, space1); // Register space 가 다르기 때문에 GBuffer0 의 t0 레지스터와 겹치지 않음.
    SamplerState ShadowMapSamplerState : register(s0, space1); // Register space 가 다르기 때문에 GBuffer0SamplerState 의 s0 레지스터와 겹치지 않음.

     


    2.2.2. Buffer

    2.2.2.1. jBuffer

    일반적인 버퍼로 jBufferUtil 로 부터 버퍼를 할당받아 사용합니다. Lifetime 이 MultiFrame 인 경우에 사용합니다. jBuffer 당 API 에서 제공하는 리소스(VkBuffer, ID3D12Resource) 가 하나씩 할당됩니다. 실제 버퍼의 메모리 할당은 jBufferUtil_Vulkan, jBufferUtil_DX12 에서 제공하는 함수를 사용하여 할당합니다.
     
    1). Vulkan
    Vulkan 의 경우 VkBuffer 생성 최대개수가 4096 개로 제한되어있습니다. 그래서 메모리 할당을 위한 별도의 메모리풀 시스템을 사용합니다. jMemoryPool 이 거기에 해당합니다. jMemoryPool 은 여러개의 jSubMemoryAllocator 를 갖고 있는데, 버퍼의 속성과 할당하는 메모리의 크기에 대해서 여러개의 jSubMemoryAllocator 를 생성합니다. jMemoryPool 은 요청받은 버퍼와 버퍼의 크기를 확인하고 적절한 jSubMemoryAllocator 에서 메모리를 할당 받습니다.
     
    2). DX12
    DX12 의 경우 'uint64 PlacedResourceSizeThreshold = 512 * 512 * 4' 크기 이하의 리소스를 할당하는 경우 CreatePlacedResource 를 사용하여 리소스 생성합니다. CreatePlacedResource 의 경우 최대 'uint64 DefaultPlacedResourceHeapSize = 256 * 1024 * 1024' 까지 할당 가능합니다. 이 크기가 넘어가면 CreateCommittedResource 를 사용하여 리소스를 생성합니다.
     
     

    2.2.2.2. jRingBuffer

    큰 사이즈의 버퍼를 하나 할당하여 필요에 따라 메모리 서브 블록을 할당받아 사용합니다. 맨 앞에서 부터 차례대로 메모리를 할당해나가며 다음 프레임이 되면 Reset 을 통해 사용한 버퍼의 Offset 을 초기화 하여 버퍼를 재사용 합니다. Lifetime 이 SingleFrame 인 경우에 사용합니다.

     
    2.2.2.3. jUniformBufferBlock
    Shader 의 UniformBuffer(ConstantBuffer) 에 사용하기 위한 버퍼로 Multi or Single Frame 타입으로 생성 가능합니다.

    [UML] jBuffer - jRingBuffer

     
    2.2.3. Shader

    2.2.3.1. jShader Compile

    hlsl 을 기반으로 Shader 를 제작하며 Vulkan, DX12 에 모두 사용합니다.

    // Fullscreen quad 를 렌더링하는데 쓰이는 Vertex shader 컴파일
    jShaderInfo shaderInfo;
    shaderInfo.SetName(jNameStatic("DirectionalLightShaderVS"));
    shaderInfo.SetShaderFilepath(jNameStatic("Resource/Shaders/hlsl/fullscreenquad_vs.hlsl"));
    shaderInfo.SetShaderType(EShaderAccessStageFlag::VERTEX);
    jShader* VertexShader = g_rhi->CreateShader(shaderInfo);

     
    각각의 hlsl 은 #include 를 사용하여 다른 hlsl 를 포함할 수 있습니다. 아래 예제를 확인해주세요.

    #include "common.hlsl"
    #include "lightutil.hlsl"
    #include "PBR.hlsl"
    
    struct VSOutput
    {
        float4 Pos : SV_POSITION;
        float4 ClipPos : TEXCOORD0;
    };
    ...

     
    1). Vulkan
    ShaderConductor library 를 사용하여 hlsl -> spirv 로 변환하여 컴파일 합니다.
     
    2). DX12
    DXC library 를 사용하여 hlsl 을 컴파일 합니다.
     

    2.2.3.2. jShader Permutation
    Shader Permutation 은 hlsl 코드에 Preprosessor 를 추가해줍니다. Permutation 은 여러 종류가 조합될 수 있습니다. 아래 예제는 총 USE_VARIABLE_SHADING_RATE 와 USE_REVERSEZ 를 사용하여 4가지 조합 중 하나를 생성할 수 있는 jShaderForwardPixelShader 를 정의합니다. 그리고 이 Shader 를 사용하여 4가지 조합 중 하나의 조합을 사용하는 jShader 를 만드는 것을 볼 수 있습니다.

    // Header file
    struct jShaderForwardPixelShader : public jShader
    {
        DECLARE_DEFINE(USE_VARIABLE_SHADING_RATE, 0, 1);
        DECLARE_DEFINE(USE_REVERSEZ, 0, 1);
    
        // Declare Permutation
        using ShaderPermutation = jPermutation<USE_VARIABLE_SHADING_RATE, USE_REVERSEZ>;
        ShaderPermutation Permutation;
    
        DECLARE_SHADER_WITH_PERMUTATION(jShaderForwardPixelShader, Permutation)
    };
    
    // Source file
    IMPLEMENT_SHADER_WITH_PERMUTATION(jShaderForwardPixelShader
    	, "ForwardPS" // Shader name
    	, "Resource/Shaders/hlsl/shader_fs.hlsl" // Shader file path
    	, "" // Additional preprocessor
    	, "main" // Entry point
    	, EShaderAccessStageFlag::FRAGMENT) // Shader type
        
    // Use case
    // Shader compile with USE_VARIABLE_SHADING_RATE(0), USE_REVERSEZ(1) preprocessors.
    jShaderForwardPixelShader::ShaderPermutation ShaderPermutation;
    ShaderPermutation.SetIndex<jShaderForwardPixelShader::USE_VARIABLE_SHADING_RATE>(0);
    ShaderPermutation.SetIndex<jShaderForwardPixelShader::USE_REVERSEZ>(1);
    Shaders.PixelShader = jShaderForwardPixelShader::CreateShader(ShaderPermutation);

     
    2.2.3.3. Runtime hlsl update
    프로그램 실행 중에 hlsl 수정하여 저장하면 그 결과가 즉시 반영됩니다. 만약 수정한 hlsl 을 컴파일하는데 실패했다면 변경전의 hlsl 을 그대로 유지합니다.
    별도의 스레드에서 생성된 모든 Shader 에 대해서 수정여부를 판단합니다. (100 ms 마다 10 개 정도의 Shader 를 비교함) 수정된 Shader 는 컴파일이 성공할 때까지 갱신을 시도 합니다.

    extern bool GUseRealTimeShaderUpdate; // RealTimeShaderUpdate 활성화 여부
    extern int32 GMaxCheckCountForRealTimeShaderUpdate; // 매 업데이트 마다 체크할 Shader 의 최대 개수
    extern int32 GSleepMSForRealTimeShaderUpdate; // 매 업데이트 후 사용 할 Sleep MS

     
     

    [UML] jShader

     

    2.2.4. RenderPass

    RenderPass 는 Vulkan 에서 나온 개념으로 렌더타겟을 더 효율적으로 관리해 줄 수 있도록 하는 기능들을 갖고 있습니다. DX12 에는 Vulkan 의 렌더패스 기능의 다양한 기능을 사용하지 않지만 Attachment 들을 관리한다는 측면에서는 공통 되는 부분이 있습니다. jEngine 에서는 jRenderPass 라는  객체를 사용하며, 아래와 같은 정보를 가집니다.

    • Attachments (렌더타겟) 에 대한 정보들
    • Attachments 의 렌더링 할 영역 정보
    • Vulkan 은 Subpass 에 대한 정의 가능
    • Vulkan 은 VkRenderPass 라는 명시적인 객체가 존재함
    • Vulkan 은 VkFrameBuffer 객체를 소유하며, 이것은 RenderPass 내에서 사용될 렌더타겟에 대한 정보를 가짐

     

    [UML] jRenderPass

     
    기본 렌더패스의 구성은 아래와 같이 만들어집니다. 간단하게 단일 렌더패스로 구성된 Color, Depth render target 을 가지는 렌더패스 입니다.

    // Color, Depth, Resolve attachment 를 사용하는 렌더패스
    jRenderPassInfo renderPassInfo;
    
    // 1. Color Statchment 를 추가함
    const int32 ColorAttachmentIndex = (int32)renderPassInfo.Attachments.size();
    jAttachment color = jAttachment(RenderFrameContextPtr->SceneRenderTargetPtr->ColorPtr, EAttachmentLoadStoreOp::CLEAR_STORE
      , EAttachmentLoadStoreOp::DONTCARE_DONTCARE, ClearColor
      , EImageLayout::UNDEFINED, EImageLayout::COLOR_ATTACHMENT);
    renderPassInfo.Attachments.push_back(color);
    
    // 2. Depth Attachment 를 추가함.
    const int32 DepthAttachmentIndex = (int32)renderPassInfo.Attachments.size();
    jAttachment depth = jAttachment(RenderFrameContextPtr->SceneRenderTargetPtr->DepthPtr, EAttachmentLoadStoreOp::CLEAR_STORE
        , EAttachmentLoadStoreOp::CLEAR_STORE, ClearDepth
        , EImageLayout::UNDEFINED, EImageLayout::DEPTH_STENCIL_ATTACHMENT);
    renderPassInfo.Attachments.push_back(depth);
    
    // 3. Resolve Attachment 는 MSAA 를 사용하는 경우 최종 결과를 할 렌더타겟을 설정합니다.
    const int32 ResolveAttachemntIndex = (int32)renderPassInfo.Attachments.size();
    if ((int32)g_rhi->GetSelectedMSAASamples() > 1)
    {
    		resolve = jAttachment(RenderFrameContextPtr->SceneRenderTargetPtr->ResolvePtr, EAttachmentLoadStoreOp::DONTCARE_STORE
            , EAttachmentLoadStoreOp::DONTCARE_DONTCARE, ClearColor
            , EImageLayout::UNDEFINED, EImageLayout::COLOR_ATTACHMENT, true);
      renderPassInfo.Attachments.push_back(resolve);
    }
    
    // 4. jRenderPassInfo 와 Attachment 에서 렌더링할 Area 를 넘겨주서 RenderPass 객체를 생성합니다.
    jRenderPass* BaseRenderPass = g_rhi->GetOrCreateRenderPass(renderPassInfo, { 0, 0 }, { SCR_WIDTH, SCR_HEIGHT });

     
    서브패스를 사용하는 렌더패스 구성은 아래와 같이 만들 수 있습니다. 아래 렌더패스는 디퍼드 렌더링 과정에서 GBuffer 생성 단계를 첫번째 Subpass 로, 라이팅 단계를 두번째 Subpass 로 지정하는 예제입니다.
     

    // Setup attachment
    jRenderPassInfo renderPassInfo;
    
    // This color attachment will be used as render target in first subpass, then input attachment in second subpass
    for (int32 i = 0; i < _countof(RenderFrameContextPtr->SceneRenderTargetPtr->GBuffer); ++i)
    {
      jAttachment color = jAttachment(RenderFrameContextPtr->SceneRenderTargetPtr->GBuffer[i], EAttachmentLoadStoreOp::CLEAR_STORE
        , EAttachmentLoadStoreOp::DONTCARE_DONTCARE, ClearColor
        , EImageLayout::UNDEFINED, EImageLayout::COLOR_ATTACHMENT);
      renderPassInfo.Attachments.push_back(color);
    }
    
    // This color attachment will be used as render target in second subpass.
    const int32 LightPassAttachmentIndex = (int32)renderPassInfo.Attachments.size();
    if (gOptions.UseSubpass)
    {
      jAttachment color = jAttachment(RenderFrameContextPtr->SceneRenderTargetPtr->ColorPtr, EAttachmentLoadStoreOp::CLEAR_STORE
        , EAttachmentLoadStoreOp::DONTCARE_DONTCARE, ClearColor
        , EImageLayout::UNDEFINED, EImageLayout::COLOR_ATTACHMENT);
      renderPassInfo.Attachments.push_back(color);
    }
    
    // This is depth attachment
    const int32 DepthAttachmentIndex = (int32)renderPassInfo.Attachments.size();
    jAttachment depth = jAttachment(RenderFrameContextPtr->SceneRenderTargetPtr->DepthPtr, EAttachmentLoadStoreOp::CLEAR_STORE
      , EAttachmentLoadStoreOp::CLEAR_STORE, ClearDepth
      , EImageLayout::UNDEFINED, EImageLayout::DEPTH_STENCIL_ATTACHMENT);
    renderPassInfo.Attachments.push_back(depth);
    
    //////////////////////////////////////////////////////////////////////////
    // Setup subpass of BasePass
    
    // First subpass, Geometry pass
    {
      jSubpass subpass;
      // Subpass index 0 -> 1
      subpass.Initialize(0, 1, EPipelineStageMask::COLOR_ATTACHMENT_OUTPUT_BIT, EPipelineStageMask::FRAGMENT_SHADER_BIT);
      
      // First subpass will use GBuffer & Depth Attachment
      const int32 GBufferCount = LightPassAttachmentIndex;
      for (int32 i = 0; i < GBufferCount; ++i)
      {
        subpass.OutputColorAttachments.push_back(i);
      }
      
      subpass.OutputDepthAttachment = DepthAttachmentIndex;
      renderPassInfo.Subpasses.push_back(subpass);
    }
    
    // Second subpass, Lighting pass
    {
      jSubpass subpass;
      // Subpass index 1 -> 2
      subpass.Initialize(1, 2, EPipelineStageMask::COLOR_ATTACHMENT_OUTPUT_BIT, EPipelineStageMask::FRAGMENT_SHADER_BIT);
      
      // Second subpass will use GBuffer as InputAttachments
      const int32 GBufferCount = LightPassAttachmentIndex;
      for (int32 i = 0; i < GBufferCount; ++i)
      {
        subpass.InputAttachments.push_back(i);
      }
      // Second usbpass will use DepthAttchment and ColorAttachment(at LightPassAttachmentIndex)
      subpass.OutputColorAttachments.push_back(LightPassAttachmentIndex);
      subpass.OutputDepthAttachment = DepthAttachmentIndex;
     
      renderPassInfo.Subpasses.push_back(subpass);
    }
    //////////////////////////////////////////////////////////////////////////
    BaseRenderPass = (jRenderPass_Vulkan*)g_rhi->GetOrCreateRenderPass(renderPassInfo, { 0, 0 }, { SCR_WIDTH, SCR_HEIGHT });

     
    jEngine 에서 아래 그림의 UseSubpass 체크박스를 설정하는 방식으로 Subpass 를 사용한 렌더링을 확인 할 수 있습니다. Subpass 를 사용하는 경우 약 0.02ms 정도의 GPU time 이 줄어든 것을 볼 수 있습니다. 현재는 간단한 예제라 큰 차이가 없지만 퍼포먼스 차이는 장면, 서브패스 설정에 따라 달라 질 수 있을 것 입니다.

    좌측이 Subpass 를 사용하지 않는 경우, 우측이 Subpass 를 사용한 경우

     
     

    2.2.5. PipelineState

    PipelineState 는 이름 그대로 렌더링 파이프라인 스테이트를 모두 모아놓은 객체입니다. Vulkan 은 VkPipieline, DX12 은 ID3D12PipelineState 입니다. jEngine 에서는 PipelineState 에서는 드로콜마다 자주 변할 수 있는 Felxible State 와 그렇지 않은 Fixed State 를 구분합니다. jPipelineStateInfo 는 아래 코드와 같이 구성됩니다.

    struct jPipelineStateInfo
    {
    ...
        bool IsGraphics = true;
        const jGraphicsPipelineShader GraphicsShader;
        const jShader* ComputeShader = nullptr;
        const jRenderPass* RenderPass = nullptr;
        jVertexBufferArray VertexBufferArray;
        jShaderBindingLayoutArray ShaderBindingLayoutArray;
        const jPushConstant* PushConstant;
        const jPipelineStateFixedInfo* PipelineStateFixed = nullptr;
        int32 SubpassIndex = 0;
    ...
    };

     
     
    2.2.5.1. Fixed State
    jPipelineStateFixedInfo 가 Fixed State 를 관리합니다. Fixed State 는 아래와 같은 항목들을 포함합니다.

    • RasterizationState
    • DepthStencilState
    • BlendingState
    • Viewport, Scissor

    Rasterization, DetphStencil, Blending State 의 경우 템플릿 함수로 생성 가능합니다. 아래는 TRasterizationStateInfo 함수와 jRasterizationStateInfo 를 생성하는 예제입니다.

    // Instanciation of jRasterizationState
    RasterizationState = TRasterizationStateInfo<
    	EPolygonMode::FILL, ECullMode::BACK, EFrontFace::CCW, 
        false, 0.0f, 0.0f, 0.0f, 1.0f, true, false, (EMSAASamples)1, 
        true, 0.2f, false, false>::Create();

     
    2.2.5.2. Felxible State
    Felxible State 는 jPipelineStateInfo 를 생성 할 때 바로 넣어줍니다. Felxible State 는 아래와 같은 요소가 있습니다.

    • Shader
    • VertexBuffers
    • jShaderBindingLayoutArray : 앞서봤던 '2.2.1.2.  jShaderBindingLayout' 의 배열이며 Shader 에 대한 시그니쳐 정보들을 제공합니다. 이것을 통해 Vulkan 은 PipelineLayout, DX12 는 RootSignature 를 생성합니다.
    • jRenderPass : Attachment(렌더타겟)에 대한 정보가 담겨있습니다. 자세한 설명은 '2.2.4. RenderPass' 항목을 참고해주세요.
    • PushConstant(Vulkan 에서만 사용) : 동적으로 Constant buffer 를 Shader 에 주입할 수있는 기능

    [UML] jPipelineStateInfo

     
    아래 그림은 Fixed State 를 생성하여 여러 jDrawCommand 에서 공통으로 사용하고, Flexible State 는 각 jDrawCommand 의 PrepareToDraw 내부에서 상황에 따라 조합하여 jPipelineStateInfo 를 생성하는 과정을 담고 있습니다.

     
     
    2.2.6. jObject, jRenderObject

    jRenderObject 는 렌더링 하는데 필요한 데이터를 모아둔 정보입니다. 여기는 위치, 회전, 스케일 그리고 버택스, 인덱스 버퍼와 같은 데이터를 포함합니다.
    버택스와 인덱스 버퍼와 같은 지오메트리 데이터는 jRenderObjectGeometryData 에 담겨있습니다.
    jMeshObject 객체는 파일로 부터 로드한 메시를 사용하여 렌더링 할 수 있습니다. 이 객체는 jMeshData 를 가지고 있고 이 데이터를 기반으로 렌더링 할 오브젝트를 준비합니다.
    jRenderObjectElement 는 jRenderObject한개의 버택스 버퍼에서 그 버퍼의 일부분을 렌더링 할 수 있는 서브메시 렌더링 기능을 사용할 수 있게 됩니다.

    [UML] jObject, jRenderObject

     
    2.2.7. jDrawCommand

    jDrawCommand 는 실제 API DrawCall 에 필요한 모든 데이터를 소유합니다. jRenderObject 와 1:1 대응됩니다. jDrawCommand 는 앞에서 봤었던 jPipelineState 와 jRenderPass, jShaderBindingInstance, jShader, jMaterial 등 모든 데이터를 가지며, 렌더링 할 View 정보인 jView 도 가집니다.
     

    추가로 사용하는 것들

    • jRenderFrameContext 객체도 추가로 가집니다. 이 객체는 현재 렌더링하고 있는 Frame 에 대한 정보를 가집니다. 이 객체는 현재 렌더링하고 있는 프레임의 렌더타겟 정보와 현재 활성화 된 jCommandBuffer(DX12 : CommandList, Vulkan : CommandBuffer) 정보를 갖고 있습니다.
    • jShaderBindingInstanceCombiner 객체도 추가로 소개됩니다. 이 객체는 Vulkan 을 위한 전용 객체입니다. Vulkan 의 'DynamicOffset on Descriptor Sets' 최적화를 적용하기 위해서 사용하며, 매 드로콜 마다 서로 다른 UniformBuffer 를 바인딩 하는 경우를 위한 최적화 입니다. 이 방식을 통해서 VkWriteDescriptorSet 과 vkUpdateDescriptorSet 호출 빈도를 줄일 수 있습니다. Vulkan 의 UniformBuffer 는 전역 버퍼로부터 할당 받으며, 이 때 전역 버퍼의 주소와 Offset 을 전달 받습니다. 이것을 VkCmdBindDescriptorSets 을 호출 할 때 전달합니다. ‘DynamicOffset on Descriptor Sets’ 최적화에 대한 설명과 퍼포먼스 테스트 결과는 다음 링크에서 찾을 수 있습니다 : GDC19: Galaxy GameDev: Bringing Maximum Boost to Mobile Games

    [UML] jDrawCommand

     
    특정 타입의 jDrawCommand 를 생성하는 jDrawCommandGenerator 를 제작 할 수도 있습니다. 아래의 예제는 디퍼드 렌더링 이후에 Lighting pass 에 대한 jDrawCommandGenerator 를 보여줍니다.

    [UML] jDrawCommandGenerator

     
     
    2.2.8. jCommandBuffer
    jCommandBuffer 는 렌더 커맨드를 기록한 뒤 Queue 에 전달 하여 렌더 커맨드가 실행되도록 합니다. jCommandBuffer 는 jFence 를 보유합니다. 이 Fence 를 사용하여 jCommandBuffer 가 실행 완료 되었는지 확인할 수 있습니다. 실행이 완료되는 경우 jCommandBufferManager 에 의해서 jCommandBuffer 는 재사용 될 수 있습니다.

    [UML] jCommandBuffer, jCommandBufferManager

     
    2.2.9. jTexture

    이미지 파일로 부터 jTexture 생성하려면 jImageFileLoader 를 사용합니다. jTexture 는 jRenderTarget 과 jSwapchain 을 생성하는데도 사용됩니다.
     
    DX12 에서는 Swapchain 를 Present 할 때 jSwapchain 이 소유한 의 Fence 를 사용하여 Signal 함수를 호출합니다. 그리고 새로운 렌더 프레임에서 이번에 사용할 Swapchain 를 가져오려고 할 때, 그 Fence 까지 실행되었는지 GetCompletedValue 함수로 확인 합니다. 실행을 대기해야 한다면 대기한 뒤 Swapchain 을 사용합니다. Vulkan 의 경우 이런 과정을 jSemaphore 로 처리하며 vkQueuePresentKHR 함수에 같이 전달합니다.

    [UML] jTexture

     

    2.2.10. jResourceBarrierBatcher

    ResourceBarrier 를 한번에 처리하기 위해 추가된 클래스 입니다. 각 jCommandBuffer 는 자신만의 ResourceBarrierBatcher 를 가집니다. ResourceBarrier 는 각각의 CommandQueue 와 관련되어 있기 때문에 이렇게 구성되는게 좋다고 생각합니다. 추가로 Buffer 나 Texture 를 생성하는 중에는 jCommandBuffer 를 사용할 수 없기 때문에 GlobalBarrierBatcher 를 추가로 둡니다. 누적한 Barrier 를 실행하는 타이밍은 RenderPassBegin, CopyBuffer or Texture, Draw, Dispatch, DispatchRay 가 호출되는 경우 입니다.

    [UML] jResourceBarrierBatcher

     

     

    3. Third party libraries

    ImGUI : https://github.com/ocornut/imgui

    assimp : https://github.com/assimp/assimp

    cityhash : https://github.com/google/cityhash

    DirectXTex : https://github.com/microsoft/DirectXTex

    GLFW : https://www.glfw.org/

    loadpng : https://github.com/lvandeve/lodepng

    robin-hood-hashing : https://github.com/martinus/robin-hood-hashing

    glslang : https://github.com/KhronosGroup/glslang

    ShaderConductor : https://github.com/microsoft/ShaderConductor

    DXC : https://github.com/microsoft/DirectXShaderCompiler

    stb : https://github.com/nothings/stb

    WinPixEventRuntime : https://devblogs.microsoft.com/pix/winpixeventruntime/

    xxHash : https://github.com/Cyan4973/xxHash



    'Graphics > Renderer Lab' 카테고리의 다른 글

    jEngine Library 빌드 관련  (0) 2023.12.29

    댓글

Designed by Tistory & scahp.