diff --git a/.gitignore b/.gitignore index 2985a8c5..b478653e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ CTestTestfile.cmake _deps .vs out -docs \ No newline at end of file +.codex_temp \ No newline at end of file diff --git a/Multithreading.md b/Multithreading.md index 71b1207f..03e329de 100644 --- a/Multithreading.md +++ b/Multithreading.md @@ -58,7 +58,7 @@ SH_RENDER_API void Renderer::Sync() std::swap(drawcall[core::ThreadType::Game], drawcall[core::ThreadType::Render]); } ``` -> swap방식은 메모리를 추가로 먹지만 매우 빠르게 작동하니 성능에 중요한 곳에서 사용하면 좋습니다. +> swap방식은 메모리를 추가로 먹지만 매우 빠르게 작동하니 성능에 중요한 곳에서 사용하면 됩니다. # 스레드 풀 core모듈의 ThreadPool클래스를 이용하여 작업을 할당하고 future로 값을 받아올 수 있습니다. @@ -89,3 +89,18 @@ for (auto& renderPipeline : renderPipelines) for (auto& futureCommand : futureCommands) recordedCommands.push_back(futureCommand.get()); ``` +# MPSC큐 +MSPC큐는 Multi Producer Single Consumer큐의 약자로, 다수의 스레드에서 쓰기 작업을 수행하고, 하나의 스레드에서 읽기 작업을 하는데 특화된 락-프리 큐입니다. + +```cpp +core::LockFreeMPSCQueue requestID; +// 다수의 스레드에서 실행 +requestID.Push(threadID); +// 하나의 스레드에서 읽어오기 +requestID.Drain( + [this](int threadID) + { + SH_INFO_FORMAT("Request: {}", threadID); + } +) +``` diff --git a/README.md b/README.md index 0a66cca9..b7409b6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # ShellEngine image +스크린샷 2026-05-11 012031 + + [![Video Label](http://img.youtube.com/vi/SEiktv0WtOM/0.jpg)](https://youtu.be/SEiktv0WtOM)
[간단한 에디터 데모 영상(Youtube)](https://youtu.be/SEiktv0WtOM) diff --git a/RenderArchitecture.md b/RenderArchitecture.md index 21ad01f1..9aa66dbf 100644 --- a/RenderArchitecture.md +++ b/RenderArchitecture.md @@ -1,161 +1,251 @@ -# ShellEngine 패스 기반 렌더링 구조 +# ShellEngine 패스 기반 렌더링 구조 ## 개요 +ShellEngine의 렌더링은 `RenderData`가 정의한 뷰/타겟 단위 작업을 `ScriptableRenderer`가 패스 목록으로 풀고, 각 `ScriptableRenderPass`가 자신의 리소스 사용 의도를 선언한 뒤, 백엔드 `CommandBuffer`가 실제 Vulkan 커맨드로 기록하는 구조입니다. + | 계층 | 책임 | |---|---| -| `ScriptableRenderPass` | 렌더링 의도 선언 (타겟, 샘플링 텍스처, 드로우 목록) | -| `ScriptableRenderer` | 패스 간 리소스 상태 추적 및 전이 정보 계산 | -| Vulkan 구현 계층 | 전이 정보를 실제 Vulkan API 호출로 번역 | - -ShellEngine의 Vulkan 렌더링 시스템은 드로우 호출을 단일 흐름으로 나열하는 방식 대신, **패스 단위로 렌더링 작업을 분리**하고 각 패스가 사용하는 렌더 텍스처와 일반 텍스쳐의 읽기/쓰기 관계를 추적하여, 리소스 전이와 실행 흐름을 자동으로 조립하는 구조로 설계되어 있습니다. +| `Renderer` | 렌더 스레드 진입점, drawable/render data 큐 반영, 커맨드 제출 조율 | +| `RenderDataManager` | 카메라/뷰 데이터를 GPU 버퍼에 업로드하고 `RenderData`를 priority 순으로 정렬 | +| `ScriptableRenderer` | 활성 패스 선택, render queue 정렬, 리소스 전이 계산, 패스별 커맨드 버퍼 기록 | +| `ScriptableRenderPass` | 패스 이름, render queue, 렌더 타겟/샘플링 텍스처 사용 의도, 드로우 기록 | +| `CommandBuffer` | 배리어, 렌더 타겟 설정, draw/dispatch/blit 같은 백엔드 독립 커맨드 인터페이스 | +| Vulkan 구현 계층 | `CommandBuffer` 호출을 dynamic rendering, pipeline bind, descriptor bind, barrier 호출로 변환 | -해당 구조로 인해 프로젝트 목적에 맞게 코드를 크게 수정하지 않고 패스를 재배치 할 수 있으며 멀티스레딩과도 자연스럽게 연결됩니다. +핵심은 패스가 “무엇을 렌더링하고 어떤 이미지를 읽고 쓰는지”만 표현하고, 이미지 레이아웃 전이와 Vulkan 동기화 코드는 `ScriptableRenderer`와 `VulkanCommandBuffer`가 처리한다는 점입니다. -## 멀티스레딩 -`ScriptableRenderer`에서 렌더큐에 맞는 패스 순서를 조정하고 리소스 의존 상태를 계산 후, 스레드 풀에 넣어 각 패스의 렌더링을 병렬적으로 수행합니다. +## 프레임 흐름 -1. **직렬 단계** - 중앙에서 리소스 상태 정리 및 의존 상태 계산 -2. **병렬 단계** - 각 패스의 커맨드 버퍼 기록을 스레드 풀에 task로 분배하여 병렬 실행 +![프레임 흐름](/docs/RenderFrameFlow.svg) -리소스 전이 판단이 중앙에서 선행되므로, 이후 커맨드 기록 단계는 패스 간 의존성 없이 병렬화할 수 있습니다. - -## 패스 추가 방법 +1. 게임/에디터 쪽에서 `Renderer::PushDrawAble()`과 `Renderer::PushRenderData()`로 렌더링할 객체와 뷰 정보를 큐에 넣습니다. +2. 렌더 스레드의 `Renderer::Render()`가 큐를 비우고 `RenderDataManager::UploadToGPU()`를 호출합니다. +3. `RenderDataManager`는 각 `RenderViewer`의 view/proj/camera position을 GPU 버퍼에 업로드하고 `RenderData`를 `priority` 기준으로 정렬합니다. +4. `VulkanRenderer::Render()`가 스왑체인 이미지를 획득한 뒤 각 `RenderData`에 frame index와 drawable 목록을 연결합니다. +5. `ScriptableRenderer::Setup()`이 해당 `RenderData`에서 사용할 패스를 활성화합니다. +6. `ScriptableRenderer::Execute()`가 활성 패스를 `RenderQueue` 순으로 정렬하고, 각 패스의 `Configure()`를 호출해 리소스 사용 정보를 확정합니다. +7. 렌더러가 `BarrierInfo`를 계산한 뒤, 각 패스의 커맨드 버퍼 기록을 스레드 풀 task로 분배합니다. +8. 모든 패스 커맨드 기록이 끝나면 `VulkanRenderer`가 기록된 커맨드들을 순서대로 제출하고 마지막 커맨드가 present semaphore를 signal합니다. +9. 마지막에 `ExecuteTransfer()`가 readback/copy 작업을 위한 `CopyPass`를 기록하고, 스왑체인을 `Present` 상태로 전이합니다. -새 패스를 추가할 때 필요한 작업은 다음과 같습니다. +## RenderData와 RenderViewer -1. `ScriptableRenderPass` 구현체 작성 -2. `renderQueue` 설정 -3. 필요 시 ```BuildDrawList``` 오버라이딩 하여 드로우 할 물체 필터링 -4. `Configure()`에서 드로우 목록 및 리소스 등록 -5. `Record()`에서 커맨드 기록 +`RenderData`는 한 번의 렌더링 작업이 사용할 뷰어, 렌더 타겟, 태그, 우선순위를 담습니다. -기존 패스나 배리어 코드를 사용자가 명시적으로 수정할 필요가 없습니다. `CopyPass`처럼 드로우와 성격이 다른 패스도 동일한 방법으로 이루어집니다. - -예시 코드: ```cpp -/// 단순히 우선 순위 정렬기능만 추가된 패스 -class TransparentPass : public ScriptableRenderPass +class RenderData { public: - SH_RENDER_API TransparentPass(std::string_view name = "Transparent", RenderQueue renderQueue = RenderQueue::Transparent); + void SetRenderTarget(const RenderTexture* renderTarget); + void SetRenderTargets(std::initializer_list renderTargets); + void ClearRenderTargets(); - SH_RENDER_API auto BuildDrawList(const RenderTarget& renderData) -> DrawList override - { - // renderData로 들어온 카메라 정보와 드로우 할 물체들의 정보를 가지고 DrawList 구조체를 만들어서 반환 - } + int priority = 0; + core::Name tag{ "Camera" }; + std::vector renderViewers; }; ``` -## 상세 -### 설계 배경 +`RenderViewer`는 view/proj 행렬, 카메라 위치/방향, viewport/scissor, GPU camera buffer offset을 가집니다. `RenderDataManager`는 각 viewer마다 `RenderDataManager::BufferData`를 업로드하고, 그 offset을 `RenderViewer::offset`에 기록합니다. Vulkan draw 단계에서는 이 offset이 카메라 descriptor set의 dynamic offset으로 전달됩니다. + +최근 구조에서는 `RenderData`가 여러 렌더 타겟을 가질 수 있습니다. -Vulkan 렌더링 시스템을 구현할 때 드로우 호출 자체보다 **리소스 상태 관리와 실행 순서**가 더 큰 문제가 됩니다. +- `SetRenderTarget()`은 단일 타겟용 편의 함수입니다. +- `SetRenderTargets()`는 MRT를 구성합니다. +- 타겟이 `nullptr`이면 현재 스왑체인 이미지를 의미합니다. +- depth 전용 `RenderTexture`는 color attachment가 아니라 depth attachment로 취급됩니다. -구체적으로 아래 사항들을 매 패스마다 직접 추적해야 합니다. +## 기본 패스 구성 -- 패스 간 실행 순서 -- 각 패스의 렌더 타겟 및 샘플링 텍스처 -- 이미지 레이아웃 및 접근 권한 변환 -- `vkCmdPipelineBarrier` 삽입 위치 +`GameRenderer::Init()`은 기본 패스를 생성하고, `GameRenderer::Setup()`은 `RenderData::tag`에 따라 필요한 패스만 enqueue합니다. + +| 태그 | 실행 패스 | +|---|---| +| `"Depth"` | `DepthPass` | +| `"SSAO"` | `SSAOPass` | +| `"Combine"` | `CombinePass` | +| `"ImGUI"` | `GUIPass` | +| 그 외 카메라 | `Opaque`, `Transparent`, `UI` | -렌더링 코드를 단일 흐름으로 구성할 경우, 패스가 늘어날수록 다음과 같은 문제가 발생합니다. +현재 기본 패스의 역할은 다음과 같습니다. -- 패스 추가 시 앞뒤 리소스 상태 전이를 전체적으로 재검토해야 함 -- 배리어 누락 또는 잘못된 레이아웃 전이로 인한 런타임 오류 발생 -- 렌더링 의도 코드와 Vulkan 동기화 코드가 강하게 결합됨 +| 패스 | Queue | 역할 | +|---|---|---| +| `DepthPass` | `BeforeRendering` | depth/shadow 계열 렌더링. depth 전용 타겟도 지원 | +| `Opaque` | `Opaque` | 기본 불투명 렌더링 | +| `SSAOPass` | `AfterOpaque` | 풀스크린 plane으로 depth 등을 샘플링해 AO 타겟에 기록 | +| `CombinePass` | `AfterRendering` | 풀스크린 plane으로 AO 등 후처리 결과를 최종 타겟에 합성 | +| `Transparent` | `Transparent` | 카메라 방향 기준 back-to-front 정렬 후 개별 draw | +| `UI` | `Transparent` | UI shader pass용 transparent pass | +| `GUIPass` | `UI` | ImGui draw data 기록 | +| `CopyPass` | `AfterRendering` | readback/copy transfer 전용 내부 패스 | -ShellEngine은 이 문제를 해결하기 위해서, 그리고 유연한 확장을 위해 패스의 렌더링 의도와 API 레벨의 전이 처리를 분리하는 구조를 채택했습니다. +`SSAOPass`와 `CombinePass`는 패스 내부에서 fullscreen plane `Mesh`와 `Drawable`을 보유하고, 외부에서 `SetMaterial()`로 지정한 material을 사용합니다. 두 패스 모두 material이 참조하는 `RenderTexture`를 `Configure()`에서 샘플링 리소스로 등록합니다. ---- +## ScriptableRenderPass -### Configure 단계 +패스는 `passName`과 `renderQueue`를 갖고, 크게 두 단계로 동작합니다. -`ScriptableRenderPass::Configure()`는 렌더러가 전이를 계산하는 데 필요한 데이터를 구성하는 단계입니다. +### Configure -1. `BuildDrawList()` - 이번 패스의 드로우 목록 생성 -2. `CollectRenderImages()` - 렌더 텍스처 수집 및 용도 분류 - - 현재 렌더 타겟 -> `ColorAttachment` - - DrawList로 전달 된 메테리얼이 참조하는 `RenderTexture` -> `SampledRead` -3. 동일 패스 내에서 같은 텍스처를 출력과 입력으로 동시에 사용하는 경우 검증 +`Configure(const RenderData&)`는 커맨드 기록 전에 리소스 사용 의도를 확정합니다. -이 단계를 통해 패스의 리소스 사용 관계가 확정됩니다. +- 기본 구현은 현재 `passName`을 가진 shader pass가 있는 drawable만 `RenderBatch`로 묶습니다. +- `SetRenderTargetImageUsages()`가 `RenderData::GetRenderTargets()`를 순회해 color/depth attachment 사용을 등록합니다. +- `SetImageUsages()`가 drawable/material의 `MaterialData::CachedRT`를 확인해 샘플링하는 `RenderTexture`를 등록합니다. +- depth texture를 샘플링하는 경우 `DepthStencilSampledRead`, 일반 texture는 `SampledRead`로 등록합니다. -> [!Note] -> ```BuildDrawList```에는 RenderTarget구조체가 전달 됩니다. -> ```cpp -> struct RenderTarget -> { -> uint32_t frameIndex; -> const Camera* camera; -> const RenderTexture* target; -> const std::vector* drawables; -> }; -> ``` ---- +예전 문서의 `BuildDrawList()` 단계는 현재 `CreateRenderBatch()`와 `Configure()` 흐름으로 대체되었습니다. -### ScriptableRenderer +### Record -`ScriptableRenderer::Execute()`는 패스 간 리소스 연결 관계를 계산하고 커맨드 버퍼 기록을 조율합니다. +`Record(CommandBuffer&, const IRenderContext&, const RenderData&)`는 실제 커맨드를 기록합니다. -1. 활성 패스 수집 -2. renderQueue 기준 정렬 -3. 각 패스 Configure() 호출 -> 리소스 사용 정보 확정 -4. 각 패스 BuildBarrierInfo() 호출 -> 전이 정보 계산 -5. 커맨드 버퍼 기록 +- 기본 구현은 `cmd.SetRenderData(renderData, true, true, true, true)`로 렌더링을 시작합니다. +- viewer마다 viewport/scissor를 설정합니다. +- `RenderBatch` 단위로 `cmd.DrawMeshBatch()`를 호출합니다. +- `TransparentPass`처럼 정렬이 필요한 패스는 batch 대신 개별 `DrawMesh()`를 호출할 수 있습니다. +- 풀스크린 후처리 패스는 내부 drawable 하나를 직접 그립니다. -패스는 직접 배리어를 생성하지 않으며, 리소스 사용 의도만 제공합니다. 실제 전이 계산은 렌더러가 전담합니다. +## 리소스 전이 ---- +패스는 직접 Vulkan barrier를 만들지 않습니다. 대신 `renderTextures`에 “이번 패스에서 이 이미지를 어떤 용도로 쓸 것인지”를 기록합니다. -### BarrierInfo +```cpp +enum class ResourceUsage +{ + Undefined, + ColorAttachment, + SampledRead, + Present, + TransferSrc, + DepthStencilAttachment, + DepthStencilSampledRead +}; +``` -`BarrierInfo`는 패스 간 리소스 의존성을 실행 가능한 형태로 담는 구조체입니다. +`ScriptableRenderer::BuildBarrierInfo()`는 각 패스의 `renderTextures`를 보고 이전 사용 상태와 현재 사용 상태를 `BarrierInfo`로 만듭니다. -| 필드 | 설명 | +| 대상 | 이전 상태 출처 | |---|---| -| 대상 이미지 | 전이 대상 리소스 | -| `lastUsage` | 이전 패스의 사용 상태 | -| `curUsage` | 현재 패스의 사용 상태 | +| `RenderTexture*` | `RenderTexture::GetUsage()` | +| `nullptr` 스왑체인 | `ScriptableRenderer::swapChainStates[frameIndex]` | -렌더러는 각 텍스처의 사용 이력을 추적하여 패스 사이의 상태 전이를 `BarrierInfo`로 구체화합니다. +계산된 `BarrierInfo`는 해당 패스 커맨드 버퍼 시작 직후 `cmd.EmitBarrier()`로 기록됩니다. Vulkan에서는 `VulkanCommandBuffer::EmitBarrier()`가 `ResourceUsage`를 image layout, pipeline stage, access mask로 매핑하고 `VulkanImageBuffer::BarrierCommand()`를 호출합니다. -**예시 - 3패스 렌더링 흐름:** +예시 흐름: +```text +depth texture Undefined -> DepthStencilAttachment +depth texture DepthStencilAttachment -> DepthStencilSampledRead +ao texture Undefined -> ColorAttachment +ao texture ColorAttachment -> SampledRead +swapchain Undefined -> ColorAttachment +readback src ColorAttachment -> TransferSrc +swapchain ColorAttachment -> Present ``` -패스 1: 씬 렌더링 -> Undefined -> ColorAttachment -패스 2: 후처리 샘플링 -> ColorAttachment -> SampledRead -패스 3: 스왑체인 출력 -> SampledRead -> Present -``` ---- +## 멀티스레딩 + +`ScriptableRenderer::Execute()`는 리소스 전이를 먼저 직렬로 계산한 뒤, 각 패스의 커맨드 버퍼 기록을 스레드 풀에 task로 분배합니다. + +직렬 단계: + +- 활성 패스 정렬 +- 모든 패스 `Configure()` +- 모든 패스 `BarrierInfo` 계산 -### Vulkan 구현 계층 +병렬 단계: -`VulkanRenderImpl::EmitBarrier()`는 렌더러가 계산한 `BarrierInfo`를 Vulkan API 호출로 번역합니다. +- 패스별 `CommandBuffer` 할당 +- `Begin()` +- `EmitBarrier()` +- `Record()` +- `End()` -- `ResourceUsage`를 image layout, pipeline stage, access mask로 매핑 -- `VulkanImageBuffer::BarrierCommand()`를 통해 `vkCmdPipelineBarrier` 기록 +커맨드 버퍼 기록은 병렬이지만, 제출은 `recordedCmds` 순서대로 수행됩니다. 따라서 패스 간 이미지 상태 전이는 정렬된 패스 순서를 기준으로 안정적으로 계산되고, 실제 제출 순서도 그 순서를 유지합니다. -이 계층은 전이가 필요한지 판단을 내리지 않으며, 상위에서 계산된 전이 정보를 API 호출로 변환하는 역할만 수행합니다. +## Vulkan 구현 -## 한계 +`VulkanCommandBuffer`는 현재 Vulkan dynamic rendering을 사용합니다. -현재 구조는 아래 기능은 포함되어 있지 않습니다. +- `SetRenderData()`가 `RenderData::GetRenderTargets()`로 `VkRenderingAttachmentInfoKHR` 배열을 구성합니다. +- 여러 color attachment를 지원하며, 첫 번째 타겟 또는 depth 전용 타겟에서 depth attachment를 가져옵니다. +- 스왑체인 렌더링에서는 `nullptr` 타겟을 swapchain image로 해석합니다. +- MSAA 타겟이 있으면 color attachment에 MSAA image를 사용하고 resolve image를 원본 color image로 지정합니다. +- `RenderTargetLayout`은 color format 목록, depth format, MSAA 여부를 담고 pipeline cache key로 사용됩니다. -- 의존성 기반 패스 자동 재배치 -- 버퍼 리소스 상태 추적 (현재 `ResourceUsage`는 이미지 상태값만 정의됨) +draw 단계에서는 shader pass 이름으로 material의 `ShaderPass` 목록을 찾고, topology/skinned 여부/render target layout에 맞는 pipeline을 가져옵니다. descriptor set은 usage 기준으로 다음처럼 바인딩됩니다. -## 구조 요약 +| Set usage | 데이터 | +|---|---| +| `Camera` | `RenderDataManager` GPU buffer, dynamic offset 사용 | +| `Object` | drawable별 material data | +| `Material` | material별 uniform/texture data | + +Shader DSL에서 `MATRIX_VIEW`, `MATRIX_PROJ`, `CAMERA`를 사용하면 parser가 `CAMERA` uniform buffer를 자동으로 구성합니다. 현재 camera buffer는 `view`, `proj`, `pos`를 담고, `MATRIX_VIEW`와 `MATRIX_PROJ`는 각각 `CAMERA.view`, `CAMERA.proj`로 치환됩니다. + +## MaterialData와 텍스처 추적 + +렌더 텍스처 샘플링 추적은 `MaterialData`의 `CachedRT`에 저장됩니다. + +```cpp +struct CachedRT +{ + uint32_t set = 0; + uint32_t binding = 0; + core::SObjWeakPtr pass; + core::SObjWeakPtr rt; +}; +``` -ScriptableRenderPass (N개) -- renderTextures: 리소스 사용 의도 선언 +패스의 `Configure()`는 material/drawable이 가진 cached render texture 중 현재 `passName`과 lighting pass 이름이 같은 항목만 읽기 리소스로 등록합니다. 이 구조 덕분에 후처리 material이 depth, SSAO, color texture를 참조하더라도 패스 쪽에서 별도 barrier 코드를 직접 작성하지 않아도 됩니다. -ScriptableRenderer -- 패스 순서 정렬 -- BuildBarrierInfo(): 패스 간 상태 전이 계산 -- 커맨드 버퍼 병렬 기록 +## 패스 추가 방법 + +새 패스를 추가할 때는 다음 순서로 작업합니다. + +1. `ScriptableRenderPass`를 상속한 클래스를 만든다. +2. 생성자에서 `passName`과 `RenderQueue`를 정한다. +3. 기본 batch 드로우면 기본 `Configure()`를 쓰고, 풀스크린/특수 패스면 `Configure()`에서 `SetRenderTargetImageUsages()`와 `SetImageUsages()`를 직접 호출한다. +4. `Record()`에서 `CommandBuffer` API로 렌더 타겟 설정, viewport/scissor, draw/dispatch/blit을 기록한다. +5. `ScriptableRenderer` 파생 클래스의 `Init()`에서 `AddRenderPass()`로 생성한다. +6. `Setup()`에서 `RenderData::tag`나 상황에 맞춰 `EnqueRenderPass()` 한다. -VulkanRenderImpl -- EmitBarrier(): ResourceUsage -> Vulkan barrier 변환 -- vkCmdPipelineBarrier 기록 +예시: + +```cpp +class MyPostPass : public ScriptableRenderPass +{ +public: + MyPostPass(const IRenderContext& ctx) + : ScriptableRenderPass(core::Name{ "MyPostPass" }, RenderQueue::AfterRendering), + ctx(ctx) + { + } + +protected: + void Configure(const RenderData& renderData) override + { + renderTextures.clear(); + SetRenderTargetImageUsages(renderData); + SetImageUsages(*material); + } + + void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override + { + cmd.SetRenderData(renderData, false, false, true, false); + for (std::size_t i = 0; i < renderData.renderViewers.size(); ++i) + { + SetViewportScissor(cmd, ctx, renderData.renderViewers[i]); + cmd.DrawMesh(*fullscreenDrawable, passName, i); + } + } + +private: + const IRenderContext& ctx; + Material* material = nullptr; + Drawable* fullscreenDrawable = nullptr; +}; +``` diff --git a/Shader.md b/Shader.md index 73a1055a..2f0e31a0 100644 --- a/Shader.md +++ b/Shader.md @@ -1,95 +1,459 @@ -# 개요 -코드를 통해 렌더링 파이프라인을 설정 할 필요 없이, 셰이더를 통해 처리 할 수 있습니다. +# Shader 문서 -Unity엔진의 ShaderLab + GLSL의 문법을 합친 것이 특징 입니다. +ShellEngine의 `.shader` 파일은 Unity ShaderLab처럼 렌더 상태와 패스를 선언하고, 각 `Stage` 안에는 GLSL 코드를 직접 작성하는 형식입니다. 엔진은 이 파일을 파싱해 Vulkan용 GLSL을 생성하고, `glslc`로 SPIR-V를 컴파일한 뒤 `Shader`, `ShaderPass`, 머티리얼 유니폼 레이아웃으로 연결합니다. -# 상세 원리 -1. [ShaderLexer](https://github.com/Shell4026/ShellEngine/blob/main/src/Render/ShaderLexer.cpp)클래스로 코드를 토큰 단위로 나눕니다 -2. [ShaderParser](https://github.com/Shell4026/ShellEngine/blob/main/src/Render/ShaderParser.cpp)클래스로 토큰을 하나하나 읽어 문법을 검사하고 파싱 트리 구조를 완성해나갑니다. -3. ShaderParse내 Generate함수로 파싱 트리를 기반으로 GLSL문법으로 변환한 후 셰이더를 컴파일 합니다. +관련 구현은 주로 다음 파일에 있습니다. -## 예시 코드 +- `src/Render/ShaderLexer.cpp`: 소스 문자열을 토큰으로 분해 +- `src/Render/ShaderParser.cpp`: 토큰을 AST로 파싱하고 GLSL 코드 생성 +- `src/Render/ShaderGenerator.cpp`: 패스별 `.vert`, `.frag` 임시 파일 생성 +- `src/Game/Asset/ShaderLoader.cpp`: `.shader` 로드, `glslc` 실행, SPIR-V 로드 +- `src/Render/ShaderPass.cpp`: 파싱 결과에서 어트리뷰트/유니폼 레이아웃 구성 +- `src/Render/VulkanImpl/VulkanShaderPass.cpp`: Vulkan 디스크립터 세트와 파이프라인 레이아웃 생성 + +## 처리 흐름 + +1. `ShaderLoader::Load(path)`가 `.shader` 파일을 텍스트로 읽습니다. +2. `ShaderLexer::Lex()`가 키워드, 식별자, 숫자, 문자열, 연산자, 중괄호 등을 토큰으로 나눕니다. +3. `ShaderParser::Parse()`가 토큰을 `ShaderAST::ShaderNode`로 변환합니다. +4. 파서는 `Stage` 본문을 분석하며 `VERTEX`, `MATRIX_PROJ`, `LIGHT` 같은 엔진 예약 심볼을 발견하면 필요한 입력 어트리뷰트와 버퍼를 자동으로 추가합니다. +5. `GenerateStageCode()`가 각 `Stage`의 최종 GLSL 코드를 만듭니다. +6. `ShaderGenerator::GenerateShaderFile()`이 패스별 `.vert`, `.frag` 파일을 캐시 경로에 저장합니다. +7. `ShaderLoader`가 `glslc`로 각 스테이지를 `.spv`로 컴파일합니다. +8. `ShaderPassBuilder`가 SPIR-V를 `ShaderPass`로 만들고, Vulkan 구현에서는 `VkShaderModule`, descriptor set layout, pipeline layout을 구성합니다. +9. 최종 `Shader` 객체는 프로퍼티 목록과 패스 목록을 보관하고, `Material`은 이를 기준으로 유니폼/샘플러 데이터를 업로드합니다. + +## 기본 구조 + +```glsl +#version 430 core + +Shader "Shader Name" +{ + Property + { + float ambient; + vec4 color; + sampler2D albedo; + } + + Pass + { + LightingPass "Opaque" + Cull Back; + ZWrite On; + ZTest On; + + Stage Vertex + { + layout(location = 0) out vec2 fragUV; + + void main() + { + fragUV = UV; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0); + } + } + + Stage Fragment + { + layout(location = 0) in vec2 fragUV; + layout(location = 0) out vec4 outColor; + + uniform float ambient; + uniform vec4 color; + uniform sampler2D albedo; + + void main() + { + outColor = texture(albedo, fragUV) * color; + outColor.rgb += ambient; + } + } + } +} +``` + +최상단 `#version`은 현재 파서에서 `#version 430 core` 형태를 기대합니다. `Shader "..."`는 생성될 `Shader` 객체의 이름입니다. `Property`는 머티리얼에서 조작할 수 있는 값 목록이며 생략할 수 있습니다. `Pass`는 하나 이상 선언할 수 있고, 각 패스는 렌더 패스 이름과 렌더 상태, `Vertex`/`Fragment` 스테이지를 가집니다. + +## Property + +`Property`에 선언한 변수는 `Material::SetProperty()`로 값을 넣을 수 있는 머티리얼 프로퍼티가 됩니다. 스테이지 안에서 `uniform`으로 사용하려면 같은 타입과 이름이 `Property`에 먼저 선언되어 있어야 합니다. + +지원되는 주요 타입은 다음과 같습니다. + +| Shader 타입 | C++ 저장 타입 | +| --- | --- | +| `int` | `int` | +| `float` | `float` | +| `vec2` | `glm::vec2` | +| `vec3` | `glm::vec3` | +| `vec4` | `glm::vec4` | +| `mat3` | `glm::mat3` | +| `mat4` | `glm::mat4` | +| `sampler2D` | `Texture` | + +배열도 선언할 수 있습니다. + +```glsl +Property +{ + vec3 kernel[64]; + sampler2D normalTex; +} +``` + +### Local 프로퍼티 + +`[Local]`을 붙인 프로퍼티는 머티리얼 공통 값이 아니라 오브젝트별 값으로 취급됩니다. 예를 들어 같은 머티리얼을 공유하되 오브젝트마다 텍스처를 다르게 넣고 싶을 때 사용합니다. + +```glsl +Property +{ + vec4 color; + [Local] sampler2D tex; +} +``` + +일반 프로퍼티는 `UniformStructLayout::Usage::Material` 세트로, `[Local]` 프로퍼티는 `UniformStructLayout::Usage::Object` 세트로 배치됩니다. 런타임에서는 `MaterialData`와 `MaterialPropertyBlock`이 이 구분을 기준으로 버퍼와 샘플러를 갱신합니다. + +## Pass + +`Pass`는 실제 draw에 사용되는 렌더링 단위입니다. 같은 `LightingPass` 이름을 가진 패스들은 `Shader::GetShaderPasses(name)`로 조회되어 해당 렌더 단계에서 순서대로 실행됩니다. + +```glsl +Pass "Optional Pass Name" +{ + LightingPass "Opaque" + Cull Back; + ZWrite On; + ZTest On; + ColorMask RGBA; + + Stage Vertex { ... } + Stage Fragment { ... } +} +``` + +패스 이름은 생략할 수 있습니다. 생략하면 파서가 `Pass1`, `Pass2`처럼 자동 이름을 붙입니다. + +### 렌더 상태 + +| 구문 | 값 | 기본값 | 설명 | +| --- | --- | --- | --- | +| `LightingPass "name"` | 문자열 | 빈 이름 | 렌더러가 패스를 찾을 때 쓰는 분류 이름 | +| `Cull` | `Off`, `Front`, `Back` | `Back` | 컬링 모드 | +| `ZWrite` | `On`, `Off` | `On` | 깊이 버퍼 쓰기 여부 | +| `ZTest` | `On`, `Off` | `On` | 깊이 테스트 여부 | +| `ColorMask` | `RGBA` 조합 또는 숫자 | `15` | 컬러 채널 쓰기 마스크 | + +`ColorMask`는 비트마스크로 저장됩니다. `R=1`, `G=2`, `B=4`, `A=8`이며 `ColorMask 0;`은 컬러 출력을 막을 때 사용합니다. + +### Stencil + +```glsl +Stencil +{ + Ref 1; + ReadMask 255; + WriteMask 255; + Comp Always; + Pass Replace; + Fail Keep; + ZFail Keep; +} +``` + +`Comp` 값은 `Never`, `Less`, `Equal`, `LessEqual`, `Greater`, `NotEqual`, `GreaterEqual`, `Always`를 사용할 수 있습니다. `Pass`, `Fail`, `ZFail` 값은 `Keep`, `Zero`, `Replace`, `IncrementClamp`, `DecrementClamp`, `Invert`, `IncrementWrap`, `DecrementWrap`를 사용할 수 있습니다. + +## Stage + +현재 그래픽 셰이더는 `Stage Vertex`와 `Stage Fragment`를 사용합니다. 각 `Stage` 내부는 GLSL과 거의 같은 문법을 쓰지만, 파서가 일부 선언과 예약 심볼을 인식해 최종 GLSL 앞부분을 자동 생성합니다. + +```glsl +Stage Vertex +{ + layout(location = 0) out vec2 fragUV; + + const float scale = 1.0; + + void main() + { + fragUV = UV; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX * scale, 1.0); + } +} +``` + +### layout 입출력 + +`layout(location = N) in/out ...;` 구문은 스테이지 입출력으로 파싱됩니다. + +```glsl +layout(location = 0) out vec2 fragUV; +layout(location = 1) out vec3 fragNormal; +``` + +버텍스 스테이지에서 엔진 예약 입력(`VERTEX`, `UV`, `NORMAL` 등)을 사용하면 직접 `in`을 선언하지 않아도 파서가 자동으로 입력 어트리뷰트를 추가합니다. + +## 엔진 예약 심볼 + +예약 심볼은 셰이더 코드 안에서 일반 GLSL 변수처럼 사용할 수 있습니다. 파서는 사용 여부를 감지해 필요한 layout, uniform buffer, storage buffer, push constant를 자동으로 생성합니다. + +| 심볼 | 타입/의미 | 자동 생성 | +| --- | --- | --- | +| `VERTEX` | `vec3`, 정점 위치 | vertex input location 0 | +| `UV` | `vec2`, UV | vertex input location 1 | +| `NORMAL` | `vec3`, 노멀 | vertex input location 2 | +| `TANGENT` | `vec3`, 탄젠트 | vertex input location 3 | +| `BONE_INDICES` | `ivec4`, 본 인덱스 | skinned input location 4 | +| `BONE_WEIGHTS` | `vec4`, 본 가중치 | skinned input location 5 | +| `MATRIX_MODEL` | 모델 행렬 | push constant `CONSTANTS.model` | +| `MATRIX_VIEW` | 카메라 view 행렬 | camera UBO `CAMERA.view` | +| `MATRIX_PROJ` | 카메라 projection 행렬 | camera UBO `CAMERA.proj` | +| `CAMERA` | 카메라 UBO 인스턴스 | `view`, `proj`, `pos` 멤버 | +| `LIGHT` | 라이트 SSBO | object set의 storage buffer | +| `SKIN` | 스키닝 SSBO | object set의 storage buffer | +| `MATRIX_SKIN` | 스키닝 행렬 | `BONE_*`와 `SKIN.ibm[]` 기반 계산식 삽입 | +| `TEXTURE_SHADOW` | 그림자 텍스처 | object set의 local sampler | + +`MATRIX_MODEL`, `MATRIX_VIEW`, `MATRIX_PROJ`는 최종 GLSL에서 각각 `CONSTANTS.model`, `CAMERA.view`, `CAMERA.proj`로 치환됩니다. + +### LIGHT 버퍼 형태 + +현재 파서는 `LIGHT` 사용 시 다음과 같은 구조를 생성합니다. + +```glsl +struct Light +{ + vec4 pos; + vec4 other; + vec4 shadowRect; + mat4 lightSpaceMatrix; +}; + +layout(std430, set = 1, binding = N) readonly buffer UNIFORM_LIGHT +{ + int count; + Light lights[]; +} LIGHT; +``` + +따라서 코드에서는 `LIGHT.count`, `LIGHT.lights[i].pos`, `LIGHT.lights[i].other`처럼 접근합니다. + +```glsl +for (int i = 0; i < LIGHT.count; ++i) +{ + int type = int(LIGHT.lights[i].other.w); + vec3 lightPos = LIGHT.lights[i].pos.xyz; +} +``` + +### MATRIX_SKIN + +`MATRIX_SKIN`을 사용하면 파서가 `BONE_WEIGHTS`, `BONE_INDICES`, `SKIN` 버퍼를 자동 등록하고 함수 본문 앞에 다음 계산식을 삽입합니다. + +```glsl +mat4 MATRIX_SKIN = + BONE_WEIGHTS.x * SKIN.ibm[BONE_INDICES.x] + + BONE_WEIGHTS.y * SKIN.ibm[BONE_INDICES.y] + + BONE_WEIGHTS.z * SKIN.ibm[BONE_INDICES.z] + + BONE_WEIGHTS.w * SKIN.ibm[BONE_INDICES.w]; +``` + +스키닝 셰이더에서는 보통 다음처럼 사용합니다. + +```glsl +vec4 skinnedPos = MATRIX_SKIN * vec4(VERTEX, 1.0); +vec3 skinnedNormal = mat3(MATRIX_SKIN) * NORMAL; +``` + +## Uniform 바인딩 규칙 + +스테이지 내부의 단일 `uniform` 선언은 반드시 `Property`에 같은 타입과 이름으로 선언되어 있어야 합니다. + +```glsl +Property +{ + float roughness; + sampler2D albedo; +} + +Stage Fragment +{ + uniform float roughness; + uniform sampler2D albedo; +} +``` + +파서는 스칼라/벡터/행렬 프로퍼티를 `UBO`라는 uniform block으로 묶습니다. `sampler2D`는 별도의 combined image sampler binding이 됩니다. + +일반 프로퍼티는 material set에 배치되고, `[Local]` 프로퍼티는 object set에 배치됩니다. 내부 enum 기준은 다음과 같습니다. + +| Usage | set | 용도 | +| --- | --- | --- | +| `Camera` | `0` | 카메라 공통 버퍼 | +| `Object` | `1` | 오브젝트별 값, push/local/lighting/skinning | +| `Material` | `2` | 머티리얼 공유 값 | + +명시적 descriptor layout도 선언할 수 있습니다. 이 경우 `Property` 검사를 거치지 않고 직접 버퍼 또는 샘플러 layout으로 추가됩니다. + +```glsl +layout(set = 2, binding = 0) uniform MyBlock +{ + vec4 color; + float strength; +} customData; + +layout(set = 2, binding = 1) uniform sampler2D customTex; +``` + +UBO는 `std140`, SSBO는 `std430` 기준으로 `UniformStructLayout`이 CPU 측 offset과 size를 계산합니다. `ShaderParser::Optimize()`는 자동 생성된 `UBO` 멤버를 스칼라, `vec2`, 그 외 큰 타입 순서로 정렬해 패딩을 줄입니다. + +## constexpr와 specialization constant + +스테이지 안에서 `constexpr`를 선언하면 Vulkan specialization constant로 변환됩니다. + +```glsl +Stage Fragment +{ + constexpr int SAMPLE_COUNT = 16; + + void main() + { + for (int i = 0; i < SAMPLE_COUNT; ++i) + { + ... + } + } +} +``` + +생성되는 GLSL은 `layout (constant_id = N) const ...` 형태입니다. 런타임에서는 `Material::SetConstant(name, value)`가 패스별 constant 데이터를 갱신하고, `VulkanPipelineManager`는 constant 데이터 해시를 파이프라인 캐시 키에 포함합니다. + +현재 `ShaderPass`가 크기를 계산하는 specialization constant 타입은 `bool`, `int`, `float` 계열입니다. + +## 생성되는 GLSL 예시 + +작성한 코드: + +```glsl +Stage Vertex +{ + layout(location = 0) out vec2 fragUV; + + void main() + { + fragUV = UV; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0); + } +} +``` + +대략 다음과 같은 GLSL로 확장됩니다. + +```glsl +#version 430 core +layout(location = 0) in vec3 VERTEX; +layout(location = 1) in vec2 UV; +layout(location = 0) out vec2 fragUV; + +layout(set = 0, binding = 0) uniform UNIFORM_CAMERA +{ + mat4 view; + mat4 proj; + vec3 pos; +} CAMERA; + +layout(push_constant) uniform UNIFORM_CONSTANTS +{ + mat4 model; +} CONSTANTS; + +void main() +{ + fragUV = UV; + gl_Position = CAMERA.proj * CAMERA.view * CONSTANTS.model * vec4(VERTEX, 1.0); +} +``` + +## 예시: 조명 텍스처 셰이더 ```glsl #version 430 core -Shader "Default Shader" +Shader "Lit Texture" { Property { float ambient; sampler2D tex; - sampler2D normalTex; } - + Pass { - LightingPass "Forward" - + LightingPass "Opaque" + Stage Vertex { - layout(location = 0) out vec2 fragUvs; + layout(location = 0) out vec2 fragUV; layout(location = 1) out vec3 fragPos; - layout(location = 2) out mat3 TBN; + layout(location = 2) out vec3 fragNormal; + void main() { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); - fragUvs = UV; - fragPos = (MATRIX_MODEL * vec4(VERTEX, 1.0f)).xyz; - - mat3 normalMatrix = transpose(inverse(mat3(MATRIX_MODEL))); - - vec3 t = normalize(normalMatrix * TANGENT); - vec3 n = normalize(normalMatrix * NORMAL); - vec3 b = cross(n, t); - - TBN = mat3(t, b, n); + fragUV = UV; + fragPos = (MATRIX_MODEL * vec4(VERTEX, 1.0)).xyz; + fragNormal = normalize(mat3(transpose(inverse(MATRIX_MODEL))) * NORMAL); + gl_Position = MATRIX_PROJ * MATRIX_VIEW * vec4(fragPos, 1.0); } } + Stage Fragment { + layout(location = 0) in vec2 fragUV; + layout(location = 1) in vec3 fragPos; + layout(location = 2) in vec3 fragNormal; layout(location = 0) out vec4 outColor; - layout(location = 0) in vec2 uvs; - layout(location = 1) in vec3 fragPos; - layout(location = 2) in mat3 TBN; uniform float ambient; uniform sampler2D tex; - uniform sampler2D normalTex; - void main() + void main() { - vec3 normal = texture(normalTex, uvs).xyz; - normal = normal * 2.0 - 1.0; - normal = normalize(TBN * normal); - float diffuse = 0.0; + for (int i = 0; i < LIGHT.count; ++i) { - int type = int(LIGHT.other[i].w); + int type = int(LIGHT.lights[i].other.w); if (type == 0) { - vec3 toLightDir = -LIGHT.pos[i].xyz; - float intensity = LIGHT.pos[i].w; - diffuse += max(dot(normal, toLightDir), 0.0) * intensity; + vec3 toLightDir = -LIGHT.lights[i].pos.xyz; + float intensity = LIGHT.lights[i].pos.w; + diffuse += max(dot(fragNormal, toLightDir), 0.0) * intensity; } else if (type == 1) { - vec3 toLightVec = LIGHT.pos[i].xyz - fragPos; + vec3 toLightVec = LIGHT.lights[i].pos.xyz - fragPos; vec3 toLightDir = normalize(toLightVec); float lightDis = length(toLightVec); - float attenuation = clamp(1.0 - (lightDis / LIGHT.pos[i].w), 0.0, 1.0); - diffuse += max(dot(normal, toLightDir), 0.0) * attenuation; + float attenuation = clamp(1.0 - (lightDis / LIGHT.lights[i].pos.w), 0.0, 1.0); + diffuse += max(dot(fragNormal, toLightDir), 0.0) * attenuation; } } - outColor = texture(tex, uvs); - outColor.xyz *= diffuse + ambient; + + outColor = texture(tex, fragUV); + outColor.rgb *= diffuse + ambient; } } } } ``` -### 스텐실을 이용한 아웃라인 셰이더 예시 + +## 예시: 스텐실 아웃라인 + +첫 번째 패스는 원본 오브젝트를 스텐실에 기록하고 컬러 출력을 막습니다. 두 번째 패스는 노멀 방향으로 정점을 밀어낸 뒤, 스텐실 값이 다른 영역만 칠합니다. + ```glsl #version 430 core @@ -100,11 +464,12 @@ Shader "Outline Shader" float outlineWidth; vec4 color; } + Pass { - LightingPass "Forward" + LightingPass "Opaque" ColorMask 0; - + Stencil { Ref 1; @@ -115,26 +480,31 @@ Shader "Outline Shader" Fail Replace; ZFail Replace; } - + Stage Vertex { void main() { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0); } } + Stage Fragment { layout(location = 0) out vec4 outColor; + void main() { - outColor = vec4(0.0f, 0.0f, 0.0f, 0.0f); + outColor = vec4(0.0); } } } + Pass { - LightingPass "Forward" + LightingPass "Opaque" + Cull Back; + Stencil { Ref 1; @@ -145,25 +515,25 @@ Shader "Outline Shader" Fail Keep; ZFail Keep; } - Cull Back; - + Stage Vertex { uniform float outlineWidth; void main() { - vec4 vert = vec4(VERTEX + NORMAL * outlineWidth, 1.0f); + vec4 vert = vec4(VERTEX + NORMAL * outlineWidth, 1.0); gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vert; } } + Stage Fragment { layout(location = 0) out vec4 outColor; uniform vec4 color; - void main() + void main() { outColor = color; } @@ -171,49 +541,15 @@ Shader "Outline Shader" } } ``` -### 로컬 키워드를 사용해 오브젝트별 텍스쳐 설정 가능한 반투명 셰이더 예시 -로컬 키워드로 된 유니폼 변수는 MeshRenderer MaterialPropertyBlock을 지정해준 후 값을 넣어주면 됩니다. - -```glsl -#version 430 core -Shader "Unlit Transparent Shader2" -{ - Property - { - vec3 color; - [Local] sampler2D tex; - } - - Pass - { - LightingPass "Transparent" - - Cull Off; - ZWrite Off; - Stage Vertex - { - layout(location = 0) out vec2 fragUvs; - - void main() - { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); - fragUvs = UV; - } - } - Stage Fragment - { - layout(location = 0) out vec4 outColor; - layout(location = 0) in vec2 uvs; - uniform vec3 color; - uniform sampler2D tex; +## 작성 시 주의할 점 - void main() - { - outColor = texture(tex, uvs); - outColor.xyz *= color.xyz; - } - } - } -} -``` +- `Stage` 안의 일반 `uniform`은 `Property`에 먼저 선언해야 합니다. 선언이 없으면 파서가 오류를 냅니다. +- `LIGHT`는 `LIGHT.pos[i]`가 아니라 `LIGHT.lights[i].pos` 형태로 접근해야 합니다. +- `MATRIX_MODEL`을 사용하면 push constant가 생성됩니다. Vulkan 구현은 현재 모델 행렬 하나(`mat4`)를 push constant로 사용합니다. +- `MATRIX_VIEW`, `MATRIX_PROJ`, `CAMERA` 중 하나를 사용하면 camera UBO가 생성됩니다. +- `[Local]`은 오브젝트별 데이터를 위한 선언입니다. 같은 머티리얼을 여러 오브젝트가 공유할 때 개별 값을 넣어야 하는 프로퍼티에 사용합니다. +- `sampler2D`는 UBO 멤버가 아니라 별도 sampler descriptor로 생성됩니다. +- `mat2`는 파서와 `Shader` 프로퍼티 타입에는 존재하지만, 현재 `ShaderPass::FillAttributes()`의 UBO 멤버 구성에는 반영되어 있지 않습니다. 머티리얼 프로퍼티로 안정적으로 쓰려면 `mat3` 또는 `mat4`를 사용하거나 해당 경로를 보강해야 합니다. +- 자동 생성되는 UBO 멤버는 최종 레이아웃 최적화를 위해 선언 순서와 다르게 정렬될 수 있습니다. CPU 측 업로드는 `UniformStructLayout`의 offset을 기준으로 하므로 이름으로 접근하는 경로를 사용해야 합니다. +- 패스의 `LightingPass` 이름은 렌더러 쪽에서 조회하는 키입니다. 새 이름을 만들 때는 해당 렌더 단계가 실제로 그 이름을 요청하는지 함께 확인해야 합니다. diff --git a/docs/RenderFrameFlow.svg b/docs/RenderFrameFlow.svg new file mode 100644 index 00000000..b1b46b02 --- /dev/null +++ b/docs/RenderFrameFlow.svg @@ -0,0 +1,134 @@ + + ShellEngine render frame flow + Horizontal frame flow diagram from game/editor queues through render data upload, pass setup, parallel command recording, Vulkan submission, present, and cleanup. + + + + + + + + + + + 1. 입력 수집 + + 2. RenderData 준비와 패스 선택 + + 3. 커맨드 기록, 제출, 정리 + + + + 1 + Game / Editor + drawable, render data 생성 + + + + 2 + Renderer queue + PushDrawAble / PushRenderData + + + + 3 + Renderer::Render + 렌더 스레드에서 큐 drain + + + + 4 + UploadToGPU + camera buffer 업로드 + + + + + + + + + 5 + Acquire image + swapchain frame index + RenderData에 연결 + + + + 6 + Setup + RenderData::tag 기준 + 활성 패스 enqueue + + + + 7 + Execute + RenderQueue 정렬 + Configure 호출 + + + + 8 + BarrierInfo + RenderTexture 사용 이력 + 스왑체인 상태 계산 + + + + + + + + + + 9 + ThreadPool + 패스별 병렬 기록 + Begin → Barrier → Record → End + + + + 10 + recordedCmds + 패스 순서대로 수집 + 제출 순서 유지 + + + + 11 + Submit + VulkanQueueManager + semaphore 연결 + + + + 12 + Present / cleanup + ExecuteTransfer, Present + readback, command 반환 + + + + + + + + + 프레임 종료 후 다음 프레임에서 큐 수집부터 반복 + diff --git a/include/Editor/EditorRenderer.h b/include/Editor/EditorRenderer.h index 4a7d5c0e..7dc4b83f 100644 --- a/include/Editor/EditorRenderer.h +++ b/include/Editor/EditorRenderer.h @@ -3,8 +3,6 @@ #include "EditorOutlinePass.h" #include "EditorPostOutlinePass.h" -#include "Render/RenderPass/ShadowMapPass.h" - #include "Game/Component/Render/Camera.h" #include "Game/GameRenderer.h" @@ -15,10 +13,6 @@ namespace sh::game class GUIPass; class World; } -namespace sh::render -{ - class TransparentPass; -} namespace sh::editor { class EditorRenderer : public game::GameRenderer @@ -34,9 +28,6 @@ namespace sh::editor SH_EDITOR_API void Setup(const render::RenderData& data) override; private: render::ScriptableRenderPass* pickingPass = nullptr; - render::ScriptableRenderPass* opaquePass = nullptr; - render::TransparentPass* transparentPass = nullptr; - render::TransparentPass* uiPass = nullptr; EditorOutlinePass* outlinePass = nullptr; EditorPostOutlinePass* postOutlinePass = nullptr; }; diff --git a/include/Game/Component/Render/Camera.h b/include/Game/Component/Render/Camera.h index 8748984c..bb046178 100644 --- a/include/Game/Component/Render/Camera.h +++ b/include/Game/Component/Render/Camera.h @@ -63,6 +63,7 @@ namespace sh::game auto GetTag() const -> const core::Name& { return renderData.tag; } auto GetProjection() const -> Projection { return projection; } auto GetRenderTexture() const -> render::RenderTexture* { return renderTexture; } + auto GetRenderData() const -> const render::RenderData& { return renderData; } protected: SH_GAME_API virtual void UpdateViewMatrix(); SH_GAME_API virtual void UpdateProjMatrix(); diff --git a/include/Game/Component/Render/SSAOComponent.h b/include/Game/Component/Render/SSAOComponent.h new file mode 100644 index 00000000..26a97036 --- /dev/null +++ b/include/Game/Component/Render/SSAOComponent.h @@ -0,0 +1,74 @@ +#pragma once +#include "Game/Export.h" +#include "Game/Component/Component.h" + +#include "Render/RenderData.h" + +#include +namespace sh::render +{ + class Material; + class RenderTexture; + class Texture; +} + +namespace sh::game +{ + class Camera; + + /// @brief 카메라에 부착해 SSAO 를 활성화한다. + class SSAOComponent : public Component + { + COMPONENT(SSAOComponent) + public: + SH_GAME_API SSAOComponent(GameObject& owner); + SH_GAME_API ~SSAOComponent(); + + SH_GAME_API void Awake() override; + SH_GAME_API void OnDestroy() override; + SH_GAME_API void BeginUpdate() override; + + SH_GAME_API void SetMaterial(render::Material* mat); + + auto GetDepthRT() const -> render::RenderTexture* { return depthRT; } + auto GetAORT() const -> render::RenderTexture* { return aoRT; } + auto GetRadius() const -> float { return radius; } + auto GetBias() const -> float { return bias; } + auto GetPower() const -> float { return power; } + private: + void EnsureRenderTextures(); + void UpdateMaterialUniforms(); + static void CreateKernel(); + private: + PROPERTY(mat, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) + render::Material* mat = nullptr; + + PROPERTY(radius) + float radius = 0.5f; + PROPERTY(bias) + float bias = 0.025f; + PROPERTY(power) + float power = 1.5f; + + PROPERTY(depthRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) + render::RenderTexture* depthRT = nullptr; + PROPERTY(normalRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) + render::RenderTexture* normalRT = nullptr; + PROPERTY(aoRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) + render::RenderTexture* aoRT = nullptr; + PROPERTY(camera, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave); + Camera* camera = nullptr; + PROPERTY(noiseTex, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave); + render::Texture* noiseTex = nullptr; + + render::RenderData depthRenderData; + render::RenderData ssaoRenderData; + render::RenderData combineRenderData; + + uint32_t cachedWidth = 0; + uint32_t cachedHeight = 0; + + static std::array kernel; + static bool bKernelInit; + }; +}//namespace diff --git a/include/Game/Component/TestComputeShader.h b/include/Game/Component/TestComputeShader.h new file mode 100644 index 00000000..6a299106 --- /dev/null +++ b/include/Game/Component/TestComputeShader.h @@ -0,0 +1,23 @@ +#pragma once +#include "Export.h" +#include "Game/Component/Component.h" + +namespace sh::render +{ + class ComputeShader; +} + +namespace sh::game +{ + class TestComputeShader : public Component + { + COMPONENT(TestComputeShader) + public: + SH_GAME_API TestComputeShader(GameObject& owner); + + SH_GAME_API void Update() override; + private: + PROPERTY(shader) + render::ComputeShader* shader = nullptr; + }; +}//namespace diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index ede90591..dd6a7ca8 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -7,7 +7,11 @@ #include namespace sh::render { - class ShadowMapPass; + class DepthPass; + class OpaquePass; + class SSAOPass; + class CombinePass; + class TransparentPass; } namespace sh::game { @@ -23,13 +27,24 @@ namespace sh::game SH_GAME_API void Init() override; SH_GAME_API void Setup(const render::RenderData& data) override; - auto GetGUIPass() const -> game::GUIPass* { return guiPass; } + auto GetDepthPass() const -> render::DepthPass& { return *depthPass; } + auto GetOpaquePass() const -> render::ScriptableRenderPass& { return *opaquePass; } + auto GetSSAOPass() const -> render::SSAOPass& { return *ssaoPass; } + auto GetCombinePass() const -> render::CombinePass& { return *combinePass; } + auto GetTransparentPass() const -> render::TransparentPass& { return *transparentPass; } + auto GetUIPass() const -> render::TransparentPass& { return *uiPass; } + auto GetGUIPass() const -> game::GUIPass& { return *guiPass; } protected: render::IRenderContext& renderCtx; World& world; game::ImGUImpl& guiCtx; + render::DepthPass* depthPass = nullptr; + render::ScriptableRenderPass* opaquePass = nullptr; + render::SSAOPass* ssaoPass = nullptr; + render::CombinePass* combinePass = nullptr; + render::TransparentPass* transparentPass = nullptr; + render::TransparentPass* uiPass = nullptr; game::GUIPass* guiPass = nullptr; - render::ShadowMapPass* shadowMapPass = nullptr; }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/CommandBuffer.h b/include/Render/CommandBuffer.h index 1ff81772..3a5d0497 100644 --- a/include/Render/CommandBuffer.h +++ b/include/Render/CommandBuffer.h @@ -2,6 +2,8 @@ #include "Core/NonCopyable.h" #include "Core/Name.h" +#include "Render/IRenderThrMethod.h" + #include namespace sh::render { @@ -11,11 +13,12 @@ namespace sh::render class Material; class Drawable; class ShaderPass; - struct RenderData; + class RenderData; struct BarrierInfo; class CommandBuffer : public core::INonCopyable { + friend struct IRenderThrMethod; public: virtual ~CommandBuffer() = default; @@ -31,5 +34,13 @@ namespace sh::render virtual void DrawMeshBatch(const std::vector& drawables, core::Name passName, std::size_t viewerIdx = 0) = 0; virtual void DrawMesh(const Drawable& drawable, core::Name passName, std::size_t viewerIdx = 0) = 0; virtual void EmitBarrier(const std::vector& barriers) = 0; + protected: + virtual auto GetRenderCall() const -> uint32_t = 0; + }; + + template<> + struct IRenderThrMethod + { + static auto GetRenderCall(const CommandBuffer& cmd) -> uint32_t { return cmd.GetRenderCall(); } }; }//namespace \ No newline at end of file diff --git a/include/Render/Formats.hpp b/include/Render/Formats.hpp index 04460740..ea1b82c8 100644 --- a/include/Render/Formats.hpp +++ b/include/Render/Formats.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace sh::render { enum class TextureFormat @@ -10,6 +13,7 @@ namespace sh::render SBGR24, SBGRA32, RGB24, + RG32F, RGBA32, BGR24, BGRA32, @@ -21,17 +25,32 @@ namespace sh::render D16 }; - static auto GetTextureFormatChannel(TextureFormat format) -> int + inline static auto GetTextureFormatChannel(TextureFormat format) -> int { switch (format) { + case TextureFormat::RG32F: + return 2; case TextureFormat::R8: return 1; } return 4; } - static auto IsDepthTexture(TextureFormat format) -> bool + inline static auto GetTextureFormatPixelSize(TextureFormat format) -> std::size_t + { + switch (format) + { + case TextureFormat::RG32F: + return sizeof(uint16_t) * 2; + case TextureFormat::R8: + return 1; + default: + return 4; + } + } + + inline static auto IsDepthTexture(TextureFormat format) -> bool { if (format == TextureFormat::D32S8 || format == TextureFormat::D24S8 || @@ -41,4 +60,26 @@ namespace sh::render return true; return false; } -}//namespace \ No newline at end of file + + inline static auto TextureFormatToString(TextureFormat format) -> const char* + { + switch (format) + { + case TextureFormat::None: return "None"; + case TextureFormat::SRGB24: return "SRGB24"; + case TextureFormat::SRGBA32: return "SRGBA32"; + case TextureFormat::SBGR24: return "SBGR24"; + case TextureFormat::SBGRA32: return "SBGRA32"; + case TextureFormat::RGB24: return "RGB24"; + case TextureFormat::RG32F: return "RG32F"; + case TextureFormat::RGBA32: return "RGBA32"; + case TextureFormat::R8: return "R8"; + case TextureFormat::D32S8: return "D32S8"; + case TextureFormat::D24S8: return "D24S8"; + case TextureFormat::D16S8: return "D16S8"; + case TextureFormat::D32: return "D32"; + case TextureFormat::D16: return "D16"; + default: return "None"; + } + } +}//namespace diff --git a/include/Render/Material.h b/include/Render/Material.h index 5f66fca3..7b3bfc05 100644 --- a/include/Render/Material.h +++ b/include/Render/Material.h @@ -25,6 +25,8 @@ #include #include #include +#include +#include namespace sh::render { @@ -53,6 +55,12 @@ namespace sh::render template void SetProperty(const std::string& name, const T& data); + template + void SetProperty(const std::string& name, const std::vector& data); + template + void SetProperty(const std::string& name, const std::array& data); + template + void SetPropertyArray(const std::string& name, const T* data, std::size_t count); SH_RENDER_API void SetProperty(const std::string& name, const glm::vec4& data); SH_RENDER_API void SetProperty(const std::string& name, const Texture* data); SH_RENDER_API void SetProperty(const std::string& name, Texture* data); @@ -121,7 +129,7 @@ namespace sh::render propertyBlock.SetProperty(name, data); - for (auto& location : propInfo->locations) + for (const Shader::PropertyInfo::Location& location : propInfo->locations) { auto it = std::find(dirtyProps.begin(), dirtyProps.end(), std::pair{ location.passPtr.Get(), location.layoutPtr}); if (it == dirtyProps.end()) @@ -131,6 +139,46 @@ namespace sh::render bPropertyDirty = true; } template + inline void Material::SetProperty(const std::string& name, const std::vector& data) + { + SetPropertyArray(name, data.data(), data.size()); + } + template + inline void Material::SetProperty(const std::string& name, const std::array& data) + { + SetPropertyArray(name, data.data(), data.size()); + } + template + inline void Material::SetPropertyArray(const std::string& name, const T* data, std::size_t count) + { + if (!core::IsValid(shader)) + return; + + const Shader::PropertyInfo* propInfo = shader->GetProperty(name); + assert(propInfo != nullptr); + if (propInfo == nullptr) + return; + + assert(propInfo->type == core::reflection::GetType()); + if (propInfo->type != core::reflection::GetType()) + return; + + assert(propInfo->locations.empty() || propInfo->locations.front().memberPtr->IsArray()); + if (!propInfo->locations.empty() && !propInfo->locations.front().memberPtr->IsArray()) + return; + + propertyBlock.SetArrayProperty(name, data, count); + + for (const Shader::PropertyInfo::Location& location : propInfo->locations) + { + auto it = std::find(dirtyProps.begin(), dirtyProps.end(), std::pair{ location.passPtr.Get(), location.layoutPtr }); + if (it == dirtyProps.end()) + dirtyProps.push_back({ location.passPtr.Get(), location.layoutPtr }); + } + + bPropertyDirty = true; + } + template inline void Material::SetConstant(const std::string& name, const T& value) { if (shader == nullptr) @@ -224,4 +272,4 @@ namespace sh::render static_assert(true); return {}; } -} \ No newline at end of file +} diff --git a/include/Render/MaterialPropertyBlock.h b/include/Render/MaterialPropertyBlock.h index 4d23b179..eadc1e3f 100644 --- a/include/Render/MaterialPropertyBlock.h +++ b/include/Render/MaterialPropertyBlock.h @@ -6,10 +6,17 @@ #include "Core/SContainer.hpp" #include "glm/mat4x4.hpp" +#include "glm/vec2.hpp" +#include "glm/vec3.hpp" +#include "glm/vec4.hpp" #include #include #include +#include +#include +#include +#include namespace sh::render { @@ -21,12 +28,18 @@ namespace sh::render template>> void SetProperty(const std::string& name, const T& data); + template + void SetArrayProperty(const std::string& name, const T* data, std::size_t count); SH_RENDER_API void SetProperty(const std::string& name, const Texture* data); SH_RENDER_API auto GetScalarProperty(const std::string& name) const -> std::optional; SH_RENDER_API auto GetVectorProperty(const std::string& name) const -> const glm::vec4*; SH_RENDER_API auto GetMatrixProperty(const std::string& name) const -> const glm::mat4*; SH_RENDER_API auto GetTextureProperty(const std::string& name) const -> const Texture*; + SH_RENDER_API auto GetIntArrayProperty(const std::string& name) const -> const std::vector*; + SH_RENDER_API auto GetScalarArrayProperty(const std::string& name) const -> const std::vector*; + SH_RENDER_API auto GetVectorArrayProperty(const std::string& name) const -> const std::vector*; + SH_RENDER_API auto GetMatrixArrayProperty(const std::string& name) const -> const std::vector*; SH_RENDER_API auto GetTextureProperties() const -> const std::unordered_map&; SH_RENDER_API auto Serialize() const -> core::Json override; @@ -35,6 +48,10 @@ namespace sh::render std::unordered_map scalarProperties; std::unordered_map vecProperties; std::unordered_map matProperties; + std::unordered_map> intArrayProperties; + std::unordered_map> scalarArrayProperties; + std::unordered_map> vecArrayProperties; + std::unordered_map> matArrayProperties; core::SHashMap textureProperties; }; @@ -58,4 +75,69 @@ namespace sh::render else static_assert(true); } -}//namespace \ No newline at end of file + template + inline void MaterialPropertyBlock::SetArrayProperty(const std::string& name, const T* data, std::size_t count) + { + if constexpr (std::is_arithmetic_v) + { + if constexpr (std::is_integral_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = static_cast(data[i]); + intArrayProperties.insert_or_assign(name, std::move(values)); + } + else + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = static_cast(data[i]); + scalarArrayProperties.insert_or_assign(name, std::move(values)); + } + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = glm::vec4{ data[i], 0.0f, 0.0f }; + vecArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = glm::vec4{ data[i], 0.0f }; + vecArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + if (count == 0) + vecArrayProperties.insert_or_assign(name, std::vector{}); + else + vecArrayProperties.insert_or_assign(name, std::vector{ data, data + count }); + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = core::Util::ConvertMat2ToMat4(data[i]); + matArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = core::Util::ConvertMat3ToMat4(data[i]); + matArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + if (count == 0) + matArrayProperties.insert_or_assign(name, std::vector{}); + else + matArrayProperties.insert_or_assign(name, std::vector{ data, data + count }); + } + else + static_assert(true); + } +}//namespace diff --git a/include/Render/RenderData.h b/include/Render/RenderData.h index 95c7e1d6..786f6d50 100644 --- a/include/Render/RenderData.h +++ b/include/Render/RenderData.h @@ -1,13 +1,17 @@ #pragma once #include "Mesh.h" #include "Texture.h" +#include "IRenderThrMethod.h" +#include "Formats.hpp" #include "Core/SContainer.hpp" #include "Core/Name.h" #include #include +#include #include +#include namespace sh { @@ -34,46 +38,75 @@ namespace sh::render std::size_t offset = 0; }; - struct RenderData + class RenderData { + friend struct IRenderThrMethod; + public: + void SetRenderTarget(const RenderTexture* renderTarget) { targets.clear(); targets.push_back(renderTarget); } + void SetRenderTargets(std::initializer_list renderTargets) + { + if (renderTargets.size() == 0) + { + targets.clear(); + targets.push_back(nullptr); + } + else + targets.assign(renderTargets.begin(), renderTargets.end()); + } + void SetRenderTargets(std::vector renderTargets) + { + if (renderTargets.size() == 0) + { + targets.clear(); + targets.push_back(nullptr); + } + else + targets = std::move(renderTargets); + } + void ClearRenderTargets() { targets.clear(); targets.push_back(nullptr); } + auto GetRenderTarget(std::size_t idx) const -> const RenderTexture* { return idx < targets.size() ? targets[idx] : nullptr; } + auto GetRenderTargets() const -> const std::vector& { return targets; } + auto GetFrameIdx() const -> uint32_t { return frameIndex; } + auto GetDrawablesPtr() const -> const std::vector* { return drawables; } + public: int priority = 0; - uint32_t frameIndex = 0; - const RenderTexture* target = nullptr; core::Name tag{ "Camera" }; - std::vector renderViewers; + private: + uint32_t frameIndex = 0; + std::vector targets{ nullptr }; const std::vector* drawables = nullptr; }; - struct RenderTargetLayout + template<> + struct IRenderThrMethod { - TextureFormat format; - TextureFormat depthFormat; - bool bUseMSAA = false; + inline static void SetFrameIndex(RenderData& rd, uint32_t frameIdx) { rd.frameIndex = frameIdx; } + inline static void SetDrawablesPtr(RenderData& rd, const std::vector* ptr) { rd.drawables = ptr; } + }; + class RenderTargetLayout + { + public: auto operator==(const RenderTargetLayout& other) const -> bool { - return format == other.format && depthFormat == other.depthFormat && bUseMSAA == other.bUseMSAA; + return colorFormats == other.colorFormats && depthFormat == other.depthFormat && bUseMSAA == other.bUseMSAA; } - }; + auto IsDepthOnly() const -> bool + { + for (TextureFormat format : colorFormats) + { + if (format != TextureFormat::None) + return false; + } + return depthFormat != TextureFormat::None; + } + public: + std::vector colorFormats{ TextureFormat::None }; + TextureFormat depthFormat = TextureFormat::None; - //struct DrawList - //{ - // struct RenderGroup - // { - // const Material* material; - // Mesh::Topology topology; - // std::vector drawables; - // }; - // struct RenderItem - // { - // const Material* material; - // Mesh::Topology topology; - // Drawable* drawable; - // }; - // std::variant, std::vector> renderData; - // uint32_t drawableCount = 0; - //}; + bool bUseMSAA = false; + }; /// @brief 리소스(이미지/버퍼) 사용 의도. 배리어 계산의 입력으로 쓰임 enum class ResourceUsage @@ -103,12 +136,12 @@ namespace std auto operator()(const sh::render::RenderTargetLayout& layout) const -> std::size_t { std::hash intHasher{}; - std::hash boolHasher{}; + std::hash boolHasher{}; - std::size_t hash = intHasher(static_cast(layout.format)); - hash = sh::core::Util::CombineHash(hash, intHasher(static_cast(layout.depthFormat))); + std::size_t hash = intHasher(static_cast(layout.depthFormat)); hash = sh::core::Util::CombineHash(hash, boolHasher(layout.bUseMSAA)); - + for (std::size_t i = 0; i < layout.colorFormats.size(); ++i) + hash = sh::core::Util::CombineHash(hash, intHasher(static_cast(layout.colorFormats[i]))); return hash; } }; diff --git a/include/Render/RenderDataManager.h b/include/Render/RenderDataManager.h index ead773b3..26e1514a 100644 --- a/include/Render/RenderDataManager.h +++ b/include/Render/RenderDataManager.h @@ -7,6 +7,8 @@ #include "Core/LockFreeMPSCQueue.h" #include "Core/ArrayView.hpp" +#include "glm/vec4.hpp" + #include #include #include @@ -37,7 +39,7 @@ namespace sh::render { glm::mat4 view; glm::mat4 proj; - glm::vec3 pos; + glm::vec4 pos; }; private: friend struct IRenderThrMethod; @@ -59,4 +61,4 @@ namespace sh::render static void UploadToGPU(RenderDataManager& manager) { manager.UploadToGPU(); } static auto GetRenderDatas(RenderDataManager& manager) -> core::ArrayView { return manager.GetRenderDatas(); } }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/RenderPass/CombinePass.h b/include/Render/RenderPass/CombinePass.h new file mode 100644 index 00000000..ab6877dd --- /dev/null +++ b/include/Render/RenderPass/CombinePass.h @@ -0,0 +1,43 @@ +#pragma once +#include "Render/Export.h" +#include "Render/ScriptableRenderPass.h" +#include "Render/RenderData.h" + +#include "Core/Observer.hpp" +#include "Core/GCObject.h" + +namespace sh::render +{ + class Mesh; + class Drawable; + class Material; + + /// @brief aoTex 를 현재 렌더 타겟 위에 덮어쓰는 풀스크린 패스. + /// @brief "Combine" 태그가 RenderData 에 있을 때만 동작. + class CombinePass : public ScriptableRenderPass + { + public: + SH_RENDER_API CombinePass(const IRenderContext& ctx); + SH_RENDER_API ~CombinePass(); + + SH_RENDER_API void SetMaterial(Material& mat); + protected: + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; + private: + void EnsureDrawable(); + private: + const IRenderContext& ctx; + + struct Resource : core::GCObject + { + Mesh* plane = nullptr; + Drawable* drawable = nullptr; + Material* mat = nullptr; + + SH_RENDER_API void PushReferenceObjects(core::GarbageCollection& gc) override; + } resource; + + core::Observer::Listener onMatDestroy; + }; +}//namespace diff --git a/include/Render/RenderPass/CopyPass.h b/include/Render/RenderPass/CopyPass.h index abb92e4a..f542177d 100644 --- a/include/Render/RenderPass/CopyPass.h +++ b/include/Render/RenderPass/CopyPass.h @@ -10,7 +10,7 @@ namespace sh::render class CopyPass : public ScriptableRenderPass { - friend class IRenderThrMethod; + friend struct IRenderThrMethod; public: SH_RENDER_API CopyPass(); protected: diff --git a/include/Render/RenderPass/ShadowMapPass.h b/include/Render/RenderPass/DepthPass.h similarity index 72% rename from include/Render/RenderPass/ShadowMapPass.h rename to include/Render/RenderPass/DepthPass.h index dfb5c293..5b2f2d25 100644 --- a/include/Render/RenderPass/ShadowMapPass.h +++ b/include/Render/RenderPass/DepthPass.h @@ -6,15 +6,13 @@ namespace sh::render { /// @brief 그림자 아틀라스에 광원의 깊이 정보를 기록하는 패스 - class ShadowMapPass : public ScriptableRenderPass + /// @brief RenderData의 태그가 Depth면 작동 + class DepthPass : public ScriptableRenderPass { public: - SH_RENDER_API ShadowMapPass(); + SH_RENDER_API DepthPass(); SH_RENDER_API void Configure(const RenderData& renderData) override; SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; - private: - uint32_t lastFrameIndex = ~0u; - uint32_t callsThisFrame = 0; }; }//namespace diff --git a/include/Render/RenderPass/SSAOPass.h b/include/Render/RenderPass/SSAOPass.h new file mode 100644 index 00000000..ecc98879 --- /dev/null +++ b/include/Render/RenderPass/SSAOPass.h @@ -0,0 +1,45 @@ +#pragma once +#include "Render/Export.h" +#include "Render/ScriptableRenderPass.h" +#include "Render/RenderData.h" + +#include "Core/Observer.hpp" +#include "Core/GCObject.h" + +namespace sh::render +{ + class RenderTexture; + class Mesh; + class Drawable; + class Material; + + /// @brief depthRT 를 샘플링하여 aoRT(R8) 에 AO 값을 쓰는 풀스크린 패스. + /// @brief "SSAO" 태그가 RenderData 에 있을 때만 동작. + class SSAOPass : public ScriptableRenderPass + { + public: + SH_RENDER_API SSAOPass(const IRenderContext& ctx); + SH_RENDER_API ~SSAOPass(); + + SH_RENDER_API void SetMaterial(Material& mat); + protected: + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; + private: + void EnsureDrawable(); + private: + const IRenderContext& ctx; + + struct Resource : core::GCObject + { + Mesh* plane = nullptr; + Drawable* drawable = nullptr; + Material* mat = nullptr; + + SH_RENDER_API void PushReferenceObjects(core::GarbageCollection& gc) override; + } resource; + RenderData localRenderData; + + core::Observer::Listener onMatDestroy; + }; +}//namespace diff --git a/include/Render/RenderTexture.h b/include/Render/RenderTexture.h index 4f010277..b5589ed1 100644 --- a/include/Render/RenderTexture.h +++ b/include/Render/RenderTexture.h @@ -3,6 +3,7 @@ #include "Texture.h" #include "RenderData.h" #include "IRenderThrMethod.h" +#include "ITextureBuffer.h" #include "glm/vec2.hpp" @@ -22,7 +23,7 @@ namespace sh::render friend IRenderThrMethod; SCLASS(RenderTexture); public: - SH_RENDER_API RenderTexture(const RenderTargetLayout& layout); + SH_RENDER_API RenderTexture(TextureFormat colorFormat, TextureFormat depthFormat = TextureFormat::D24S8, bool bMSAA = false); SH_RENDER_API RenderTexture(RenderTexture&& other) noexcept; SH_RENDER_API ~RenderTexture(); @@ -31,24 +32,25 @@ namespace sh::render SH_RENDER_API void OnPropertyChanged(const core::reflection::Property& property) override; SH_RENDER_API void SetSize(uint32_t width, uint32_t height); - SH_RENDER_API auto GetSize() const -> glm::vec2; - SH_RENDER_API auto GetLayout() const -> const RenderTargetLayout& { return layout; } - SH_RENDER_API auto GetMSAABuffer() const -> ITextureBuffer* { return msaaBuffer.get(); } - SH_RENDER_API auto GetDepthBuffer() const -> ITextureBuffer*; - SH_RENDER_API auto IsDepthOnly() const -> bool { return layout.format == TextureFormat::None; } - SH_RENDER_API auto GetUsage() const -> ResourceUsage { return usage; } + auto GetSize() const -> glm::vec2 { return { width, height }; } + auto GetDepthFormat() const -> TextureFormat { return depthFormat; } + auto GetMSAABuffer() const -> ITextureBuffer* { return msaaBuffer.get(); } + auto GetDepthBuffer() const -> ITextureBuffer* { return IsDepthTexture() ? textureBuffer.get() : depthBuffer.get(); } + auto IsDepthTexture() const -> bool { return GetTextureFormat() == TextureFormat::None && depthFormat != TextureFormat::None; } + auto GetUsage() const -> ResourceUsage { return usage; } protected: - SH_RENDER_API void ChangeUsage(ResourceUsage newUsage); + void ChangeUsage(ResourceUsage newUsage) { usage = newUsage; } private: void CreateBuffers(); + void CreateDepthBuffer(const ITextureBuffer::CreateInfo& ci); private: PROPERTY(width); uint32_t width; PROPERTY(height); uint32_t height; - RenderTargetLayout layout; + TextureFormat depthFormat = TextureFormat::D24S8; std::unique_ptr msaaBuffer; std::unique_ptr depthBuffer; @@ -56,10 +58,11 @@ namespace sh::render ResourceUsage usage = ResourceUsage::Undefined; bool bChangeSize = false; + bool bMSAA = false; }; inline void IRenderThrMethod::ChangeUsage(RenderTexture& rt, ResourceUsage newUsage) { rt.ChangeUsage(newUsage); } -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/ScriptableRenderPass.h b/include/Render/ScriptableRenderPass.h index 9e84aa79..d75e02ce 100644 --- a/include/Render/ScriptableRenderPass.h +++ b/include/Render/ScriptableRenderPass.h @@ -52,11 +52,6 @@ namespace sh::render { friend IRenderThrMethod; public: - SH_RENDER_API ScriptableRenderPass(const core::Name& passName, RenderQueue renderQueue); - SH_RENDER_API virtual ~ScriptableRenderPass() = default; - - SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } - protected: struct RenderBatch { const Material* material; @@ -64,12 +59,20 @@ namespace sh::render bool bSkinned = false; std::vector drawables; }; + SH_RENDER_API ScriptableRenderPass(const core::Name& passName, RenderQueue renderQueue); + SH_RENDER_API virtual ~ScriptableRenderPass() = default; + + SH_RENDER_API static auto CreateRenderBatch(const core::Name& passName, const std::vector& drawables) -> std::vector; + + SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } + protected: SH_RENDER_API virtual void Configure(const RenderData& renderData); SH_RENDER_API virtual void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData); - SH_RENDER_API auto CreateRenderBatch(const std::vector& drawables) const -> std::vector; + SH_RENDER_API void SetRenderTargetImageUsages(const RenderData& renderData); SH_RENDER_API void SetImageUsages(const RenderData& renderData); SH_RENDER_API void SetImageUsages(const std::vector& drawables); + SH_RENDER_API void SetImageUsages(const Material& mat); SH_RENDER_API void SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderViewer& renderViewer); /// @brief Configure 이후에 호출해야 정확한 렌더콜 갯수를 알 수 있음 @@ -96,4 +99,4 @@ namespace sh::render { return pass.GetRenderCallCount(); } -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/ShaderLexer.h b/include/Render/ShaderLexer.h index 2883cf9c..3e814d2c 100644 --- a/include/Render/ShaderLexer.h +++ b/include/Render/ShaderLexer.h @@ -17,7 +17,7 @@ namespace sh::render Layout, Uniform, In, Out, Sampler2D, Constexpr, Const, VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT, BONE_WEIGHTS, BONE_INDICES, SKIN, - MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, MATRIX_SKIN, TEXTURE_SHADOW, + MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, CAMERA, MATRIX_SKIN, TEXTURE_SHADOW, LBracket, // ( RBracket, // ) LBrace, // { diff --git a/include/Render/Texture.h b/include/Render/Texture.h index d47b3d3c..1e471248 100644 --- a/include/Render/Texture.h +++ b/include/Render/Texture.h @@ -34,19 +34,21 @@ namespace sh::render SH_RENDER_API Texture(Texture&& other) noexcept; SH_RENDER_API virtual ~Texture(); + SH_RENDER_API void SyncDirty() override; + SH_RENDER_API void Sync() override; + SH_RENDER_API void OnPropertyChanged(const core::reflection::Property& prop) override; /// @brief 픽셀 데이터를 지정한다. /// @brief [주의] 동기화 타이밍에 텍스쳐 버퍼가 재설정됨. /// @param data 데이터 포인터 /// @param mipLevel 밉 레벨 - SH_RENDER_API void SetPixelData(const std::vector& pixels, uint32_t mipLevel = 0); - SH_RENDER_API void SetPixelData(std::vector&& pixels, uint32_t mipLevel = 0); + SH_RENDER_API void SetPixelData(std::vector pixels, uint32_t mipLevel = 0); /// @brief 픽셀 데이터를 지정한다. /// @brief [주의] 동기화 타이밍에 텍스쳐 버퍼가 재설정됨. /// @param pixels 데이터 포인터 /// @param size 데이터 사이즈 /// @param mipLevel 밉 레벨 SH_RENDER_API void SetPixelData(const uint8_t* pixels, std::size_t size, uint32_t mipLevel = 0); - SH_RENDER_API virtual auto GetPixelData() const -> const std::vector>&; + virtual auto GetPixelData() const -> const std::vector>& { return pixels; } SH_RENDER_API virtual auto GetPixelData(uint32_t mipLevel) const -> const std::vector&; SH_RENDER_API virtual void Build(const IRenderContext& context); @@ -54,36 +56,29 @@ namespace sh::render /// @brief 네이티브 텍스쳐 버퍼를 가져온다. /// @return 텍스쳐 버퍼 포인터 SH_RENDER_API auto GetTextureBuffer() const -> ITextureBuffer*; - SH_RENDER_API auto GetTextureFormat() const -> TextureFormat; - - SH_RENDER_API auto GetWidth() const -> uint32_t; - SH_RENDER_API auto GetHeight() const -> uint32_t; /// @brief 텍스쳐 포멧을 변경한다. /// @brief [주의] 동기화 타이밍에 텍스쳐 버퍼가 재설정됨. /// @param target 텍스쳐 포멧 SH_RENDER_API void ChangeTextureFormat(TextureFormat target); - SH_RENDER_API auto IsSRGB() const -> bool; - SH_RENDER_API auto GetMipLevel() const -> uint32_t; - SH_RENDER_API void SetAnisoLevel(uint32_t aniso); - SH_RENDER_API auto GetAnisoLevel() const -> uint32_t; - + SH_RENDER_API void SetGenerateMipmap(bool bGenerate); - SH_RENDER_API auto IsGenerateMipmap() const -> bool; - - SH_RENDER_API void SyncDirty() override; - SH_RENDER_API void Sync() override; SH_RENDER_API void SetSRGB(bool bSRGB); SH_RENDER_API void SetFiltering(Filtering filter); - SH_RENDER_API auto GetFiltering() const-> Filtering; - - SH_RENDER_API void OnPropertyChanged(const core::reflection::Property& prop) override; SH_RENDER_API void ExportToPNG(const std::filesystem::path& path); - SH_RENDER_API auto GetContext() const -> const IRenderContext* { return context; } + SH_RENDER_API auto GetMipLevel() const -> uint32_t; + auto GetContext() const -> const IRenderContext* { return context; } + auto GetTextureFormat() const -> TextureFormat { return format; } + auto GetWidth() const -> uint32_t { return width; } + auto GetHeight() const -> uint32_t { return height; } + auto GetFiltering() const -> Filtering { return filtering; } + auto IsSRGB() const -> bool { return bSRGB; } + auto GetAnisoLevel() const -> uint32_t { return aniso; } + auto IsGenerateMipmap() const -> bool { return bGenerateMipmap; } private: void CreateTextureBuffer(); auto CheckSRGB() const -> bool; @@ -114,4 +109,4 @@ namespace sh::render bool bGenerateMipmap = true; bool bSetDataDirty = false; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanCommandBuffer.h b/include/Render/VulkanImpl/VulkanCommandBuffer.h index 0238f6dc..601a81d2 100644 --- a/include/Render/VulkanImpl/VulkanCommandBuffer.h +++ b/include/Render/VulkanImpl/VulkanCommandBuffer.h @@ -77,6 +77,8 @@ namespace sh::render::vk SH_RENDER_API auto GetCommandPool() const -> VkCommandPool { return cmdPool; } SH_RENDER_API void Clear(); + protected: + auto GetRenderCall() const -> uint32_t override { return renderCall; } private: void BindCameraSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout, uint32_t cameraOffset); void BindMaterialSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout); @@ -103,6 +105,9 @@ namespace sh::render::vk std::vector signalSemaphores; VkFence fence = VK_NULL_HANDLE; + + uint32_t renderCall = 0; + bool bBeginRender = false; }; }//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanImageBuffer.h b/include/Render/VulkanImpl/VulkanImageBuffer.h index 3757a3b0..73a1d723 100644 --- a/include/Render/VulkanImpl/VulkanImageBuffer.h +++ b/include/Render/VulkanImpl/VulkanImageBuffer.h @@ -55,7 +55,7 @@ namespace sh::render::vk VkAccessFlags dstAccess); SH_RENDER_API static auto ConvertTextureFormat(TextureFormat format) -> VkFormat; private: - static auto GetChannelCount(VkFormat format) -> uint32_t; + static auto GetFormatPixelSize(VkFormat format) -> uint32_t; private: const VulkanContext* ctx = nullptr; @@ -70,11 +70,11 @@ namespace sh::render::vk VkSampleCountFlagBits sample = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; uint32_t width = 0; uint32_t height = 0; - uint32_t channel = 4; + uint32_t pixelSize = 4; uint32_t aniso = 0; uint32_t mipLevel = 1; bool bSwapChainImg = false; bool bRenderTarget = false; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanPipeline.h b/include/Render/VulkanImpl/VulkanPipeline.h index 4215b496..327a4fa8 100644 --- a/include/Render/VulkanImpl/VulkanPipeline.h +++ b/include/Render/VulkanImpl/VulkanPipeline.h @@ -96,4 +96,4 @@ namespace sh::render::vk bool bZWrite = true; bool bZTest = true; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanSwapChain.h b/include/Render/VulkanImpl/VulkanSwapChain.h index 2c4cc602..d2c135e5 100644 --- a/include/Render/VulkanImpl/VulkanSwapChain.h +++ b/include/Render/VulkanImpl/VulkanSwapChain.h @@ -45,19 +45,19 @@ namespace sh::render::vk SH_RENDER_API auto IsSwapChainSupport(VkPhysicalDevice gpu) -> bool; - SH_RENDER_API auto GetSurface() const -> const VkSurfaceKHR { return surface; } - SH_RENDER_API auto GetSwapChain() const -> const VkSwapchainKHR { return swapChain; } - SH_RENDER_API auto GetSwapChainDetail() const -> const SwapChainSupportDetails& { return details; } - SH_RENDER_API auto GetSwapChainSize() const -> VkExtent2D { return swapChainSize; } - SH_RENDER_API auto GetSwapChainImageFormat() const -> VkFormat { return swapChainImageFormat; } - SH_RENDER_API auto GetSwapChainImages() const -> const std::vector& { return swapChainImages; } - SH_RENDER_API auto GetSwapChainImages() -> std::vector& { return swapChainImages; } - SH_RENDER_API auto GetSwapChainMSAAImages() const -> const std::vector& { return swapChainImagesMSAA; } - SH_RENDER_API auto GetSwapChainMSAAImages() -> std::vector& { return swapChainImagesMSAA; } - SH_RENDER_API auto GetSwapChainDepthImages() const -> const std::vector& { return swapChainImagesDepth; } - SH_RENDER_API auto GetSwapChainDepthImages() -> std::vector& { return swapChainImagesDepth; } - SH_RENDER_API auto GetSwapChainImageCount() const -> uint32_t { return swapChainImageCount; } - SH_RENDER_API auto GetRenderTargetLayout() const -> const RenderTargetLayout& { return rtLayout; } + auto GetSurface() const -> const VkSurfaceKHR { return surface; } + auto GetSwapChain() const -> const VkSwapchainKHR { return swapChain; } + auto GetSwapChainDetail() const -> const SwapChainSupportDetails& { return details; } + auto GetSwapChainSize() const -> VkExtent2D { return swapChainSize; } + auto GetSwapChainImageFormat() const -> VkFormat { return swapChainImageFormat; } + auto GetSwapChainImages() const -> const std::vector& { return swapChainImages; } + auto GetSwapChainImages() -> std::vector& { return swapChainImages; } + auto GetSwapChainMSAAImages() const -> const std::vector& { return swapChainImagesMSAA; } + auto GetSwapChainMSAAImages() -> std::vector& { return swapChainImagesMSAA; } + auto GetSwapChainDepthImages() const -> const std::vector& { return swapChainImagesDepth; } + auto GetSwapChainDepthImages() -> std::vector& { return swapChainImagesDepth; } + auto GetSwapChainImageCount() const -> uint32_t { return swapChainImageCount; } + auto GetRenderTargetLayout() const -> const RenderTargetLayout& { return rtLayout; } private: void QuerySwapChainDetails(VkPhysicalDevice gpu); auto SelectSurfaceFormat() -> VkSurfaceFormatKHR; @@ -79,4 +79,4 @@ namespace sh::render::vk RenderTargetLayout rtLayout; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/resource/ProjectTemplate/Assets/SampleWorld/default.shader b/resource/ProjectTemplate/Assets/SampleWorld/default.shader index 3468e48a..15be92e2 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/default.shader +++ b/resource/ProjectTemplate/Assets/SampleWorld/default.shader @@ -10,7 +10,7 @@ Shader "Default Shader" } Pass { - LightingPass "ShadowMapPass" + LightingPass "DepthPass" Cull Off; Stage Vertex diff --git a/resource/shaders/SSAO.shader b/resource/shaders/SSAO.shader new file mode 100644 index 00000000..c07167f8 --- /dev/null +++ b/resource/shaders/SSAO.shader @@ -0,0 +1,156 @@ +#version 430 core + +Shader "SSAO Shader" +{ + Property + { + vec3 kernel[64]; + sampler2D normalTex; + sampler2D depthTex; + sampler2D noiseTex; + sampler2D aoTex; + } + + Pass + { + LightingPass "SSAOPass" + Cull Back; + + Stage Vertex + { + layout(location = 0) out vec2 fragUV; + void main() + { + fragUV = UV; + gl_Position = vec4(VERTEX, 1.0f); + } + } + Stage Fragment + { + layout(location = 0) in vec2 uvs; + + layout(location = 0) out vec4 outColor; + + uniform vec3 kernel[64]; + uniform sampler2D depthTex; + uniform sampler2D normalTex; + uniform sampler2D noiseTex; + + vec3 ReconstructViewPos(vec2 uv, float depth) + { + vec4 clipPos = vec4(uv.x * 2.0 - 1.0, (1.0 - uv.y) * 2.0 - 1.0, depth, 1.0); + vec4 viewPos = inverse(MATRIX_PROJ) * clipPos; + viewPos /= viewPos.w; + return viewPos.xyz; + } + + void main() + { + float depth = texture(depthTex, uvs).r; + + if (depth >= 1.0) + { + outColor = vec4(1.0); + return; + } + vec3 fragPos = ReconstructViewPos(uvs, depth); + vec3 normal = texture(normalTex, uvs).xyz; + normal = normalize(normal * 2.0 - 1.0); + + const vec2 noiseScale = vec2(1024.0/4.0, 768.0/4.0); + vec3 randomVec = normalize(vec3(texture(noiseTex, uvs * noiseScale).rg, 0.0)); + + vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 TBN = mat3(tangent, bitangent, normal); + + float occlusion = 0.0; + + for (int i = 0; i < 64; ++i) + { + const float radius = 0.5f; + vec3 samplePos = fragPos + TBN * kernel[i] * radius; + + vec4 offset = MATRIX_PROJ * vec4(samplePos, 1.0); + offset.xyz /= offset.w; + offset.xy = vec2(offset.x * 0.5 + 0.5, 0.5 - offset.y * 0.5); + + if (offset.x < 0.0 || offset.x > 1.0 || + offset.y < 0.0 || offset.y > 1.0) + { + continue; + } + + float sampleDepth = texture(depthTex, offset.xy).r; + vec3 sampleViewPos = ReconstructViewPos(offset.xy, sampleDepth); + + float rangeCheck = smoothstep( + 0.0, + 1.0, + radius / abs(fragPos.z - sampleViewPos.z) + ); + + const float bias = 0.025f; + occlusion += (sampleViewPos.z >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck; + } + + const float strength = 1.5f; + occlusion = 1.0 - (occlusion / 64.0); + occlusion = pow(occlusion, strength); + + outColor = vec4(vec3(occlusion), 1.0); + } + } + } + Pass + { + LightingPass "CombinePass" + Cull Off; + ZWrite Off; + ZTest Off; + + Stage Vertex + { + layout(location = 0) out vec2 fragUV; + void main() + { + fragUV = UV; + gl_Position = vec4(VERTEX, 1.0); + } + } + Stage Fragment + { + layout(location = 0) in vec2 uvs; + + layout(location = 0) out vec4 outColor; + + uniform sampler2D aoTex; + + float BlurAO(vec2 uv) + { + vec2 texelSize = 1.0 / vec2(textureSize(aoTex, 0)); + float result = 0.0; + + for (int x = -2; x < 2; ++x) + { + for (int y = -2; y < 2; ++y) + { + vec2 offset = vec2(float(x), float(y)) * texelSize; + result += texture(aoTex, uv + offset).r; + } + } + + return result / 16.0; + } + + void main() + { + float ao = BlurAO(uvs); + float shadow = 1.0 - ao; + if (shadow <= 0.001) + discard; + outColor = vec4(0.0, 0.0, 0.0, shadow); + } + } + } +} diff --git a/src/Editor/BuildSystem.cpp b/src/Editor/BuildSystem.cpp index 8b81104b..fc2d6c7b 100644 --- a/src/Editor/BuildSystem.cpp +++ b/src/Editor/BuildSystem.cpp @@ -219,6 +219,8 @@ namespace sh::editor bundle.AddAsset(lineShaderAsset, true); game::ShaderAsset uiTextShaderAsset{ *editorResource->GetShader("UITextShader") }; bundle.AddAsset(uiTextShaderAsset, true); + game::ShaderAsset ssaoShaderAsset{ *editorResource->GetShader("SSAOShader") }; + bundle.AddAsset(ssaoShaderAsset, true); game::MaterialAsset errorMatAsset{ *editorResource->GetMaterial("ErrorMaterial") }; bundle.AddAsset(errorMatAsset, true); diff --git a/src/Editor/EditorOutlinePass.cpp b/src/Editor/EditorOutlinePass.cpp index 926e9f6f..fdadb815 100644 --- a/src/Editor/EditorOutlinePass.cpp +++ b/src/Editor/EditorOutlinePass.cpp @@ -15,37 +15,34 @@ namespace sh::editor } SH_EDITOR_API void EditorOutlinePass::SetOutTexture(render::RenderTexture& tex) { - rd.target = &tex; + rd.SetRenderTarget(&tex); rd.renderViewers.resize(1); } SH_EDITOR_API void EditorOutlinePass::Configure(const render::RenderData& renderData) { renderTextures.clear(); - renderTextures[rd.target] = render::ResourceUsage::ColorAttachment; - if (renderData.drawables != nullptr) + renderTextures[rd.GetRenderTargets()[0]] = render::ResourceUsage::ColorAttachment; + if (renderData.GetDrawablesPtr() != nullptr) { - SetImageUsages(*renderData.drawables); - renderBatches = CreateRenderBatch(*renderData.drawables); + SetImageUsages(*renderData.GetDrawablesPtr()); + renderBatches = CreateRenderBatch(passName, *renderData.GetDrawablesPtr()); } } SH_EDITOR_API void EditorOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { - if (renderData.renderViewers.empty()) + if (renderData.renderViewers.empty() || renderData.GetDrawablesPtr() == nullptr) return; + rd.renderViewers[0] = renderData.renderViewers.front(); - rd.drawables = renderData.drawables; + render::IRenderThrMethod::SetDrawablesPtr(rd, renderData.GetDrawablesPtr()); + const render::RenderTexture& mainTaret = *rd.GetRenderTargets()[0]; cmd.SetRenderData(rd, true, true, true, true); - cmd.SetViewport(0, 0, rd.target->GetSize().x, rd.target->GetSize().y); - cmd.SetScissor(0, 0, rd.target->GetSize().x, rd.target->GetSize().y); - - if (rd.drawables == nullptr) - return; + cmd.SetViewport(0, 0, mainTaret.GetSize().x, mainTaret.GetSize().y); + cmd.SetScissor(0, 0, mainTaret.GetSize().x, mainTaret.GetSize().y); for (const RenderBatch& batch : renderBatches) - { cmd.DrawMeshBatch(batch.drawables, passName); - } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 5c488f88..0ba7113c 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -33,7 +33,11 @@ namespace sh::editor } SH_EDITOR_API void EditorPostOutlinePass::SetOutlineMaterial(render::Material& mat) { + if (this->mat == &mat || mat.IsPendingKill()) + return; this->mat = &mat; + if (drawable != nullptr) + drawable->SetMaterial(mat); } SH_EDITOR_API void EditorPostOutlinePass::Configure(const render::RenderData& renderData) @@ -47,21 +51,16 @@ namespace sh::editor } renderTextures.clear(); - renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; + SetRenderTargetImageUsages(renderData); + if (mat != nullptr) - { - for (const render::MaterialData::CachedRT& cachedRT : render::IRenderThrMethod::GetCachedRTs(mat->GetMaterialData())) - { - if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) - renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? render::ResourceUsage::DepthStencilSampledRead : render::ResourceUsage::SampledRead; - } - } + SetImageUsages(*mat); } SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { if (drawable == nullptr || !core::IsValid(drawable->GetMesh()) || !core::IsValid(drawable->GetMaterial())) return; - cmd.SetRenderData(renderData, false, false, false, false); + cmd.SetRenderData(renderData, false, false, true, false); std::size_t viewerIdx = 0; for (const render::RenderViewer& viewer : renderData.renderViewers) { @@ -70,4 +69,4 @@ namespace sh::editor ++viewerIdx; } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 1982d765..907b5abb 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -1,9 +1,7 @@ #include "EditorRenderer.h" #include "Game/GUIPass.h" - #include "Render/RenderPass/TransparentPass.h" -#include "Render/RenderPass/ShadowMapPass.h" #include namespace sh::editor @@ -14,26 +12,16 @@ namespace sh::editor } SH_EDITOR_API void EditorRenderer::Init() { + GameRenderer::Init(); pickingPass = &AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); - shadowMapPass = &AddRenderPass(); - opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); - transparentPass = &AddRenderPass(); - uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); postOutlinePass = &AddRenderPass(ctx); - guiPass = &AddRenderPass(); - guiPass->SetImGUIContext(guiCtx); } SH_EDITOR_API void EditorRenderer::Setup(const render::RenderData& data) { - if (data.tag == "ImGUI") - { - EnqueRenderPass(*guiPass); - return; - } if (data.tag == "EditorCamera") { - guiPass->viewportTexture = data.target; + guiPass->viewportTexture = data.GetRenderTargets()[0]; EnqueRenderPass(*outlinePass); EnqueRenderPass(*opaquePass); EnqueRenderPass(*transparentPass); @@ -46,19 +34,6 @@ namespace sh::editor EnqueRenderPass(*pickingPass); return; } - if (data.tag == "ImGUI") - { - EnqueRenderPass(*guiPass); - return; - } - if (data.tag == "Shadow") - { - EnqueRenderPass(*shadowMapPass); - return; - } - // 그 외 카메라 - EnqueRenderPass(*opaquePass); - EnqueRenderPass(*transparentPass); - EnqueRenderPass(*uiPass); + GameRenderer::Setup(data); } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Editor/EditorResource.cpp b/src/Editor/EditorResource.cpp index d0906ad4..040d5ab8 100644 --- a/src/Editor/EditorResource.cpp +++ b/src/Editor/EditorResource.cpp @@ -20,7 +20,7 @@ namespace sh::editor { EditorResource::~EditorResource() { - for (auto icon : icons) + for (game::GUITexture* icon : icons) icon->Destroy(); } SH_EDITOR_API void EditorResource::LoadAllAssets(Project& project) @@ -82,45 +82,42 @@ namespace sh::editor for (auto icon : icons) icon->SetName("icon"); - auto lineShader = shaders.insert_or_assign("Line", static_cast(shaderLoader.Load("shaders/line.shader"))).first->second; - auto errorShader = shaders.insert_or_assign("ErrorShader", static_cast(shaderLoader.Load("shaders/error.shader"))).first->second; - auto gridShader = shaders.insert_or_assign("GridShader", static_cast(shaderLoader.Load("shaders/grid.shader"))).first->second; - auto pickingShader = shaders.insert_or_assign("EditorPickingShader", static_cast(shaderLoader.Load("shaders/EditorPicking.shader"))).first->second; - auto outlineShader = shaders.insert_or_assign("OutlineShader", static_cast(shaderLoader.Load("shaders/outline.shader"))).first->second; - auto triangleShader = shaders.insert_or_assign("TriangleShader", static_cast(shaderLoader.Load("shaders/triangle.shader"))).first->second; - auto outlinePreShader = shaders.insert_or_assign("OutlinePreShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePre.shader"))).first->second; - auto outlinePostShader = shaders.insert_or_assign("OutlinePostShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePost.shader"))).first->second; - auto uiTextShader = shaders.insert_or_assign("UITextShader", static_cast(shaderLoader.Load("shaders/UIText.shader"))).first->second; - - auto blackTex = textures.insert_or_assign("BlackTexture", static_cast(texLoader.Load("textures/black.png"))).first->second; + render::Shader* const lineShader = shaders.insert_or_assign("Line", static_cast(shaderLoader.Load("shaders/line.shader"))).first->second; + render::Shader* const errorShader = shaders.insert_or_assign("ErrorShader", static_cast(shaderLoader.Load("shaders/error.shader"))).first->second; + render::Shader* const gridShader = shaders.insert_or_assign("GridShader", static_cast(shaderLoader.Load("shaders/grid.shader"))).first->second; + render::Shader* const pickingShader = shaders.insert_or_assign("EditorPickingShader", static_cast(shaderLoader.Load("shaders/EditorPicking.shader"))).first->second; + render::Shader* const outlineShader = shaders.insert_or_assign("OutlineShader", static_cast(shaderLoader.Load("shaders/outline.shader"))).first->second; + render::Shader* const triangleShader = shaders.insert_or_assign("TriangleShader", static_cast(shaderLoader.Load("shaders/triangle.shader"))).first->second; + render::Shader* const outlinePreShader = shaders.insert_or_assign("OutlinePreShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePre.shader"))).first->second; + render::Shader* const outlinePostShader = shaders.insert_or_assign("OutlinePostShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePost.shader"))).first->second; + render::Shader* const uiTextShader = shaders.insert_or_assign("UITextShader", static_cast(shaderLoader.Load("shaders/UIText.shader"))).first->second; + render::Shader* const ssaoShader = shaders.insert_or_assign("SSAOShader", static_cast(shaderLoader.Load("shaders/SSAO.shader"))).first->second; + ssaoShader->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }); + + render::Texture* const blackTex = textures.insert_or_assign("BlackTexture", static_cast(texLoader.Load("textures/black.png"))).first->second; blackTex->Build(*renderer.GetContext()); blackTex->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f18" }); - auto errorMat = materials.insert_or_assign("ErrorMaterial", core::SObject::Create(errorShader)).first->second; - auto lineMat = materials.insert_or_assign("LineMaterial", core::SObject::Create(lineShader)).first->second; - auto gridMat = materials.insert_or_assign("GridMaterial", core::SObject::Create(gridShader)).first->second; - auto pickingMat = materials.insert_or_assign("PickingMaterial", core::SObject::Create(pickingShader)).first->second; - auto triMat = materials.insert_or_assign("TriangleMaterial", core::SObject::Create(triangleShader)).first->second; - auto outlinePreMat = materials.insert_or_assign("OutlinePreMaterial", core::SObject::Create(outlinePreShader)).first->second; - auto outlinePostMat = materials.insert_or_assign("OutlinePostMaterial", core::SObject::Create(outlinePostShader)).first->second; - auto uiTextMat = materials.insert_or_assign("UITextMaterial", core::SObject::Create(uiTextShader)).first->second; + render::Material* const errorMat = materials.insert_or_assign("ErrorMaterial", core::SObject::Create(errorShader)).first->second; + render::Material* const lineMat = materials.insert_or_assign("LineMaterial", core::SObject::Create(lineShader)).first->second; + render::Material* const gridMat = materials.insert_or_assign("GridMaterial", core::SObject::Create(gridShader)).first->second; + render::Material* const pickingMat = materials.insert_or_assign("PickingMaterial", core::SObject::Create(pickingShader)).first->second; + render::Material* const triMat = materials.insert_or_assign("TriangleMaterial", core::SObject::Create(triangleShader)).first->second; + render::Material* const outlinePreMat = materials.insert_or_assign("OutlinePreMaterial", core::SObject::Create(outlinePreShader)).first->second; + render::Material* const outlinePostMat = materials.insert_or_assign("OutlinePostMaterial", core::SObject::Create(outlinePostShader)).first->second; + render::Material* const uiTextMat = materials.insert_or_assign("UITextMaterial", core::SObject::Create(uiTextShader)).first->second; - auto sphereModel = models.insert_or_assign("SphereModel", static_cast(modelLoader.Load("model/Sphere.obj"))).first->second; + render::Model* const sphereModel = models.insert_or_assign("SphereModel", static_cast(modelLoader.Load("model/Sphere.obj"))).first->second; sphereModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f15" }); sphereModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f19" }); - auto cubeModel = models.insert_or_assign("CubeModel", static_cast(modelLoader.Load("model/cube.obj"))).first->second; + render::Model* const cubeModel = models.insert_or_assign("CubeModel", static_cast(modelLoader.Load("model/cube.obj"))).first->second; cubeModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f16" }); cubeModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f20" }); - auto planeModel = models.insert_or_assign("PlaneModel", static_cast(modelLoader.Load("model/Plane.glb"))).first->second; + render::Model* const planeModel = models.insert_or_assign("PlaneModel", static_cast(modelLoader.Load("model/Plane.glb"))).first->second; planeModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f17" }); planeModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f21" }); - render::RenderTargetLayout rt{}; - rt.format = render::TextureFormat::R8; - rt.depthFormat = render::TextureFormat::D24S8; - rt.bUseMSAA = false; - - render::RenderTexture* outlineTexture = core::SObject::Create(rt); + render::RenderTexture* outlineTexture = core::SObject::Create(render::TextureFormat::R8, render::TextureFormat::D24S8, false); outlineTexture->SetSize(1024, 1024); outlineTexture->Build(*renderer.GetContext()); textures.insert_or_assign("OutlineTexture", outlineTexture); diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index c3b5916a..4e40dc94 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -13,6 +13,7 @@ #include "Render/Mesh/Grid.h" #include "Render/Model.h" #include "Render/ShadowMapManager.h" +#include "Render/RenderPass/SSAOPass.h" #include "Editor/Component/EditorControl.h" @@ -105,18 +106,13 @@ namespace sh::editor if (viewportPtr != nullptr && viewportPtr->IsPendingKill()) viewportPtr->SetUUID(core::UUID::Generate()); - render::RenderTargetLayout rt{}; - rt.format = render::TextureFormat::SRGBA32; - rt.depthFormat = render::TextureFormat::D24S8; - rt.bUseMSAA = true; - - viewportTexture = core::SObject::Create(rt); + viewportTexture = core::SObject::Create(render::TextureFormat::SRGBA32, render::TextureFormat::D24S8, true); viewportTexture->SetUUID(core::UUID{ "180635b4e4d1a98ebb0064ab47dc452a" }); viewportTexture->Build(*renderer.GetContext()); } else viewportTexture = viewportPtr; - editorRenderer->GetGUIPass()->viewportTexture = viewportTexture; + editorRenderer->GetGUIPass().viewportTexture = viewportTexture; game::GameObject* const camObj = AddGameObject("EditorCamera"); camObj->transform->SetPosition({ 2.f, 2.f, 2.f }); diff --git a/src/Game/Component/Render/Camera.cpp b/src/Game/Component/Render/Camera.cpp index 06a04c63..c44b089a 100644 --- a/src/Game/Component/Render/Camera.cpp +++ b/src/Game/Component/Render/Camera.cpp @@ -33,6 +33,7 @@ namespace sh::game height = size.y; viewer.viewportRect = { 0, 0, size.x, size.y }; viewer.viewportScissor = { 0, 0, size.x, size.y }; + renderData.ClearRenderTargets(); } else { @@ -42,7 +43,7 @@ namespace sh::game viewer.viewportRect = { 0, 0, size.x, size.y }; viewer.viewportScissor = { 0, 0, size.x, size.y }; - renderData.target = renderTexture; + renderData.SetRenderTarget(renderTexture); } gameObject.transform->UpdateMatrix(); viewer.pos = gameObject.transform->GetWorldPosition(); @@ -125,4 +126,4 @@ namespace sh::game renderData.renderViewers.front().projMatrix = glm::orthoRH_ZO(-dis * aspect, dis * aspect, -dis, dis, nearPlane, farPlane); } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Game/Component/Render/PickingCamera.cpp b/src/Game/Component/Render/PickingCamera.cpp index 68dd5753..9344dc8f 100644 --- a/src/Game/Component/Render/PickingCamera.cpp +++ b/src/Game/Component/Render/PickingCamera.cpp @@ -19,12 +19,7 @@ namespace sh::game { pixels.resize(4); - render::RenderTargetLayout rt{}; - rt.format = render::TextureFormat::RGBA32; - rt.depthFormat = render::TextureFormat::D24S8; - rt.bUseMSAA = false; - - renderTex = core::SObject::Create(rt); + renderTex = core::SObject::Create(render::TextureFormat::RGBA32, render::TextureFormat::D24S8, false); renderTex->SetSize(1024, 768); renderTex->Build(*world.renderer.GetContext()); renderTex->SetName("PickingFramebuffer"); diff --git a/src/Game/Component/Render/SSAOComponent.cpp b/src/Game/Component/Render/SSAOComponent.cpp new file mode 100644 index 00000000..c9202d24 --- /dev/null +++ b/src/Game/Component/Render/SSAOComponent.cpp @@ -0,0 +1,199 @@ +#include "Component/Render/SSAOComponent.h" +#include "Component/Render/Camera.h" +#include "World.h" +#include "GameObject.h" +#include "GameRenderer.h" + +#include "Core/SObject.h" +#include "Core/GarbageCollection.h" +#include "Core/Util.h" + +#include "Render/Renderer.h" +#include "Render/IRenderContext.h" +#include "Render/Material.h" +#include "Render/RenderTexture.h" +#include "Render/RenderPass/CombinePass.h" +#include "Render/RenderPass/SSAOPass.h" + +#include +#include + +#include +#include +namespace sh::game +{ + std::array SSAOComponent::kernel{}; + bool SSAOComponent::bKernelInit = false; + + SSAOComponent::SSAOComponent(GameObject& owner) : + Component(owner) + { + canPlayInEditor = true; + + depthRenderData.tag = core::Name{ "Depth" }; + ssaoRenderData.tag = core::Name{ "SSAO" }; + combineRenderData.tag = core::Name{ "Combine" }; + depthRenderData.priority = 2; + ssaoRenderData.priority = 1; + combineRenderData.priority = -1; + combineRenderData.ClearRenderTargets(); + } + SSAOComponent::~SSAOComponent() = default; + + SH_GAME_API void SSAOComponent::Awake() + { + camera = gameObject.GetComponent(); + if (camera == nullptr) + SH_ERROR("SSAOComponent requires a Camera component on the same GameObject"); + CreateKernel(); + + mat = core::SObject::Create(static_cast(core::SObject::GetSObjectUsingResolver(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }))); + mat->Build(*world.renderer.GetContext()); + SetMaterial(mat); + } + SH_GAME_API void SSAOComponent::OnDestroy() + { + if (depthRT != nullptr) + depthRT->Destroy(); + if (normalRT != nullptr) + normalRT->Destroy(); + if (aoRT != nullptr) + aoRT->Destroy(); + if (noiseTex != nullptr) + noiseTex->Destroy(); + if (mat != nullptr) + mat->Destroy(); + + Super::OnDestroy(); + } + SH_GAME_API void SSAOComponent::BeginUpdate() + { + if (camera == nullptr) + return; + + depthRenderData.renderViewers = ssaoRenderData.renderViewers = combineRenderData.renderViewers = camera->GetRenderData().renderViewers; + combineRenderData.SetRenderTargets(camera->GetRenderData().GetRenderTargets()); + EnsureRenderTextures(); + UpdateMaterialUniforms(); + + world.renderer.PushRenderData(depthRenderData); + world.renderer.PushRenderData(ssaoRenderData); + world.renderer.PushRenderData(combineRenderData); + } + SH_GAME_API void SSAOComponent::SetMaterial(render::Material* _mat) + { + mat = _mat; + if (mat == nullptr) + return; + + GameRenderer* const renderer = dynamic_cast(world.renderer.GetScriptableRenderer()); + if (renderer == nullptr) + return; + + render::SSAOPass& ssaoPass = renderer->GetSSAOPass(); + ssaoPass.SetMaterial(*mat); + + render::CombinePass& combinePass = renderer->GetCombinePass(); + combinePass.SetMaterial(*mat); + } + + void SSAOComponent::EnsureRenderTextures() + { + uint32_t w = static_cast(camera->GetWidth()); + uint32_t h = static_cast(camera->GetHeight()); + if (w == 0 || h == 0) + return; + + render::IRenderContext* ctx = world.renderer.GetContext(); + if (ctx == nullptr) + return; + + core::GarbageCollection& gc = *core::GarbageCollection::GetInstance(); + + if (depthRT == nullptr) + { + depthRT = core::SObject::Create(render::TextureFormat::None, render::TextureFormat::D32, false); + depthRT->SetSize(w, h); + depthRT->Build(*ctx); + } + if (normalRT == nullptr) + { + normalRT = core::SObject::Create(render::TextureFormat::RGBA32, render::TextureFormat::None, false); + normalRT->SetSize(w, h); + normalRT->Build(*ctx); + } + depthRenderData.SetRenderTargets({ depthRT, normalRT }); + + if (aoRT == nullptr) + { + aoRT = core::SObject::Create(render::TextureFormat::R8, render::TextureFormat::None, false); + aoRT->SetSize(w, h); + aoRT->Build(*ctx); + + ssaoRenderData.SetRenderTarget(aoRT); + } + + if (noiseTex == nullptr) + { + noiseTex = core::SObject::Create(render::TextureFormat::RG32F, 4, 4, false); + std::array noise{}; // 4x4x2 channels, R16G16_SFLOAT + for (int i = 0; i < 32; ++i) + noise[i] = glm::packHalf1x16(core::Util::RandomRange(-1.f, 1.f)); + noiseTex->SetPixelData(reinterpret_cast(noise.data()), sizeof(uint16_t) * noise.size()); + noiseTex->SetAnisoLevel(0); + noiseTex->SetFiltering(render::Texture::Filtering::Box); + noiseTex->Build(*ctx); + } + + if (w != cachedWidth || h != cachedHeight) + { + depthRT->SetSize(w, h); + normalRT->SetSize(w, h); + aoRT->SetSize(w, h); + cachedWidth = w; + cachedHeight = h; + } + } + void SSAOComponent::UpdateMaterialUniforms() + { + if (!core::IsValid(mat)) + return; + + const glm::mat4& proj = camera->GetProjMatrix(); + const glm::mat4 invProj = glm::inverse(proj); + + mat->SetProperty("kernel", kernel); + mat->SetProperty("depthTex", depthRT); + mat->SetProperty("normalTex", normalRT); + mat->SetProperty("noiseTex", noiseTex); + mat->SetProperty("aoTex", aoRT); + mat->UpdateUniformBuffers(); + //material->SetProperty("invProj", invProj); + //material->SetProperty("proj", proj); + //material->SetProperty("radius", radius); + //material->SetProperty("bias", bias); + //material->SetProperty("power", power); + //material->SetProperty("depthTex", static_cast(depthRT)); + } + void SSAOComponent::CreateKernel() + { + if (bKernelInit) + return; + bKernelInit = true; + + for (int i = 0; i < 64; ++i) + { + glm::vec3 sample( + core::Util::RandomRange(-1.f, 1.f), + core::Util::RandomRange(-1.f, 1.f), + core::Util::RandomRange(0.f, 1.f) + ); + sample = glm::normalize(sample); + sample *= core::Util::RandomRange(0.f, 1.f); + float scale = i / 64.f; + scale = glm::mix(0.1f, 1.0f, scale * scale); + sample *= scale; + kernel[i] = sample; + } + } +}//namespace diff --git a/src/Game/Component/TestComputeShader.cpp b/src/Game/Component/TestComputeShader.cpp new file mode 100644 index 00000000..d58d26d1 --- /dev/null +++ b/src/Game/Component/TestComputeShader.cpp @@ -0,0 +1,53 @@ +#include "Component/TestComputeShader.h" + +#include "Game/World.h" +#include "Game/Input.h" + +#include "Render/Renderer.h" +#include "Render/ScriptableRenderer.h" +#include "Render/ComputeShader.h" + +#include "Core/Logger.h" + +namespace sh::game +{ + TestComputeShader::TestComputeShader(GameObject& owner) : + Component(owner) + { + } + + SH_GAME_API void TestComputeShader::Update() + { + if (!Input::GetKeyPressed(Input::KeyCode::C)) + return; + + if (shader == nullptr) + { + SH_ERROR("TestComputeShader: shader is null"); + return; + } + + render::ScriptableRenderer* const scriptableRenderer = world.renderer.GetScriptableRenderer(); + if (scriptableRenderer == nullptr) + { + SH_ERROR("TestComputeShader: ScriptableRenderer is null"); + return; + } + std::vector a(10'000, 0); + std::vector b(10'000, 0); + std::vector c(10'000, 0); + for (int i = 0; i < 10'000; ++i) + { + a[i] = i; + b[i] = 10'000 - i; + } + shader->SetFloats("bufferA", a.data(), a.size()); + shader->SetFloats("bufferB", b.data(), b.size()); + shader->SetFloats("bufferC", c.data(), c.size()); + const uint32_t workGroup = std::ceil(10'000.f / 128.f); + SH_INFO_FORMAT("Dispatch ComputeShader [{} x {} x {}]", workGroup, 1, 1); + scriptableRenderer->Dispatch(*shader, workGroup, 1, 1); + std::vector buffer = shader->GetBuffer("bufferC")->GetData(); + std::memcpy(c.data(), buffer.data(), buffer.size()); + } +}//namespace diff --git a/src/Game/GUIPass.cpp b/src/Game/GUIPass.cpp index aeefca97..2bfe2bac 100644 --- a/src/Game/GUIPass.cpp +++ b/src/Game/GUIPass.cpp @@ -19,18 +19,18 @@ namespace sh::game SH_GAME_API void GUIPass::Configure(const render::RenderData& renderData) { renderTextures.clear(); - renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; + SetRenderTargetImageUsages(renderData); if (viewportTexture != nullptr) renderTextures[viewportTexture] = render::ResourceUsage::SampledRead; } SH_GAME_API void GUIPass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { - cmd.SetRenderData(renderData, false, true, false, false); + cmd.SetRenderData(renderData, false, true, true, false); for (const render::RenderViewer& viewer : renderData.renderViewers) { SetViewportScissor(cmd, ctx, viewer); ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 6f355040..0ea21804 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -2,7 +2,10 @@ #include "GUIPass.h" #include "World.h" +#include "Render/RenderPass/DepthPass.h" #include "Render/RenderPass/TransparentPass.h" +#include "Render/RenderPass/SSAOPass.h" +#include "Render/RenderPass/CombinePass.h" #include namespace sh::game @@ -16,27 +19,41 @@ namespace sh::game } SH_GAME_API void GameRenderer::Init() { - //AddShadowPass(); - AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); - AddRenderPass(); - AddRenderPass("UI", render::RenderQueue::UI); + depthPass = &AddRenderPass(); + opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); + ssaoPass = &AddRenderPass(renderCtx); + combinePass = &AddRenderPass(renderCtx); + transparentPass = &AddRenderPass(); + uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); guiPass = &AddRenderPass(); guiPass->SetImGUIContext(guiCtx); } SH_GAME_API void GameRenderer::Setup(const render::RenderData& data) { + if (data.tag == "Depth") + { + EnqueRenderPass(*depthPass); + return; + } + if (data.tag == "SSAO") + { + EnqueRenderPass(*ssaoPass); + return; + } + if (data.tag == "Combine") + { + EnqueRenderPass(*combinePass); + return; + } if (data.tag == "ImGUI") { if (guiPass != nullptr) EnqueRenderPass(*guiPass); return; } - - for (const std::unique_ptr& pass : allPasses) - { - if (pass.get() == guiPass) - continue; - EnqueRenderPass(*pass); - } + // 그 외 카메라 + EnqueRenderPass(*opaquePass); + EnqueRenderPass(*transparentPass); + EnqueRenderPass(*uiPass); } }//namespace diff --git a/src/Game/World.cpp b/src/Game/World.cpp index b97a00e3..bfabdb74 100644 --- a/src/Game/World.cpp +++ b/src/Game/World.cpp @@ -530,6 +530,7 @@ namespace sh::game AddAfterSyncTask( [this, savePoint = it->second]() { + renderer.Reset(); Deserialize(savePoint); } ); diff --git a/src/Render/Material.cpp b/src/Render/Material.cpp index 1426d620..079592eb 100644 --- a/src/Render/Material.cpp +++ b/src/Render/Material.cpp @@ -6,6 +6,8 @@ #include "Core/SContainer.hpp" #include "Core/Logger.h" +#include + namespace sh::render { SH_RENDER_API Material::Material() : @@ -161,69 +163,147 @@ namespace sh::render bool isSampler = false; for (const UniformStructLayout::UniformMember& member : uniformLayout->GetMembers()) { + const auto writeArrayFn = [&](const auto* values, auto convertFn) + { + if (values == nullptr || member.count == 0) + return; + + const uint32_t stride = member.layoutSize / member.count; + const uint32_t count = std::min(member.count, static_cast(values->size())); + for (uint32_t i = 0; i < count; ++i) + { + auto converted = convertFn((*values)[i]); + SetData(converted, data, member.offset + stride * i); + } + }; + if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetScalarProperty(member.name); - if (var.has_value()) - SetData(static_cast(var.value()), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetIntArrayProperty(member.name), + [](int value) { return value; }); + } else - SetData(0, data, member.offset); + { + auto var = propertyBlock.GetScalarProperty(member.name); + if (var.has_value()) + SetData(static_cast(var.value()), data, member.offset); + else + SetData(0, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetScalarProperty(member.name); - if (var.has_value()) - SetData(var.value(), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetScalarArrayProperty(member.name), + [](float value) { return value; }); + } else - SetData(0.0f, data, member.offset); + { + auto var = propertyBlock.GetScalarProperty(member.name); + if (var.has_value()) + SetData(var.value(), data, member.offset); + else + SetData(0.0f, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetVectorProperty(member.name); - if (var) - SetData(glm::vec2{ var->x, var->y }, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetVectorArrayProperty(member.name), + [](const glm::vec4& value) { return glm::vec2{ value.x, value.y }; }); + } else - SetData(glm::vec2{ 0.f }, data, member.offset); + { + auto var = propertyBlock.GetVectorProperty(member.name); + if (var) + SetData(glm::vec2{ var->x, var->y }, data, member.offset); + else + SetData(glm::vec2{ 0.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetVectorProperty(member.name); - if (var) - SetData(glm::vec3{ var->x, var->y, var->z }, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetVectorArrayProperty(member.name), + [](const glm::vec4& value) { return glm::vec3{ value.x, value.y, value.z }; }); + } else - SetData(glm::vec3{ 0.f }, data, member.offset); + { + auto var = propertyBlock.GetVectorProperty(member.name); + if (var) + SetData(glm::vec3{ var->x, var->y, var->z }, data, member.offset); + else + SetData(glm::vec3{ 0.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetVectorProperty(member.name); - if (var) - SetData(*var, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetVectorArrayProperty(member.name), + [](const glm::vec4& value) { return value; }); + } else - SetData(glm::vec4{ 0.f }, data, member.offset); + { + auto var = propertyBlock.GetVectorProperty(member.name); + if (var) + SetData(*var, data, member.offset); + else + SetData(glm::vec4{ 0.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetMatrixProperty(member.name); - if (var) - SetData(core::Util::ConvertMat4ToMat2(*var), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetMatrixArrayProperty(member.name), + [](const glm::mat4& value) { return core::Util::ConvertMat4ToMat2(value); }); + } else - SetData(glm::mat2{ 1.f }, data, member.offset); + { + auto var = propertyBlock.GetMatrixProperty(member.name); + if (var) + SetData(core::Util::ConvertMat4ToMat2(*var), data, member.offset); + else + SetData(glm::mat2{ 1.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetMatrixProperty(member.name); - if (var) - SetData(core::Util::ConvertMat4ToMat3(*var), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetMatrixArrayProperty(member.name), + [](const glm::mat4& value) { return core::Util::ConvertMat4ToMat3(value); }); + } else - SetData(glm::mat3{ 1.f }, data, member.offset); + { + auto var = propertyBlock.GetMatrixProperty(member.name); + if (var) + SetData(core::Util::ConvertMat4ToMat3(*var), data, member.offset); + else + SetData(glm::mat3{ 1.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetMatrixProperty(member.name); - if (var) - SetData(*var, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetMatrixArrayProperty(member.name), + [](const glm::mat4& value) { return value; }); + } else - SetData(glm::mat2{ 1.f }, data, member.offset); + { + auto var = propertyBlock.GetMatrixProperty(member.name); + if (var) + SetData(*var, data, member.offset); + else + SetData(glm::mat4{ 1.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { @@ -380,6 +460,132 @@ namespace sh::render } } } + if (propertyJson.contains("matrix")) + { + const auto& matJson = propertyJson["matrix"]; + for (const auto& [name, value] : matJson.items()) + { + if (!value.is_array() || value.size() != 4) + continue; + + glm::mat4 mat{ 0.f }; + for (int i = 0; i < 4; ++i) + { + const core::Json& col = value[i]; + if (!col.is_array() || col.size() != 4) + continue; + mat[i] = { col[0], col[1], col[2], col[3] }; + } + SetProperty(name, mat); + } + } + if (propertyJson.contains("scalarArrays")) + { + const auto& scalarArrayJson = propertyJson["scalarArrays"]; + for (const auto& [name, value] : scalarArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values = value.get>(); + SetProperty(name, values); + } + } + if (propertyJson.contains("intArrays")) + { + const auto& intArrayJson = propertyJson["intArrays"]; + for (const auto& [name, value] : intArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values = value.get>(); + SetProperty(name, values); + } + } + if (propertyJson.contains("vectorArrays")) + { + const auto& vectorArrayJson = propertyJson["vectorArrays"]; + for (const auto& [name, value] : vectorArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values{}; + for (const core::Json& vecJson : value) + { + if (!vecJson.is_array() || vecJson.size() != 4) + continue; + values.push_back(glm::vec4{ + vecJson[0].get(), + vecJson[1].get(), + vecJson[2].get(), + vecJson[3].get() + }); + } + + const Shader::PropertyInfo* propInfo = core::IsValid(shader) ? shader->GetProperty(name) : nullptr; + if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector vec2Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + vec2Values[i] = glm::vec2{ values[i].x, values[i].y }; + SetProperty(name, vec2Values); + } + else if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector vec3Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + vec3Values[i] = glm::vec3{ values[i].x, values[i].y, values[i].z }; + SetProperty(name, vec3Values); + } + else + SetProperty(name, values); + } + } + if (propertyJson.contains("matrixArrays")) + { + const auto& matrixArrayJson = propertyJson["matrixArrays"]; + for (const auto& [name, value] : matrixArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values{}; + for (const core::Json& matJson : value) + { + if (!matJson.is_array() || matJson.size() != 4) + continue; + glm::mat4 mat{ 0.f }; + for (int i = 0; i < 4; ++i) + { + const core::Json& col = matJson[i]; + if (!col.is_array() || col.size() != 4) + continue; + mat[i] = { col[0], col[1], col[2], col[3] }; + } + values.push_back(mat); + } + + const Shader::PropertyInfo* propInfo = core::IsValid(shader) ? shader->GetProperty(name) : nullptr; + if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector mat2Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + mat2Values[i] = core::Util::ConvertMat4ToMat2(values[i]); + SetProperty(name, mat2Values); + } + else if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector mat3Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + mat3Values[i] = core::Util::ConvertMat4ToMat3(values[i]); + SetProperty(name, mat3Values); + } + else + SetProperty(name, values); + } + } if (propertyJson.contains("textures")) { const auto& texJson = propertyJson["textures"]; @@ -409,4 +615,4 @@ namespace sh::render } } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index 118d4b5d..aef336d0 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -312,18 +312,21 @@ namespace sh::render IShaderBinding& shaderBindings = *itUb->second.get(); shaderBindings.Link(binding, *shaderBindingsSyncData.tex); + auto it = std::find_if(cachedRTs.begin(), cachedRTs.end(), + [&](const CachedRT& cached) + { + return cached.set == set && cached.binding == binding && cached.pass == shaderPass; + }); + if (shaderBindingsSyncData.tex->GetType().IsChildOf(RenderTexture::GetStaticType())) { - auto it = std::find_if(cachedRTs.begin(), cachedRTs.end(), - [&](const CachedRT& cached) - { - if (cached.set == set && cached.binding == binding && cached.pass == shaderPass) - return true; - return false; - } - ); + const RenderTexture* const rt = static_cast(shaderBindingsSyncData.tex); if (it == cachedRTs.end()) - cachedRTs.push_back(CachedRT{ set, binding, shaderPass, static_cast(shaderBindingsSyncData.tex) }); + cachedRTs.push_back(CachedRT{ set, binding, shaderPass, rt }); + else + it->rt = rt; } + else if (it != cachedRTs.end()) + cachedRTs.erase(it); } }//namespace \ No newline at end of file diff --git a/src/Render/MaterialPropertyBlock.cpp b/src/Render/MaterialPropertyBlock.cpp index f73025b6..deb3574a 100644 --- a/src/Render/MaterialPropertyBlock.cpp +++ b/src/Render/MaterialPropertyBlock.cpp @@ -8,6 +8,10 @@ namespace sh::render scalarProperties.clear(); vecProperties.clear(); matProperties.clear(); + intArrayProperties.clear(); + scalarArrayProperties.clear(); + vecArrayProperties.clear(); + matArrayProperties.clear(); textureProperties.clear(); } SH_RENDER_API void MaterialPropertyBlock::SetProperty(const std::string& name, const Texture* data) @@ -42,6 +46,34 @@ namespace sh::render return nullptr; return it->second; } + SH_RENDER_API auto MaterialPropertyBlock::GetIntArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = intArrayProperties.find(name); + if (it == intArrayProperties.end()) + return nullptr; + return &it->second; + } + SH_RENDER_API auto MaterialPropertyBlock::GetScalarArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = scalarArrayProperties.find(name); + if (it == scalarArrayProperties.end()) + return nullptr; + return &it->second; + } + SH_RENDER_API auto MaterialPropertyBlock::GetVectorArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = vecArrayProperties.find(name); + if (it == vecArrayProperties.end()) + return nullptr; + return &it->second; + } + SH_RENDER_API auto MaterialPropertyBlock::GetMatrixArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = matArrayProperties.find(name); + if (it == matArrayProperties.end()) + return nullptr; + return &it->second; + } SH_RENDER_API auto MaterialPropertyBlock::GetTextureProperties() const -> const std::unordered_map& { return textureProperties; @@ -68,6 +100,37 @@ namespace sh::render } propertiesJson["matrix"] = std::move(matrixJson); + core::Json intArrayJson{}; + for (auto& [name, values] : intArrayProperties) + intArrayJson[name] = values; + propertiesJson["intArrays"] = std::move(intArrayJson); + + core::Json scalarArrayJson{}; + for (auto& [name, values] : scalarArrayProperties) + scalarArrayJson[name] = values; + propertiesJson["scalarArrays"] = std::move(scalarArrayJson); + + core::Json vectorArrayJson{}; + for (auto& [name, values] : vecArrayProperties) + { + for (const glm::vec4& value : values) + vectorArrayJson[name].push_back({ value.x, value.y, value.z, value.w }); + } + propertiesJson["vectorArrays"] = std::move(vectorArrayJson); + + core::Json matrixArrayJson{}; + for (auto& [name, values] : matArrayProperties) + { + for (const glm::mat4& value : values) + { + core::Json matJson{}; + for (int i = 0; i < 4; ++i) + matJson.push_back({ value[i].x, value[i].y, value[i].z, value[i].w }); + matrixArrayJson[name].push_back(std::move(matJson)); + } + } + propertiesJson["matrixArrays"] = std::move(matrixArrayJson); + core::Json texJson{}; for (auto& [name, value] : textureProperties) texJson[name] = value->GetUUID().ToString(); @@ -120,6 +183,77 @@ namespace sh::render SetProperty(name, mat); } } + if (json.contains("scalarArrays")) + { + const auto& scalarArrayJson = json["scalarArrays"]; + for (const auto& [name, value] : scalarArrayJson.items()) + { + if (value.is_array()) + { + std::vector values = value.get>(); + SetArrayProperty(name, values.data(), values.size()); + } + } + } + if (json.contains("intArrays")) + { + const auto& intArrayJson = json["intArrays"]; + for (const auto& [name, value] : intArrayJson.items()) + { + if (value.is_array()) + { + std::vector values = value.get>(); + SetArrayProperty(name, values.data(), values.size()); + } + } + } + if (json.contains("vectorArrays")) + { + const auto& vectorArrayJson = json["vectorArrays"]; + for (const auto& [name, value] : vectorArrayJson.items()) + { + if (!value.is_array()) + continue; + std::vector values{}; + for (const core::Json& vecJson : value) + { + if (!vecJson.is_array() || vecJson.size() != 4) + continue; + values.push_back(glm::vec4{ + vecJson[0].get(), + vecJson[1].get(), + vecJson[2].get(), + vecJson[3].get() + }); + } + SetArrayProperty(name, values.data(), values.size()); + } + } + if (json.contains("matrixArrays")) + { + const auto& matrixArrayJson = json["matrixArrays"]; + for (const auto& [name, value] : matrixArrayJson.items()) + { + if (!value.is_array()) + continue; + std::vector values{}; + for (const core::Json& matJson : value) + { + if (!matJson.is_array() || matJson.size() != 4) + continue; + glm::mat4 mat{ 0.f }; + for (int i = 0; i < 4; ++i) + { + const core::Json& col = matJson[i]; + if (!col.is_array() || col.size() != 4) + continue; + mat[i] = { col[0], col[1], col[2], col[3] }; + } + values.push_back(mat); + } + SetArrayProperty(name, values.data(), values.size()); + } + } if (json.contains("textures")) { const auto& texJson = json["textures"]; @@ -134,4 +268,4 @@ namespace sh::render } } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/RenderDataManager.cpp b/src/Render/RenderDataManager.cpp index baa89593..63063255 100644 --- a/src/Render/RenderDataManager.cpp +++ b/src/Render/RenderDataManager.cpp @@ -58,7 +58,7 @@ namespace sh::render BufferData data; data.view = viewer.viewMatrix; data.proj = viewer.projMatrix; - data.pos = viewer.pos; + data.pos = glm::vec4{ viewer.pos, 1.0f }; buffer->SetData(&data, offset, sizeof(BufferData)); @@ -81,4 +81,4 @@ namespace sh::render { return core::ArrayView{renderDatas.data(), renderDatasSize}; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/RenderPass/CombinePass.cpp b/src/Render/RenderPass/CombinePass.cpp new file mode 100644 index 00000000..c47c883c --- /dev/null +++ b/src/Render/RenderPass/CombinePass.cpp @@ -0,0 +1,103 @@ +#include "RenderPass/CombinePass.h" +#include "CommandBuffer.h" +#include "Drawable.h" +#include "IRenderContext.h" +#include "Material.h" +#include "Mesh.h" + +#include "Core/GarbageCollection.h" +#include "Core/SObject.h" + +namespace sh::render +{ + CombinePass::CombinePass(const IRenderContext& ctx) : + ScriptableRenderPass(core::Name{ "CombinePass" }, RenderQueue::AfterRendering), + ctx(ctx) + { + resource.plane = core::SObject::Create(); + + resource.plane->SetVertex({ + {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, -1.0f, 0.0f}, {1.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, 1.0f, 0.0f}, {1.0f, 0.0f}, {0.f, 0.f, 0.f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f}, {0.f, 0.f, 0.f}}, + }); + resource.plane->SetIndices({ 0, 1, 2, 2, 3, 0 }); + resource.plane->Build(ctx); + + onMatDestroy.SetCallback( + [this](const core::SObject* objPtr) + { + resource.mat = nullptr; + if (resource.drawable != nullptr) + resource.drawable->Destroy(); + resource.drawable = nullptr; + } + ); + } + CombinePass::~CombinePass() + { + core::GarbageCollection& gc = *core::GarbageCollection::GetInstance(); + gc.ForceDelete(resource.plane); + gc.ForceDelete(resource.drawable); + resource.plane = nullptr; + resource.drawable = nullptr; + resource.mat = nullptr; + } + + SH_RENDER_API void CombinePass::SetMaterial(Material& mat) + { + if (resource.mat == &mat || mat.IsPendingKill()) + return; + resource.mat = &mat; + resource.mat->onDestroy.Register(onMatDestroy); + if (resource.drawable != nullptr) + resource.drawable->SetMaterial(*resource.mat); + } + + void CombinePass::EnsureDrawable() + { + if (resource.drawable != nullptr || resource.mat == nullptr) + return; + resource.drawable = core::SObject::Create(*resource.mat, *resource.plane); + resource.drawable->Build(ctx); + } + + SH_RENDER_API void CombinePass::Configure(const RenderData& renderData) + { + renderTextures.clear(); + + if (!core::IsValid(resource.mat)) + return; + + EnsureDrawable(); + if (resource.drawable == nullptr || !core::IsValid(resource.drawable->GetMesh()) || !core::IsValid(resource.drawable->GetMaterial())) + return; + + SetRenderTargetImageUsages(renderData); + SetImageUsages(*resource.mat); + } + + SH_RENDER_API void CombinePass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + if (!core::IsValid(resource.mat) || !core::IsValid(resource.drawable)) + return; + + cmd.SetRenderData(renderData, false, false, true, false); + + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + cmd.DrawMesh(*resource.drawable, passName, viewerIdx); + ++viewerIdx; + } + } + + SH_RENDER_API void CombinePass::Resource::PushReferenceObjects(core::GarbageCollection& gc) + { + gc.PushReferenceObject(plane); + gc.PushReferenceObject(drawable); + gc.PushReferenceObject(mat); + } +}//namespace diff --git a/src/Render/RenderPass/DepthPass.cpp b/src/Render/RenderPass/DepthPass.cpp new file mode 100644 index 00000000..e0b2e1ef --- /dev/null +++ b/src/Render/RenderPass/DepthPass.cpp @@ -0,0 +1,32 @@ +#include "RenderPass/DepthPass.h" +#include "CommandBuffer.h" +#include "IRenderContext.h" + +namespace sh::render +{ + DepthPass::DepthPass() : + ScriptableRenderPass(core::Name{ "DepthPass" }, RenderQueue::BeforeRendering) + { + } + + SH_RENDER_API void DepthPass::Configure(const RenderData& renderData) + { + ScriptableRenderPass::Configure(renderData); + } + SH_RENDER_API void DepthPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + cmd.SetRenderData(renderData, true, true, true, true); + + if (renderData.GetDrawablesPtr() == nullptr) + return; + + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + for (const RenderBatch& batch : renderBatches) + cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); + ++viewerIdx; + } + } +}//namespace diff --git a/src/Render/RenderPass/SSAOPass.cpp b/src/Render/RenderPass/SSAOPass.cpp new file mode 100644 index 00000000..b8d6d2e1 --- /dev/null +++ b/src/Render/RenderPass/SSAOPass.cpp @@ -0,0 +1,105 @@ +#include "RenderPass/SSAOPass.h" +#include "RenderTexture.h" +#include "CommandBuffer.h" +#include "Mesh.h" +#include "Material.h" +#include "Drawable.h" +#include "IRenderContext.h" + +#include "Core/SObject.h" +#include "Core/GarbageCollection.h" + +namespace sh::render +{ + SSAOPass::SSAOPass(const IRenderContext& ctx) : + ScriptableRenderPass(core::Name{ "SSAOPass" }, RenderQueue::AfterOpaque), + ctx(ctx) + { + resource.plane = core::SObject::Create(); + + resource.plane->SetVertex({ + {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, -1.0f, 0.0f}, {1.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, 1.0f, 0.0f}, {1.0f, 0.0f}, {0.f, 0.f, 0.f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f}, {0.f, 0.f, 0.f}}, + }); + resource.plane->SetIndices({ 0, 1, 2, 2, 3, 0 }); + resource.plane->Build(ctx); + + onMatDestroy.SetCallback( + [this](const core::SObject* objPtr) + { + resource.mat = nullptr; + if (resource.drawable != nullptr) + resource.drawable->Destroy(); + resource.drawable = nullptr; + } + ); + } + SSAOPass::~SSAOPass() + { + core::GarbageCollection& gc = *core::GarbageCollection::GetInstance(); + gc.ForceDelete(resource.plane); + gc.ForceDelete(resource.drawable); + resource.plane = nullptr; + resource.drawable = nullptr; + resource.mat = nullptr; + } + + SH_RENDER_API void SSAOPass::SetMaterial(Material& mat) + { + if (resource.mat == &mat || mat.IsPendingKill()) + return; + resource.mat = &mat; + resource.mat->onDestroy.Register(onMatDestroy); + if (resource.drawable != nullptr) + resource.drawable->SetMaterial(*resource.mat); + } + + void SSAOPass::EnsureDrawable() + { + if (resource.drawable != nullptr || resource.mat == nullptr) + return; + resource.drawable = core::SObject::Create(*resource.mat, *resource.plane); + resource.drawable->Build(ctx); + } + + SH_RENDER_API void SSAOPass::Configure(const RenderData& renderData) + { + renderTextures.clear(); + + if (!core::IsValid(resource.mat)) + return; + + EnsureDrawable(); + if (resource.drawable == nullptr || !core::IsValid(resource.drawable->GetMesh()) || !core::IsValid(resource.drawable->GetMaterial())) + return; + + SetRenderTargetImageUsages(renderData); + SetImageUsages(*resource.mat); + } + SH_RENDER_API void SSAOPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + if (!core::IsValid(resource.mat) || !core::IsValid(resource.drawable)) + return; + localRenderData.SetRenderTargets(renderData.GetRenderTargets()); + localRenderData.renderViewers = renderData.renderViewers; + + cmd.SetRenderData(renderData, true, false, true, false); + + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : localRenderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + cmd.DrawMesh(*resource.drawable, passName, viewerIdx); + ++viewerIdx; + } + } + + SH_RENDER_API void SSAOPass::Resource::PushReferenceObjects(core::GarbageCollection& gc) + { + gc.PushReferenceObject(plane); + gc.PushReferenceObject(drawable); + gc.PushReferenceObject(mat); + } +}//namespace diff --git a/src/Render/RenderPass/ShadowMapPass.cpp b/src/Render/RenderPass/ShadowMapPass.cpp deleted file mode 100644 index db2ae667..00000000 --- a/src/Render/RenderPass/ShadowMapPass.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "RenderPass/ShadowMapPass.h" -#include "CommandBuffer.h" -#include "IRenderContext.h" - -namespace sh::render -{ - ShadowMapPass::ShadowMapPass() : - ScriptableRenderPass(core::Name{ "ShadowMapPass" }, RenderQueue::BeforeRendering) - { - } - - SH_RENDER_API void ShadowMapPass::Configure(const RenderData& renderData) - { - ScriptableRenderPass::Configure(renderData); - } - SH_RENDER_API void ShadowMapPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) - { - if (renderData.target == nullptr) - return; - - // 한 프레임 내 첫 그림자 호출에서만 아틀라스를 클리어하고, 이후엔 LOAD하여 다른 광원의 슬롯 결과를 보존 - if (renderData.frameIndex != lastFrameIndex) - { - lastFrameIndex = renderData.frameIndex; - callsThisFrame = 0; - } - const bool bClearDepth = (callsThisFrame == 0); - ++callsThisFrame; - - cmd.SetRenderData(renderData, false, bClearDepth, false, true); - - if (renderData.drawables == nullptr) - return; - - std::size_t viewerIdx = 0; - for (const RenderViewer& viewer : renderData.renderViewers) - { - SetViewportScissor(cmd, ctx, viewer); - for (const RenderBatch& batch : renderBatches) - cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); - ++viewerIdx; - } - } -}//namespace diff --git a/src/Render/RenderPass/TransparentPass.cpp b/src/Render/RenderPass/TransparentPass.cpp index d1d581aa..4fb05ad7 100644 --- a/src/Render/RenderPass/TransparentPass.cpp +++ b/src/Render/RenderPass/TransparentPass.cpp @@ -21,11 +21,11 @@ namespace sh::render { cmd.SetRenderData(renderTarget, false, false, true, false); - if (renderTarget.drawables == nullptr) + if (renderTarget.GetDrawablesPtr() == nullptr) return; items.clear(); - for (const Drawable* drawable : *renderTarget.drawables) + for (const Drawable* drawable : *renderTarget.GetDrawablesPtr()) { RenderItem item{}; item.material = drawable->GetMaterial(); diff --git a/src/Render/RenderTexture.cpp b/src/Render/RenderTexture.cpp index cc7cbaee..c4fa99d3 100644 --- a/src/Render/RenderTexture.cpp +++ b/src/Render/RenderTexture.cpp @@ -2,13 +2,16 @@ #include "VulkanImageBuffer.h" #include "IRenderContext.h" +#include "Core/Logger.h" + namespace sh::render { - RenderTexture::RenderTexture(const RenderTargetLayout& layout) : - Texture(layout.format, 1024, 1024, false), + RenderTexture::RenderTexture(TextureFormat colorFormat, TextureFormat depthFormat, bool bMSAA) : + Texture(colorFormat, 1024, 1024, false), width(1024), height(1024), - layout(layout) + depthFormat(depthFormat), + bMSAA(bMSAA) { SetAnisoLevel(0); } @@ -16,12 +19,11 @@ namespace sh::render Texture(std::move(other)), width(other.width), height(other.height), - layout(other.layout) - { - } - RenderTexture::~RenderTexture() + depthFormat(other.depthFormat), + bMSAA(other.bMSAA) { } + RenderTexture::~RenderTexture() = default; SH_RENDER_API void RenderTexture::Build(const IRenderContext& context) { @@ -59,43 +61,34 @@ namespace sh::render bChangeSize = true; SyncDirty(); } - SH_RENDER_API auto RenderTexture::GetSize() const -> glm::vec2 - { - return { width, height }; - } - SH_RENDER_API void RenderTexture::ChangeUsage(ResourceUsage newUsage) - { - usage = newUsage; - } - void RenderTexture::CreateBuffers() { assert(context->GetRenderAPIType() == RenderAPI::Vulkan); - const bool bDepthOnly = (layout.format == TextureFormat::None); - - if (textureBuffer == nullptr) - { - if (context->GetRenderAPIType() == RenderAPI::Vulkan) - textureBuffer = std::make_unique(); - } ITextureBuffer::CreateInfo ci{}; ci.width = width; ci.height = height; - ci.format = bDepthOnly ? layout.depthFormat : GetTextureFormat(); + ci.format = IsDepthTexture() ? depthFormat : GetTextureFormat(); ci.aniso = 0; ci.filtering = 1; ci.mipLevel = 1; ci.bMSAAImg = false; ci.bRenderTarget = true; - textureBuffer->Create(*context, ci); usage = ResourceUsage::Undefined; - if (bDepthOnly) + if (IsDepthTexture()) + { + CreateDepthBuffer(ci); return; + } - if (layout.bUseMSAA) + if (textureBuffer == nullptr) + if (context->GetRenderAPIType() == RenderAPI::Vulkan) + textureBuffer = std::make_unique(); + textureBuffer->Create(*context, ci); + + if (bMSAA) { ci.bMSAAImg = true; if (msaaBuffer == nullptr) @@ -105,26 +98,38 @@ namespace sh::render } msaaBuffer->Create(*context, ci); } - - if (layout.depthFormat == TextureFormat::D32S8 || - layout.depthFormat == TextureFormat::D24S8 || - layout.depthFormat == TextureFormat::D16S8) + if (depthFormat != TextureFormat::None) + { + ci.format = depthFormat; + CreateDepthBuffer(ci); + } + } + void RenderTexture::CreateDepthBuffer(const ITextureBuffer::CreateInfo& ci) + { + const bool bValidDepthFormat = + depthFormat == TextureFormat::D32 || + depthFormat == TextureFormat::D16 || + depthFormat == TextureFormat::D32S8 || + depthFormat == TextureFormat::D24S8 || + depthFormat == TextureFormat::D16S8; + if (!bValidDepthFormat) + { + SH_ERROR_FORMAT("Invalid depth foramt!: {}", TextureFormatToString(ci.format)); + return; + } + if (IsDepthTexture()) + { + if (textureBuffer == nullptr) + if (context->GetRenderAPIType() == RenderAPI::Vulkan) + textureBuffer = std::make_unique(); + textureBuffer->Create(*context, ci); + } + else { if (depthBuffer == nullptr) - { if (context->GetRenderAPIType() == RenderAPI::Vulkan) depthBuffer = std::make_unique(); - } - ci.format = layout.depthFormat; depthBuffer->Create(*context, ci); } } - SH_RENDER_API auto RenderTexture::GetDepthBuffer() const -> ITextureBuffer* - { - if (depthBuffer != nullptr) - return depthBuffer.get(); - if (layout.format == TextureFormat::None) - return textureBuffer.get(); // depth only인 경우 textureBuffer 자체가 DepthBuffer - return nullptr; - } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index aab82b77..2707a220 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -14,30 +14,7 @@ namespace sh::render renderQueue(renderQueue) { } - SH_RENDER_API void ScriptableRenderPass::Configure(const RenderData& renderData) - { - if (renderData.drawables != nullptr) - { - renderBatches = CreateRenderBatch(*renderData.drawables); - } - SetImageUsages(renderData); - } - SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) - { - cmd.SetRenderData(renderData, true, true, true, true); - if (renderData.drawables == nullptr) - return; - std::size_t viewerIdx = 0; - for (const RenderViewer& viewer : renderData.renderViewers) - { - SetViewportScissor(cmd, ctx, viewer); - for (const RenderBatch& batch : renderBatches) - cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); - ++viewerIdx; - } - //ctx.GetRenderImpl().RecordCommand(cmd, passName, renderTarget, drawList, bStoreImage); - } - SH_RENDER_API auto ScriptableRenderPass::CreateRenderBatch(const std::vector& drawables) const -> std::vector + SH_RENDER_API auto ScriptableRenderPass::CreateRenderBatch(const core::Name& passName, const std::vector& drawables) -> std::vector { std::vector groups; struct GroupKey @@ -105,17 +82,50 @@ namespace sh::render } return groups; } + SH_RENDER_API void ScriptableRenderPass::Configure(const RenderData& renderData) + { + if (renderData.GetDrawablesPtr() != nullptr) + { + renderBatches = CreateRenderBatch(passName, *renderData.GetDrawablesPtr()); + } + SetImageUsages(renderData); + } + SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + cmd.SetRenderData(renderData, true, true, true, true); + if (renderData.GetDrawablesPtr() == nullptr) + return; + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + for (const RenderBatch& batch : renderBatches) + cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); + ++viewerIdx; + } + //ctx.GetRenderImpl().RecordCommand(cmd, passName, renderTarget, drawList, bStoreImage); + } SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const RenderData& renderData) { renderTextures.clear(); - - if (renderData.target != nullptr && renderData.target->IsDepthOnly()) - renderTextures[renderData.target] = ResourceUsage::DepthStencilAttachment; - else - renderTextures[renderData.target] = ResourceUsage::ColorAttachment; - if (renderData.drawables != nullptr) - SetImageUsages(*renderData.drawables); + SetRenderTargetImageUsages(renderData); + + if (renderData.GetDrawablesPtr() != nullptr) + SetImageUsages(*renderData.GetDrawablesPtr()); + } + SH_RENDER_API void ScriptableRenderPass::SetRenderTargetImageUsages(const RenderData& renderData) + { + for (const RenderTexture* target : renderData.GetRenderTargets()) + { + if (target == nullptr) + { + renderTextures[nullptr] = ResourceUsage::ColorAttachment; + continue; + } + + renderTextures[target] = target->IsDepthTexture() ? ResourceUsage::DepthStencilAttachment : ResourceUsage::ColorAttachment; + } } SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const std::vector& drawables) { @@ -124,17 +134,21 @@ namespace sh::render if (drawable == nullptr || !drawable->CheckAssetValid()) continue; - const Material* const mat = drawable->GetMaterial(); - for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(mat->GetMaterialData())) - { - if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) - renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; - } for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(drawable->GetMaterialData())) { if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) - renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthTexture() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; } + const Material* const mat = drawable->GetMaterial(); + SetImageUsages(*mat); + } + } + SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const Material& mat) + { + for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(mat.GetMaterialData())) + { + if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthTexture() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; } } SH_RENDER_API void ScriptableRenderPass::SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderViewer& renderViewer) @@ -142,4 +156,4 @@ namespace sh::render cmd.SetViewport(renderViewer.viewportRect.x, renderViewer.viewportRect.y, renderViewer.viewportRect.z, renderViewer.viewportRect.w); cmd.SetScissor(renderViewer.viewportScissor.x, renderViewer.viewportScissor.y, renderViewer.viewportScissor.z, renderViewer.viewportScissor.w); } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 2d43e652..33e7d247 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -111,7 +111,7 @@ namespace sh::render for (int i = 0; i < activePasses.size(); ++i) { - barriers[i] = BuildBarrierInfo(*activePasses[i], data.frameIndex); + barriers[i] = BuildBarrierInfo(*activePasses[i], data.GetFrameIdx()); } std::vector>> futurePasses; @@ -146,8 +146,8 @@ namespace sh::render SH_RENDER_API void ScriptableRenderer::ExecuteTransfer(uint32_t imgIdx) { RenderData data{}; - data.frameIndex = imgIdx; - data.drawables = nullptr; + IRenderThrMethod::SetFrameIndex(data, imgIdx); + IRenderThrMethod::SetDrawablesPtr(data, nullptr); IRenderThrMethod::Configure(*cpyPass, data); diff --git a/src/Render/ShaderLexer.cpp b/src/Render/ShaderLexer.cpp index 330072f8..a165f0d2 100644 --- a/src/Render/ShaderLexer.cpp +++ b/src/Render/ShaderLexer.cpp @@ -256,6 +256,7 @@ namespace sh::render {"MATRIX_MODEL", TokenType::MATRIX_MODEL}, {"MATRIX_VIEW", TokenType::MATRIX_VIEW}, {"MATRIX_PROJ", TokenType::MATRIX_PROJ}, + {"CAMERA", TokenType::CAMERA}, {"BONE_WEIGHTS", TokenType::BONE_WEIGHTS}, {"BONE_INDICES", TokenType::BONE_INDICES}, {"SKIN", TokenType::SKIN}, @@ -291,4 +292,4 @@ namespace sh::render { return std::isalnum(static_cast(c)) || c == '_'; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index d21a0f86..1ec8679b 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace sh::render { @@ -622,7 +623,6 @@ namespace sh::render }; auto uboit = refreshUboIt(); - std::string code{}; int nested = 1; bool usingVertex = false; @@ -637,6 +637,42 @@ namespace sh::render bool usingSKIN = false; bool usingMATRIX_SKIN = false; bool usingTEXTURE_SHADOW = false; + auto registerCameraFn = [&]() + { + if (usingCamera) + return; + + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "CAMERA"; }); + if (it == stageNode.buffers.end()) + { + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::Uniform; + uboNode.name = "CAMERA"; + uboNode.set = static_cast(UniformStructLayout::Usage::Camera); + uboNode.binding = 0; + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec3, 1, "pos" }); + stageNode.buffers.push_back(std::move(uboNode)); + } + else + { + auto hasVar = [&](std::string_view name) + { + return std::find_if(it->vars.begin(), it->vars.end(), + [&](const ShaderAST::VariableNode& var) { return var.name == name; }) != it->vars.end(); + }; + if (!hasVar("view")) + it->vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); + if (!hasVar("proj")) + it->vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); + if (!hasVar("pos")) + it->vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec3, 1, "pos" }); + } + usingCamera = true; + uboit = refreshUboIt(); + }; while (nested != 0 || PeekToken().type != ShaderLexer::TokenType::EndOfFile) { @@ -665,26 +701,11 @@ namespace sh::render registerAttributeFn(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4); else if (CheckToken(ShaderLexer::TokenType::BONE_INDICES)) registerAttributeFn(usingBoneIndices, "BONE_INDICES", SkinnedMesh::BONE_INDEX_ID, ShaderAST::VariableType::IVec4); - else if (CheckToken(ShaderLexer::TokenType::MATRIX_VIEW) || CheckToken(ShaderLexer::TokenType::MATRIX_PROJ)) + else if (CheckToken(ShaderLexer::TokenType::MATRIX_VIEW) || + CheckToken(ShaderLexer::TokenType::MATRIX_PROJ) || + CheckToken(ShaderLexer::TokenType::CAMERA)) { - if (!usingCamera) - { - auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), - [](const ShaderAST::BufferNode& ubo) { return ubo.name == "CAMERA"; }); - if (it == stageNode.buffers.end()) - { - ShaderAST::BufferNode uboNode{}; - uboNode.bufferType = ShaderAST::BufferType::Uniform; - uboNode.name = "CAMERA"; - uboNode.set = static_cast(UniformStructLayout::Usage::Camera); - uboNode.binding = 0; - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); - stageNode.buffers.push_back(std::move(uboNode)); - uboit = refreshUboIt(); - } - usingCamera = true; - } + registerCameraFn(); } else if (CheckToken(ShaderLexer::TokenType::MATRIX_MODEL)) { @@ -1169,4 +1190,4 @@ namespace sh::render } return shaderNode; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/ShadowMapManager.cpp b/src/Render/ShadowMapManager.cpp index 099111f2..b47f7a56 100644 --- a/src/Render/ShadowMapManager.cpp +++ b/src/Render/ShadowMapManager.cpp @@ -27,7 +27,7 @@ namespace sh::render this->atlasSize = atlasSize; packer = std::make_unique(static_cast(atlasSize), static_cast(atlasSize), 0); - renderData.tag = core::Name{ "Shadow" }; + renderData.tag = core::Name{ "Depth" }; renderData.priority = 1000; // 다른 패스보다 먼저 실행되도록 } @@ -55,12 +55,7 @@ namespace sh::render SH_ERROR("ShadowMapManager: context is not initialized"); return; } - RenderTargetLayout layout{}; - layout.format = TextureFormat::None; - layout.depthFormat = TextureFormat::D32; - layout.bUseMSAA = false; - - atlas = core::SObject::Create(layout); + atlas = core::SObject::Create(TextureFormat::None, TextureFormat::D32, false); atlas->SetSize(atlasSize, atlasSize); atlas->Build(*ctx); atlas->SetName("ShadowAtlas"); @@ -96,7 +91,7 @@ namespace sh::render casterLightSpace.clear(); renderData.renderViewers.clear(); - renderData.target = atlas; + renderData.SetRenderTarget(atlas); renderData.renderViewers.reserve(casters.size()); for (const IShadowCaster* caster : casters) diff --git a/src/Render/Texture.cpp b/src/Render/Texture.cpp index 60d90f3e..0d005ef6 100644 --- a/src/Render/Texture.cpp +++ b/src/Render/Texture.cpp @@ -25,7 +25,7 @@ namespace sh::render int mipHeight = height; for (int i = 0; i < mipLevels; ++i) { - pixels[i].resize(mipWidth * mipHeight * GetTextureFormatChannel(format)); + pixels[i].resize(mipWidth * mipHeight * GetTextureFormatPixelSize(format)); mipWidth = std::max(1, mipWidth / 2); mipHeight = std::max(1, mipHeight / 2); } @@ -41,21 +41,44 @@ namespace sh::render if (other.bDirty.test_and_set(std::memory_order::memory_order_acquire)) bDirty.test_and_set(std::memory_order::memory_order_relaxed); } - Texture::~Texture() + Texture::~Texture() = default; + + SH_RENDER_API void Texture::SyncDirty() { + if (!bDirty.test_and_set(std::memory_order::memory_order_acquire)) + core::ThreadSyncManager::PushSyncable(*this); } + SH_RENDER_API void Texture::Sync() + { + if (bSetDataDirty) + { + CreateTextureBuffer(); + bSetDataDirty = false; + } - SH_RENDER_API void Texture::SetPixelData(const std::vector& pixels, uint32_t mipLevel) + bDirty.clear(std::memory_order::memory_order_relaxed); + } + SH_RENDER_API void Texture::OnPropertyChanged(const core::reflection::Property& prop) { - assert(this->pixels[mipLevel].size() == pixels.size()); - this->pixels[mipLevel] = pixels; - if (textureBuffer != nullptr) + if (prop.GetName() == core::Util::ConstexprHash("aniso")) { - bSetDataDirty = true; - SyncDirty(); + SetAnisoLevel(aniso); + } + else if (prop.GetName() == core::Util::ConstexprHash("bSRGB")) + { + SetSRGB(bSRGB); + } + else if (prop.GetName() == core::Util::ConstexprHash("bGenerateMipmap")) + { + SetGenerateMipmap(bGenerateMipmap); + } + else if (prop.GetName() == core::Util::ConstexprHash("filtering")) + { + SetFiltering(filtering); } } - SH_RENDER_API void Texture::SetPixelData(std::vector&& pixels, uint32_t mipLevel) + + SH_RENDER_API void Texture::SetPixelData(std::vector pixels, uint32_t mipLevel) { assert(this->pixels[mipLevel].size() == pixels.size()); this->pixels[mipLevel] = std::move(pixels); @@ -68,6 +91,7 @@ namespace sh::render SH_RENDER_API void Texture::SetPixelData(const uint8_t* pixels, std::size_t size, uint32_t mipLevel) { assert(this->pixels[mipLevel].size() == size); + this->pixels[mipLevel].resize(size); std::memcpy(this->pixels[mipLevel].data(), pixels, size); if (textureBuffer != nullptr) { @@ -75,11 +99,6 @@ namespace sh::render SyncDirty(); } } - - SH_RENDER_API auto Texture::GetPixelData() const -> const std::vector>& - { - return pixels; - } SH_RENDER_API auto Texture::GetPixelData(uint32_t mipLevel) const -> const std::vector& { assert(pixels.size() > mipLevel); @@ -99,18 +118,6 @@ namespace sh::render { return textureBuffer.get(); } - SH_RENDER_API auto Texture::GetTextureFormat() const -> TextureFormat - { - return format; - } - SH_RENDER_API auto Texture::GetWidth() const -> uint32_t - { - return width; - } - SH_RENDER_API auto Texture::GetHeight() const -> uint32_t - { - return height; - } SH_RENDER_API void Texture::ChangeTextureFormat(TextureFormat target) { @@ -126,10 +133,6 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API auto Texture::IsSRGB() const -> bool - { - return bSRGB; - } SH_RENDER_API auto Texture::GetMipLevel() const -> uint32_t { if (!bGenerateMipmap) @@ -145,35 +148,12 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API auto Texture::GetAnisoLevel() const -> uint32_t - { - return aniso; - } SH_RENDER_API void Texture::SetGenerateMipmap(bool bGenerate) { bGenerateMipmap = bGenerate; bSetDataDirty = true; SyncDirty(); } - SH_RENDER_API auto Texture::IsGenerateMipmap() const -> bool - { - return bGenerateMipmap; - } - SH_RENDER_API void Texture::SyncDirty() - { - if (!bDirty.test_and_set(std::memory_order::memory_order_acquire)) - core::ThreadSyncManager::PushSyncable(*this); - } - SH_RENDER_API void Texture::Sync() - { - if (bSetDataDirty) - { - CreateTextureBuffer(); - bSetDataDirty = false; - } - - bDirty.clear(std::memory_order::memory_order_relaxed); - } SH_RENDER_API void Texture::SetSRGB(bool bSRGB) { if (bSRGB) @@ -200,29 +180,6 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API auto Texture::GetFiltering() const -> Filtering - { - return filtering; - } - SH_RENDER_API void Texture::OnPropertyChanged(const core::reflection::Property& prop) - { - if (prop.GetName() == core::Util::ConstexprHash("aniso")) - { - SetAnisoLevel(aniso); - } - else if (prop.GetName() == core::Util::ConstexprHash("bSRGB")) - { - SetSRGB(bSRGB); - } - else if (prop.GetName() == core::Util::ConstexprHash("bGenerateMipmap")) - { - SetGenerateMipmap(bGenerateMipmap); - } - else if (prop.GetName() == core::Util::ConstexprHash("filtering")) - { - SetFiltering(filtering); - } - } SH_RENDER_API void Texture::ExportToPNG(const std::filesystem::path& path) { stbi_write_png(path.u8string().c_str(), width, height, GetTextureFormatChannel(format), pixels[0].data(), width * GetTextureFormatChannel(format)); @@ -260,4 +217,4 @@ namespace sh::render return true; return false; } -} \ No newline at end of file +}//namespace diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index 08f56c29..94a86057 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -24,9 +24,54 @@ #include "Core/Logger.h" #include - +#include namespace sh::render::vk { + namespace + { + auto HasStencil(TextureFormat format) -> bool + { + return format == TextureFormat::D32S8 || + format == TextureFormat::D24S8 || + format == TextureFormat::D16S8; + } + + auto BuildRenderTargetLayout(const VulkanContext& context, const std::vector& targets) -> RenderTargetLayout + { + RenderTargetLayout layout{}; + + std::vector colorFormats; + colorFormats.reserve(targets.size()); + + const RenderTexture* main = targets[0]; + if (main == nullptr) + { + layout.depthFormat = context.GetSwapChain().GetRenderTargetLayout().depthFormat; + layout.bUseMSAA = context.GetSampleCount() != VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; + } + else + { + layout.depthFormat = main->GetDepthFormat(); + layout.bUseMSAA = main->GetMSAABuffer() != nullptr; + } + + for (const RenderTexture* target : targets) + { + if (target == nullptr) + colorFormats.push_back(context.GetSwapChain().GetRenderTargetLayout().colorFormats[0]); + else + { + if (target->IsDepthTexture()) + layout.depthFormat = target->GetDepthFormat(); + else + colorFormats.push_back(target->GetTextureFormat()); + } + } + layout.colorFormats = std::move(colorFormats); + return layout; + } + } + VulkanCommandBuffer::VulkanCommandBuffer(const VulkanContext& context) : context(context) {} @@ -50,6 +95,7 @@ namespace sh::render::vk SH_RENDER_API void VulkanCommandBuffer::Begin(bool bOnetime) { + renderCall = 0; if (buffer == VK_NULL_HANDLE) return; @@ -134,45 +180,65 @@ namespace sh::render::vk bBeginRender = false; } - const RenderTexture* const target = renderData.target; - - const bool bDepthOnly = target == nullptr ? false : target->IsDepthOnly(); - const VulkanImageBuffer* colorImg = nullptr; - const VulkanImageBuffer* colorMSAAImg = nullptr; + const VulkanImageBuffer* colorImgs[10]{}; // 힙 할당 최소화 + const VulkanImageBuffer* colorMSAAImgs[10]{}; // 힙 할당 최소화 const VulkanImageBuffer* depthImg = nullptr; - RenderTargetLayout rtLayout{}; - if (target != nullptr) - { - colorImg = bDepthOnly ? nullptr : static_cast(target->GetTextureBuffer()); - colorMSAAImg = static_cast(target->GetMSAABuffer()); - depthImg = static_cast(target->GetDepthBuffer()); - rtLayout = target->GetLayout(); - } + std::size_t colorImgsCount = 0; + std::size_t colorMSAAImgsCount = 0; + // 메모) 깊이 버퍼는 제일 앞의 렌더 텍스쳐 것을 쓰고 깊이 버퍼 전용 텍스쳐가 있으면 그걸 우선시 함 + const RenderTexture* const main = renderData.GetRenderTargets()[0]; + if (main == nullptr) + depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderData.GetFrameIdx()]; else + depthImg = static_cast(main->GetDepthBuffer()); + + for (const RenderTexture* target : renderData.GetRenderTargets()) { - colorImg = &context.GetSwapChain().GetSwapChainImages()[renderData.frameIndex]; - colorMSAAImg = &context.GetSwapChain().GetSwapChainMSAAImages()[renderData.frameIndex]; - depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderData.frameIndex]; - rtLayout = context.GetSwapChain().GetRenderTargetLayout(); + if (target == nullptr) + { + colorImgs[colorImgsCount++] = &context.GetSwapChain().GetSwapChainImages()[renderData.GetFrameIdx()]; + const std::vector& swapChainMSAAImages = context.GetSwapChain().GetSwapChainMSAAImages(); + colorMSAAImgs[colorMSAAImgsCount++] = swapChainMSAAImages.empty() ? nullptr : &swapChainMSAAImages[renderData.GetFrameIdx()]; + } + else + { + if (!target->IsDepthTexture()) + { + colorImgs[colorImgsCount++] = static_cast(target->GetTextureBuffer()); + colorMSAAImgs[colorMSAAImgsCount++] = static_cast(target->GetMSAABuffer()); + } + else + depthImg = static_cast(target->GetDepthBuffer()); + } } - const VulkanImageBuffer* const sizeRef = colorImg != nullptr ? colorImg : depthImg; + RenderTargetLayout rtLayout = BuildRenderTargetLayout(context, renderData.GetRenderTargets()); + + const VulkanImageBuffer* const sizeRef = colorImgsCount != 0 ? colorImgs[0] : depthImg; + if (sizeRef == nullptr) + return; const uint32_t width = sizeRef->GetWidth(); const uint32_t height = sizeRef->GetHeight(); const bool bHasDepth = (depthImg != nullptr); + const bool bDepthOnly = colorImgsCount == 0; - VkRenderingAttachmentInfoKHR colorAttachment{}; - if (!bDepthOnly) + VkRenderingAttachmentInfoKHR colorAttachments[10]{}; // 힙 할당 최소화 + const std::size_t colorAttachmentsCount = colorImgsCount; + for (std::size_t i = 0; i < colorAttachmentsCount; ++i) { + assert(colorImgs[i] != nullptr); + assert(colorImgs[i]->GetWidth() == width && colorImgs[i]->GetHeight() == height); + + VkRenderingAttachmentInfoKHR& colorAttachment = colorAttachments[i]; colorAttachment.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - const bool bMSAA = (colorMSAAImg != nullptr); - colorAttachment.imageView = bMSAA ? colorMSAAImg->GetImageView() : colorImg->GetImageView(); + const bool bMSAA = (i < colorMSAAImgsCount && colorMSAAImgs[i] != nullptr); + colorAttachment.imageView = bMSAA ? colorMSAAImgs[i]->GetImageView() : colorImgs[i]->GetImageView(); colorAttachment.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorAttachment.loadOp = bClearColor ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = bStoreColor ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.clearValue.color = { { 0.f, 0.f, 0.f, 1.f } }; if (bMSAA) { - colorAttachment.resolveImageView = colorImg->GetImageView(); + colorAttachment.resolveImageView = colorImgs[i]->GetImageView(); colorAttachment.resolveImageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorAttachment.resolveMode = VkResolveModeFlagBitsKHR::VK_RESOLVE_MODE_AVERAGE_BIT_KHR; } @@ -190,11 +256,11 @@ namespace sh::render::vk VkRenderingInfoKHR renderingInfo{}; renderingInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; - renderingInfo.colorAttachmentCount = bDepthOnly ? 0 : 1; - renderingInfo.pColorAttachments = bDepthOnly ? nullptr : &colorAttachment; + renderingInfo.colorAttachmentCount = static_cast(colorAttachmentsCount); + renderingInfo.pColorAttachments = colorAttachmentsCount == 0 ? nullptr : colorAttachments; renderingInfo.pDepthAttachment = bHasDepth ? &depthAttachment : nullptr; // depth only는 스텐실 지원x - renderingInfo.pStencilAttachment = (bHasDepth && !bDepthOnly) ? &depthAttachment : nullptr; + renderingInfo.pStencilAttachment = (bHasDepth && HasStencil(rtLayout.depthFormat) && !bDepthOnly) ? &depthAttachment : nullptr; renderingInfo.renderArea = { { 0, 0 }, { width, height } }; renderingInfo.layerCount = 1; @@ -365,16 +431,17 @@ namespace sh::render::vk VulkanImageBuffer* imgBuffer = nullptr; VulkanImageBuffer* msaaBuffer = nullptr; - if (barrier.target.index() == 0) + if (std::holds_alternative(barrier.target)) { - imgBuffer = (static_cast(std::get<0>(barrier.target)->GetTextureBuffer())); - msaaBuffer = (static_cast(std::get<0>(barrier.target)->GetMSAABuffer())); + imgBuffer = static_cast(std::get(barrier.target)->GetTextureBuffer()); + msaaBuffer = static_cast(std::get(barrier.target)->GetMSAABuffer()); } else { - const uint32_t imgIdx = std::get<1>(barrier.target); + const uint32_t imgIdx = std::get(barrier.target); imgBuffer = &context.GetSwapChain().GetSwapChainImages()[imgIdx]; - msaaBuffer = &context.GetSwapChain().GetSwapChainMSAAImages()[imgIdx]; + const auto& swapChainMSAAImages = context.GetSwapChain().GetSwapChainMSAAImages(); + msaaBuffer = swapChainMSAAImages.empty() ? nullptr : &context.GetSwapChain().GetSwapChainMSAAImages()[imgIdx]; } VkImageLayout srcLayout, dstLayout; @@ -433,7 +500,8 @@ namespace sh::render::vk mapUsageFn(barrier.lastUsage, srcLayout, srcStage, srcAccess); mapUsageFn(barrier.curUsage, dstLayout, dstStage, dstAccess); - VulkanImageBuffer::BarrierCommand(buffer, *imgBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); + if (imgBuffer != nullptr) + VulkanImageBuffer::BarrierCommand(buffer, *imgBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); if (msaaBuffer != nullptr) { @@ -608,5 +676,6 @@ namespace sh::render::vk } vkCmdDrawIndexed(buffer, indexCount, 1, firstIndex, 0, 0); + ++renderCall; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index 1387b34b..c62992fc 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -57,6 +57,8 @@ namespace sh::render::vk } VulkanContext::~VulkanContext() { + if (device != nullptr) + Clear(); } SH_RENDER_API void VulkanContext::Init() @@ -119,7 +121,9 @@ namespace sh::render::vk vkDeviceWaitIdle(device); + IRenderThrMethod::ClearBuffer(renderDataManager); pipelineManager.reset(); + computePipelineManager.reset(); if (emptyDescLayout) { vkDestroyDescriptorSetLayout(device, emptyDescLayout, nullptr); @@ -136,6 +140,8 @@ namespace sh::render::vk gpu = VK_NULL_HANDLE; gpus.clear(); DestroyInstance(); + + device = nullptr; } SH_RENDER_API auto VulkanContext::AllocateCommandBuffer(bool bCompute) -> CommandBuffer* { diff --git a/src/Render/VulkanImpl/VulkanImageBuffer.cpp b/src/Render/VulkanImpl/VulkanImageBuffer.cpp index d6ab000b..174a7dc8 100644 --- a/src/Render/VulkanImpl/VulkanImageBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanImageBuffer.cpp @@ -25,17 +25,22 @@ namespace sh::render::vk sample(other.sample), width(other.width), height(other.height), - channel(other.channel), + pixelSize(other.pixelSize), aniso(other.aniso), mipLevel(other.mipLevel), bSwapChainImg(other.bSwapChainImg), bRenderTarget(other.bRenderTarget) { - other.Clear(); + other.img = nullptr; + other.imgMem = nullptr; + other.imgView = nullptr; + other.sampler = nullptr; } SH_RENDER_API auto VulkanImageBuffer::operator=(VulkanImageBuffer&& other) noexcept -> VulkanImageBuffer& { + if (this == &other) + return *this; Clear(); ctx = other.ctx; @@ -51,7 +56,7 @@ namespace sh::render::vk sample = other.sample; width = other.width; height = other.height; - channel = other.channel; + pixelSize = other.pixelSize; aniso = other.aniso; mipLevel = other.mipLevel; @@ -94,7 +99,7 @@ namespace sh::render::vk width = info.width == 0 ? 1 : info.width; height = info.height == 0 ? 1 : info.height; - channel = GetChannelCount(format); + pixelSize = GetFormatPixelSize(format); aniso = info.aniso; mipLevel = actualMip; @@ -208,7 +213,7 @@ namespace sh::render::vk filter = VkFilter::VK_FILTER_LINEAR; format = VkFormat::VK_FORMAT_UNDEFINED; width = height = 0; - channel = 4; + pixelSize = 4; aniso = 0; mipLevel = 1; @@ -250,7 +255,7 @@ namespace sh::render::vk mipWidth = std::max(1u, mipWidth / 2); mipHeight = std::max(1u, mipHeight / 2); } - std::size_t bufferSize = mipWidth * mipHeight * channel; + std::size_t bufferSize = mipWidth * mipHeight * pixelSize; stagingBuffer.Create(bufferSize, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT, @@ -326,7 +331,7 @@ namespace sh::render::vk sample = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; width = swapChain.GetSwapChainSize().width; height = swapChain.GetSwapChainSize().height; - channel = GetChannelCount(format); + pixelSize = GetFormatPixelSize(format); aniso = 0; mipLevel = 1; @@ -415,6 +420,8 @@ namespace sh::render::vk case TextureFormat::RGB24: [[fallthrough]]; // 대부분 지원 안 함 case TextureFormat::RGBA32: return VkFormat::VK_FORMAT_R8G8B8A8_UNORM; + case TextureFormat::RG32F: + return VkFormat::VK_FORMAT_R16G16_SFLOAT; case TextureFormat::BGR24: [[fallthrough]]; // 대부분 지원 안 함 case TextureFormat::BGRA32: return VkFormat::VK_FORMAT_B8G8R8A8_UNORM; @@ -434,7 +441,7 @@ namespace sh::render::vk return VkFormat::VK_FORMAT_UNDEFINED; } } - auto VulkanImageBuffer::GetChannelCount(VkFormat format) -> uint32_t + auto VulkanImageBuffer::GetFormatPixelSize(VkFormat format) -> uint32_t { switch (format) { @@ -443,6 +450,8 @@ namespace sh::render::vk case VkFormat::VK_FORMAT_B8G8R8A8_UNORM: [[fallthrough]]; case VkFormat::VK_FORMAT_B8G8R8A8_SRGB: [[fallthrough]]; return 4; + case VkFormat::VK_FORMAT_R16G16_SFLOAT: + return sizeof(uint16_t) * 2; case VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT: [[fallthrough]]; case VkFormat::VK_FORMAT_D24_UNORM_S8_UINT: [[fallthrough]]; case VkFormat::VK_FORMAT_D16_UNORM_S8_UINT: [[fallthrough]]; @@ -454,4 +463,4 @@ namespace sh::render::vk return 0; } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/VulkanImpl/VulkanPipeline.cpp b/src/Render/VulkanImpl/VulkanPipeline.cpp index 4175889d..6cf8db32 100644 --- a/src/Render/VulkanImpl/VulkanPipeline.cpp +++ b/src/Render/VulkanImpl/VulkanPipeline.cpp @@ -6,6 +6,16 @@ #include namespace sh::render::vk { + namespace + { + auto HasStencil(TextureFormat format) -> bool + { + return format == TextureFormat::D32S8 || + format == TextureFormat::D24S8 || + format == TextureFormat::D16S8; + } + } + SH_RENDER_API VulkanPipeline::VulkanPipeline(VkDevice device, const RenderTargetLayout& rt) : device(device), pipeline(nullptr), shader(nullptr), @@ -223,14 +233,15 @@ namespace sh::render::vk colorBlendAttachment.alphaBlendOp = VkBlendOp::VK_BLEND_OP_ADD; colorBlendAttachment.dstAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;; - const bool bDepthOnly = (rtLayout.format == TextureFormat::None); + const uint32_t colorAttachmentCount = static_cast(rtLayout.colorFormats.size()); VkPipelineColorBlendStateCreateInfo colorBlending{}; + std::vector colorBlendAttachments(colorAttachmentCount, colorBlendAttachment); colorBlending.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VkLogicOp::VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = bDepthOnly ? 0 : 1; - colorBlending.pAttachments = bDepthOnly ? nullptr : &colorBlendAttachment; + colorBlending.attachmentCount = colorAttachmentCount; + colorBlending.pAttachments = colorBlendAttachments.empty() ? nullptr : colorBlendAttachments.data(); colorBlending.blendConstants[0] = 0.0f; colorBlending.blendConstants[1] = 0.0f; colorBlending.blendConstants[2] = 0.0f; @@ -259,16 +270,20 @@ namespace sh::render::vk stageCreateInfo.pSpecializationInfo = &specializationInfo; } - VkFormat colorFormat = VulkanImageBuffer::ConvertTextureFormat(rtLayout.format); + std::vector colorFormats; + colorFormats.reserve(colorAttachmentCount); + for (TextureFormat format : rtLayout.colorFormats) + colorFormats.push_back(VulkanImageBuffer::ConvertTextureFormat(format)); + VkFormat depthFormat = VulkanImageBuffer::ConvertTextureFormat(rtLayout.depthFormat); VkPipelineRenderingCreateInfoKHR pipelineRenderingCI{}; pipelineRenderingCI.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; - pipelineRenderingCI.colorAttachmentCount = bDepthOnly ? 0 : 1; - pipelineRenderingCI.pColorAttachmentFormats = bDepthOnly ? nullptr : &colorFormat; + pipelineRenderingCI.colorAttachmentCount = colorAttachmentCount; + pipelineRenderingCI.pColorAttachmentFormats = colorFormats.empty() ? nullptr : colorFormats.data(); pipelineRenderingCI.depthAttachmentFormat = depthFormat; // depth-only RT는 view aspect=DEPTH-only로 만들었으므로 stencil 포맷도 비활성화. - pipelineRenderingCI.stencilAttachmentFormat = bDepthOnly ? VkFormat::VK_FORMAT_UNDEFINED : depthFormat; + pipelineRenderingCI.stencilAttachmentFormat = (!rtLayout.IsDepthOnly() && HasStencil(rtLayout.depthFormat)) ? depthFormat : VkFormat::VK_FORMAT_UNDEFINED; VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -365,4 +380,4 @@ namespace sh::render::vk { specializationData = dataPtr; } -} \ No newline at end of file +} diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 7c93bc33..2062443f 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -116,8 +116,8 @@ namespace sh::render::vk std::size_t viewIdx = 0; for (RenderData& rd : renderDatas) { - rd.frameIndex = imgIdx; - rd.drawables = &drawables; + IRenderThrMethod::SetFrameIndex(rd, imgIdx); + IRenderThrMethod::SetDrawablesPtr(rd, &drawables); IRenderThrMethod::Setup(*renderer, rd); IRenderThrMethod::Execute(*renderer, rd); // ScriptableRenderer에 등록된 패스들을 렌더큐 순서대로, 병렬적으로 커맨드에 기록 @@ -125,10 +125,12 @@ namespace sh::render::vk IRenderThrMethod::ExecuteTransfer(*renderer, imgIdx); + uint32_t renderCall = 0; const std::vector& recordedCmds = renderer->GetRecordedCommands(); if (recordedCmds.size() == 1) { VulkanCommandBuffer& cmd = static_cast(recordedCmds.front().cmd); + renderCall += IRenderThrMethod::GetRenderCall(cmd); cmd.AddWaitSemaphore( WaitSemaphore { @@ -149,6 +151,7 @@ namespace sh::render::vk { ScriptableRenderPass& pass = recordedCmd.pass; VulkanCommandBuffer& cmd = static_cast(recordedCmd.cmd); + renderCall += IRenderThrMethod::GetRenderCall(cmd); // 스왑체인을 쓰는 첫 패스는 imageAvailableSemaphore대기 if (!bUsedImageAvailableSemaphore) @@ -173,6 +176,7 @@ namespace sh::render::vk context->GetQueueManager().Submit(VulkanQueueManager::Role::Graphics, cmd, fence); } } + SetDrawCallCount(renderCall); VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; diff --git a/src/Render/VulkanImpl/VulkanShaderPass.cpp b/src/Render/VulkanImpl/VulkanShaderPass.cpp index 604ccef5..bc95a194 100644 --- a/src/Render/VulkanImpl/VulkanShaderPass.cpp +++ b/src/Render/VulkanImpl/VulkanShaderPass.cpp @@ -165,6 +165,20 @@ namespace sh::render::vk void VulkanShaderPass::AddDescriptorBinding(uint32_t set, uint32_t binding, VkDescriptorType type, VkShaderStageFlagBits stage) { + if (setlayouts.size() <= set) + { + setlayouts.resize(set + 1); + } + setlayouts[set].set = set; + + auto it = setlayouts[set].descriptorSetLayoutBindings.find(binding); + if (it != setlayouts[set].descriptorSetLayoutBindings.end()) + { + assert(it->second.descriptorType == type); + it->second.stageFlags |= stage; + return; + } + VkDescriptorSetLayoutBinding layoutBinding{}; layoutBinding.binding = binding; layoutBinding.descriptorCount = 1; @@ -172,11 +186,6 @@ namespace sh::render::vk layoutBinding.pImmutableSamplers = nullptr; layoutBinding.stageFlags = stage; - if (setlayouts.size() <= set) - { - setlayouts.resize(set + 1); - } - setlayouts[set].set = set; setlayouts[set].descriptorSetLayoutBindings.insert_or_assign(binding, layoutBinding); } auto VulkanShaderPass::CreateDescriptorLayout() -> VkResult @@ -245,4 +254,4 @@ namespace sh::render::vk } setlayouts.clear(); } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/VulkanImpl/VulkanSwapChain.cpp b/src/Render/VulkanImpl/VulkanSwapChain.cpp index 4a6280cf..d78eec3a 100644 --- a/src/Render/VulkanImpl/VulkanSwapChain.cpp +++ b/src/Render/VulkanImpl/VulkanSwapChain.cpp @@ -163,7 +163,7 @@ namespace sh::render::vk if (swapChainImageFormat == VkFormat::VK_FORMAT_B8G8R8A8_SRGB) colorFormat = TextureFormat::SBGRA32; - rtLayout.format = colorFormat; + rtLayout.colorFormats[0] = colorFormat; rtLayout.depthFormat = TextureFormat::None; rtLayout.bUseMSAA = bMSAA; for (VkImage img : images)