공부중

[DirectX12]Hello Wolrd Sample - D3D12HelloTexture - 3 본문

Programing/DirectX

[DirectX12]Hello Wolrd Sample - D3D12HelloTexture - 3

곤란 2018. 6. 3. 17:25
반응형
void D3D12HelloTexture::OnInit()
{
	LoadPipeline();
	LoadAssets();
}

 

저번 글에서는 LoadPipeline에 관해서 적었다.

이번 글에서는 LoadAssets안을 살펴보도록 하자.

 

// Load the sample assets.
void D3D12HelloTexture::LoadAssets()
{
	// Create the root signature.
	{
		D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

		// This is the highest version the sample supports. 
		//If CheckFeatureSupport succeeds, the HighestVersion returned will not be greater than this.
		featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

		if (FAILED(m_device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
		{
			featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
		}

		CD3DX12_DESCRIPTOR_RANGE1 ranges[1];
		ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);

		CD3DX12_ROOT_PARAMETER1 rootParameters[1];
		rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);

		D3D12_STATIC_SAMPLER_DESC sampler = {};
		sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
		sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.MipLODBias = 0;
		sampler.MaxAnisotropy = 0;
		sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
		sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
		sampler.MinLOD = 0.0f;
		sampler.MaxLOD = D3D12_FLOAT32_MAX;
		sampler.ShaderRegister = 0;
		sampler.RegisterSpace = 0;
		sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

		CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
		rootSignatureDesc.Init_1_1(_countof(rootParameters), 
					   rootParameters, 
					   1, 
					   &sampler, 
					   D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

		ComPtr signature;
		ComPtr error;
		ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, 
								    featureData.HighestVersion, 
								    &signature, &error));
		ThrowIfFailed(m_device->CreateRootSignature(0, 
							    signature->GetBufferPointer(), 
							    signature->GetBufferSize(), 
							    IID_PPV_ARGS(&m_rootSignature)));
	}

	// Create the pipeline state, which includes compiling and loading shaders.
	{
		ComPtr vertexShader;
		ComPtr pixelShader;

#if defined(_DEBUG)
		// Enable better shader debugging with the graphics debugging tools.
		UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
		UINT compileFlags = 0;
#endif

		ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), 
						 nullptr, 
						 nullptr, 
						 "VSMain", 
						 "vs_5_0", 
						 compileFlags, 
						 0, 
						 &vertexShader, 
						 nullptr));
		ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), 
						 nullptr, 
						 nullptr, 
						 "PSMain", 
						 "ps_5_0", 
						 compileFlags, 
						 0, 
						 &pixelShader, 
						 nullptr));

		// Define the vertex input layout.
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
		};

		// Describe and create the graphics pipeline state object (PSO).
		D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
		psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
		psoDesc.pRootSignature = m_rootSignature.Get();
		psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
		psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
		psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
		psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
		psoDesc.DepthStencilState.DepthEnable = FALSE;
		psoDesc.DepthStencilState.StencilEnable = FALSE;
		psoDesc.SampleMask = UINT_MAX;
		psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
		psoDesc.NumRenderTargets = 1;
		psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
		psoDesc.SampleDesc.Count = 1;
		ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState)));
	}

	// Create the command list.
	ThrowIfFailed(m_device->CreateCommandList(0, 
						  D3D12_COMMAND_LIST_TYPE_DIRECT, 
						  m_commandAllocator.Get(), 
						  m_pipelineState.Get(), 
						  IID_PPV_ARGS(&m_commandList)));

	// Create the vertex buffer.
	{
		// Define the geometry for a triangle.
		Vertex triangleVertices[] =
		{
			{ { 0.0f, 0.25f * m_aspectRatio, 0.0f }, { 0.5f, 0.0f } },
			{ { 0.25f, -0.25f * m_aspectRatio, 0.0f }, { 1.0f, 1.0f } },
			{ { -0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 1.0f } }
		};

		const UINT vertexBufferSize = sizeof(triangleVertices);

		// Note: using upload heaps to transfer static data like vert buffers is not 
		// recommended. Every time the GPU needs it, the upload heap will be marshalled 
		// over. Please read up on Default Heap usage. An upload heap is used here for 
		// code simplicity and because there are very few verts to actually transfer.
		ThrowIfFailed(m_device->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
			D3D12_HEAP_FLAG_NONE,
			&CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize),
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&m_vertexBuffer)));

		// Copy the triangle data to the vertex buffer.
		UINT8* pVertexDataBegin;
		CD3DX12_RANGE readRange(0, 0);		// We do not intend to read from this resource on the CPU.
		ThrowIfFailed(m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
		memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
		m_vertexBuffer->Unmap(0, nullptr);

		// Initialize the vertex buffer view.
		m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
		m_vertexBufferView.StrideInBytes = sizeof(Vertex);
		m_vertexBufferView.SizeInBytes = vertexBufferSize;
	}

	// Note: ComPtr's are CPU objects but this resource needs to stay in scope until
	// the command list that references it has finished executing on the GPU.
	// We will flush the GPU at the end of this method to ensure the resource is not
	// prematurely destroyed.
	ComPtr textureUploadHeap;

	// Create the texture.
	{
		// Describe and create a Texture2D.
		D3D12_RESOURCE_DESC textureDesc = {};
		textureDesc.MipLevels = 1;
		textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		textureDesc.Width = TextureWidth;
		textureDesc.Height = TextureHeight;
		textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
		textureDesc.DepthOrArraySize = 1;
		textureDesc.SampleDesc.Count = 1;
		textureDesc.SampleDesc.Quality = 0;
		textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;

		ThrowIfFailed(m_device->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
			D3D12_HEAP_FLAG_NONE,
			&textureDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(&m_texture)));

		const UINT64 uploadBufferSize = GetRequiredIntermediateSize(m_texture.Get(), 0, 1);

		// Create the GPU upload buffer.
		ThrowIfFailed(m_device->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
			D3D12_HEAP_FLAG_NONE,
			&CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&textureUploadHeap)));

		// Copy data to the intermediate upload heap and then schedule a copy 
		// from the upload heap to the Texture2D.
		std::vector texture = GenerateTextureData();

		D3D12_SUBRESOURCE_DATA textureData = {};
		textureData.pData = &texture[0];
		textureData.RowPitch = TextureWidth * TexturePixelSize;
		textureData.SlicePitch = textureData.RowPitch * TextureHeight;

		UpdateSubresources(m_commandList.Get(), m_texture.Get(), textureUploadHeap.Get(), 0, 0, 1, &textureData);
		m_commandList->ResourceBarrier(1, 
					       &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), 
										     D3D12_RESOURCE_STATE_COPY_DEST, 
										     D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));

		// Describe and create a SRV for the texture.
		D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
		srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
		srvDesc.Format = textureDesc.Format;
		srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
		srvDesc.Texture2D.MipLevels = 1;
		m_device->CreateShaderResourceView(m_texture.Get(), &srvDesc, m_srvHeap->GetCPUDescriptorHandleForHeapStart());
	}
	
	// Close the command list and execute it to begin the initial GPU setup.
	ThrowIfFailed(m_commandList->Close());
	ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
	m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

	// Create synchronization objects and wait until assets have been uploaded to the GPU.
	{
		ThrowIfFailed(m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
		m_fenceValue = 1;

		// Create an event handle to use for frame synchronization.
		m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
		if (m_fenceEvent == nullptr)
		{
			ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
		}

		// Wait for the command list to execute; we are reusing the same command 
		// list in our main loop but for now, we just want to wait for setup to 
		// complete before continuing.
		WaitForPreviousFrame();
	}
}

</void**>

이거 너무 변경사항이 많고 코드도 엄청 길어졌다 -_-;;; (으으.....)

여기서는 위에서부터 차근차근 살펴봐야될것같다.

 

	// Create the root signature.
	{
		D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

		// This is the highest version the sample supports. 
		// If CheckFeatureSupport succeeds, the HighestVersion returned will not be greater than this.
		featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

		if (FAILED(m_device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
		{
			featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
		}

		CD3DX12_DESCRIPTOR_RANGE1 ranges[1];
		ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);

		CD3DX12_ROOT_PARAMETER1 rootParameters[1];
		rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);

		D3D12_STATIC_SAMPLER_DESC sampler = {};
		sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
		sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.MipLODBias = 0;
		sampler.MaxAnisotropy = 0;
		sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
		sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
		sampler.MinLOD = 0.0f;
		sampler.MaxLOD = D3D12_FLOAT32_MAX;
		sampler.ShaderRegister = 0;
		sampler.RegisterSpace = 0;
		sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

		CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
		rootSignatureDesc.Init_1_1(_countof(rootParameters), 
					   rootParameters, 
					   1, 
					   &sampler, 
					   D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

		ComPtr signature;
		ComPtr error;
		ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, 
								    featureData.HighestVersion, 
								    &signature, &error));
		ThrowIfFailed(m_device->CreateRootSignature(0, 
							    signature->GetBufferPointer(), 
							    signature->GetBufferSize(), 
							    IID_PPV_ARGS(&m_rootSignature)));
	}

 

root signature를 생성하는 부분이다. 여기서부터 변경점이 엄청 많다.

이전 프로젝트는 공백 포함 약 7줄이였던것이 엄청 늘어났다 -_-,,,

 

하나하나 살펴보도록 하자.

 

먼저 D3D12_FEATURE_DATA_ROOT_SIGNATURE 의 구조체를 생성했다.

저 D3D12_FEATURE_DATA_ROOT_SIGNATURE가 어떤 구조체인가 살펴보니

MSDN에서도 대놓고 값 채워서 CheckFeatureSupport에 넘겨주어서 root signature 버전 지원 확인용으로 쓴다고 적혀있다.

D3D12_FEATURE_DATA_ROOT_SIGNATURE structure - MSDN

 

D3D_ROOT_SIGNATURE_VERSION_1_1 을 플래그로 넣어놓고 디바이스에서 CheckFeatureSupport에 넘겨주고 있다.

CheckFeatureSupport 메소드는 현재 그래픽 드라이버가 지원하는 기능에 대한 정보를 가져오는 메소드 이다.

여기서는 D3D12_FEATURE_ROOT_SIGNATURE를 넘겨주면서 root signature 버전에대한 체크를 하는데 

다른 플래그들을 넘겨주어서 현재 그래픽 드라이버가 지원하는지 확인이 가능하다

(ex - D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS 라던가... 관련된 내용은 enum D3D12_FEATURE를 보면 된다.)

D3D12_FEATURE enumeration - MSDN

 

CheckFeatureSupport를 이용해서 실패시 if문 안의 코드가 실행되며 한단계 버전을 내리는 것을 볼 수 있다.

 

다음 라인을 들어가기전에 root signature(루트 서명)에 대해서 다시 정리를 해보려고 한다.

root signature는 그리기 명령을 제출 하기 전에 파이프라인에 묶어야 할 자원들이 무엇이고 

그 자원들이 셰이더 입력 레지스터들에 어떻게 대응되는지를 정의 하는 녀석이다.

 

descriptor table(서술자 테이블) root parameter(루트 매개변수)를  정의하려면 

D3D12_ROOT_PARAMETER의 DescriptorTable 멤버를 채워서 descriptor table에 대한 좀 더 구체적인 정보를 제공해야 한다.

 

 

    //
    // filename : d3d12.h
    //

    typedef struct D3D12_ROOT_PARAMETER1
    {
    D3D12_ROOT_PARAMETER_TYPE ParameterType;
    union 
        {
        D3D12_ROOT_DESCRIPTOR_TABLE1 DescriptorTable;
        D3D12_ROOT_CONSTANTS Constants;
        D3D12_ROOT_DESCRIPTOR1 Descriptor;
        } 	;
    D3D12_SHADER_VISIBILITY ShaderVisibility;
    } 	D3D12_ROOT_PARAMETER1;

    // 생략....

typedef struct D3D12_ROOT_DESCRIPTOR_TABLE1
    {
    UINT NumDescriptorRanges;
    _Field_size_full_(NumDescriptorRanges)  const D3D12_DESCRIPTOR_RANGE1 *pDescriptorRanges;
    } 	D3D12_ROOT_DESCRIPTOR_TABLE1;


 

D3D12_ROOT_DESCRIPTOR_TABLE은 D3D12_DESCRIPTOR_RANGE 배열과 배열의 구간(range) 갯수를 지정하면 된다.

 

DEFINE_ENUM_FLAG_OPERATORS( D3D12_DESCRIPTOR_RANGE_FLAGS );
typedef struct D3D12_DESCRIPTOR_RANGE1
    {
    D3D12_DESCRIPTOR_RANGE_TYPE RangeType;
    UINT NumDescriptors;
    UINT BaseShaderRegister;
    UINT RegisterSpace;
    D3D12_DESCRIPTOR_RANGE_FLAGS Flags;
    UINT OffsetInDescriptorsFromTableStart;
    } 	D3D12_DESCRIPTOR_RANGE1;

 

D3D12_DESCRIPTOR_RANGE1의 구조체는 위와같이 되어있고.

코드에 보면 구조체 이름이 CD3DX12_DESCRIPTOR_RANGE 라는 구조체를 사용했는데 이것은 D3D12_DESCRIPTOR_RANGE를 가지고

편의용 메서드들을 추가한 구조체이다.

CD3DX12_DESCRIPTOR_RANGE1 structure - MSDN

위의 코드에 있는 Init과 그외 궁금한것들은 MSDN을 보자.

 

아무튼 이러한 이유로 RANGE1 구조체로 먼저 만들어서 Init 하고

위의 구조체와 같이 편의용으로 만들어진 CD3DX12_ROOT_PARAMETER1 형의 구조체를 가지고 DescriptorTable을 Init 해주고 있다.

CD3DX12_ROOT_PARAMETER1 structure - MSDN

역시 그외 궁금한것은 MSDN을 보도록 하자.

 

 

여기서도 다음 코드 라인으로 들어가기 전에 한가지를 설명 들어가야 할것 같다.

보이는것은 D3D12_STATIC_SAMPLER_DESC 라는 구조체인데 이것은 정적 표본추출기(static sampler)라고 부른다.

 

먼저 표본추출기(sampler)라는게 뭔지 부터 알아봐야 할것 같다.

텍스쳐 자원에서 표본을 추출할때 구체적으로 어떤 필터링 방식과 좌표 지정 모드라는것을 적용한지 결정하기 위해서

표본추출기 객체(sampler object)로 지정을 한다.

 

여기서 정적 표본추출기(static sampler)는 이름대로 표본추출기 힙을 생성하지 않고도 설정이 가능하도록 

쓸 수 있는것이라고 생각하면 되겠다.

그냥 구조체의 정보가 궁금하면 D3D12_STATIC_SAMPLER_DESC - MSDN로 링크타고 보도록 하자

 

D3D12_STATIC_SAMPLER_DESC 형으로 sampler를 만든 뒤에 값을 넣어주고 있다.

필터는 D3D12_FILTER_MIN_MAG_MIP_POINT 를 넣어주었는데 "축소, 배율 및 밉 레벨 샘플링에 포인트 샘플링을 사용합니다."라는

의미이다 필터 종류에 대해서 더 많은것을 보고 싶으면 D3D12_FILTER enumeration - MSDN 을 참조하자.

 

그리고 다음 라인에 텍스쳐의 좌표 지정 모드를 어떻게 할것인지 정해주는 부분이고

UVW는 U(수평), V(수직) W(깊이)를 나타낸다. 3ds max 도움말에서도 나온다. - 링크

 

넘겨주는 값에 대해서는 D3D12_TEXTURE_ADDRESS_MODE enumeration - MSDN를 참조하도록 하자.

 

나머지 값들에 대해서는 위에서 적은 링크들을 살펴보도록 하자...

 

 

다음라인은 CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC 라는 구조체로 만들어줬는데

이것은 D3D12_VERSIONED_ROOT_SIGNATURE_DESC의 편의용 버전이라고 생각하면 되고

D3D12_VERSIONED_ROOT_SIGNATURE_DESC는 아래와 같이 생겨먹었다.

 

typedef struct D3D12_VERSIONED_ROOT_SIGNATURE_DESC
    {
    D3D_ROOT_SIGNATURE_VERSION Version;
    union 
        {
        D3D12_ROOT_SIGNATURE_DESC Desc_1_0;
        D3D12_ROOT_SIGNATURE_DESC1 Desc_1_1;
        } 	;
    } 	D3D12_VERSIONED_ROOT_SIGNATURE_DESC;

 

보면 버전에 따라서 1_0을 사용할지 1_1을 사용할지 결정을 할 수가 있고

CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC의 Init을 통해서 버전별로 Init을 선택해서 사용할 수가 있다.

 

ID3DBlob으로 signature와 error을 만들어 놓은뒤 D3DX12SerializeVersionedRootSignature를 이용해서 

RootSignature를 생성하기 전에 만들어둔것들을 Serialize(직렬화) 해주고 있다.

D3DX12SerializeVersionedRootSignature function - MSDN 을 보니 해당 함수는 버전을 따로 선택해서 call해줄 필요없이 최신 버전(1_1)을 지원하지 않으면 1_0으로 내려서 만들어 주는것 같다.

그리고 이 함수는 생긴지 얼마 안된 함수라고 밑에 적혀있다. (Windows 10 version 14393에 만들어짐) 

자세한 내용은 역시 위의 링크 MSDN을 살펴보도록 하자 -_-;;;

 

드디어 마지막으로 디바이스에서 RootSignature를 생성해 주는것으로 해당 블록은 끝이 났다.

 

대충 약 7줄이였던것이 이렇게 길어지고 처음보는것들과 다시 들으면 잊어버릴만한 것들 뭐 결국 내가 다시 볼예정인데

뭔가 너무 중구난방으로 정리가 되어버렸다 -_-;;;

 

아무튼 다음 글은 파이프라인 상태와 셰이더 로딩에 대해서 적을 예정이다.

 

 

다음 글에서.....

 

 

 

 

 

 

 

반응형