Compare commits
22 Commits
d2140bf5cc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bd375cd24 | |||
| c0d62dc749 | |||
| c98b41f6f4 | |||
| 72d09a1c49 | |||
| ba91e0f5dd | |||
| 9064c2f5f2 | |||
| 5797a75619 | |||
| e83f911aef | |||
| dd2299c8b0 | |||
| b8d29e39f6 | |||
| 72914b3865 | |||
| e6950fa704 | |||
| 21b0530f7b | |||
| c3d443eb85 | |||
| d705cc839b | |||
| 0d6b8bf7d8 | |||
| 4362008b39 | |||
| 712f99e723 | |||
| 48daaa1bd0 | |||
| e462f7d6f7 | |||
| 5b89c2bb76 | |||
| f3fc34898a |
@@ -42,6 +42,8 @@ add_executable(xc_3dgs_d3d12_mvs
|
||||
shaders/DeviceRadixSort.hlsl
|
||||
shaders/DebugPointsVS.hlsl
|
||||
shaders/DebugPointsPS.hlsl
|
||||
shaders/CompositeVS.hlsl
|
||||
shaders/CompositePS.hlsl
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
@@ -52,6 +54,8 @@ set_source_files_properties(
|
||||
shaders/DeviceRadixSort.hlsl
|
||||
shaders/DebugPointsVS.hlsl
|
||||
shaders/DebugPointsPS.hlsl
|
||||
shaders/CompositeVS.hlsl
|
||||
shaders/CompositePS.hlsl
|
||||
PROPERTIES
|
||||
HEADER_FILE_ONLY TRUE
|
||||
)
|
||||
|
||||
@@ -67,10 +67,12 @@ private:
|
||||
bool InitializePreparePassResources();
|
||||
bool InitializeSortResources();
|
||||
bool InitializeDebugDrawResources();
|
||||
bool InitializeCompositeResources();
|
||||
void ShutdownGaussianGpuResources();
|
||||
void ShutdownPreparePassResources();
|
||||
void ShutdownSortResources();
|
||||
void ShutdownDebugDrawResources();
|
||||
void ShutdownCompositeResources();
|
||||
void Shutdown();
|
||||
bool CaptureSortSnapshot();
|
||||
bool CapturePass3HistogramDebug();
|
||||
@@ -137,6 +139,13 @@ private:
|
||||
XCEngine::RHI::RHIPipelineState* m_debugPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_debugDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_debugDescriptorSet = nullptr;
|
||||
XCEngine::RHI::D3D12Texture m_splatRenderTargetTexture;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_splatRenderTargetRtv;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_splatRenderTargetSrv;
|
||||
XCEngine::RHI::RHIPipelineLayout* m_compositePipelineLayout = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_compositePipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_compositeDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_compositeDescriptorSet = nullptr;
|
||||
|
||||
XCEngine::RHI::D3D12Device m_device;
|
||||
XCEngine::RHI::D3D12CommandQueue m_commandQueue;
|
||||
|
||||
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
@@ -0,0 +1,12 @@
|
||||
Texture2D<float4> gSplatTexture : register(t0);
|
||||
|
||||
struct PixelInput
|
||||
{
|
||||
float4 position : SV_Position;
|
||||
};
|
||||
|
||||
float4 MainPS(PixelInput input) : SV_Target0
|
||||
{
|
||||
float4 color = gSplatTexture.Load(int3(int2(input.position.xy), 0));
|
||||
return float4(color.rgb, color.a);
|
||||
}
|
||||
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
@@ -0,0 +1,12 @@
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 position : SV_Position;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(uint vertexId : SV_VertexID)
|
||||
{
|
||||
VertexOutput output = (VertexOutput)0;
|
||||
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 4.0 - 1.0;
|
||||
output.position = float4(quadPosition, 1.0, 1.0);
|
||||
return output;
|
||||
}
|
||||
@@ -37,10 +37,8 @@ VertexOutput MainVS(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID
|
||||
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 2.0 - 1.0;
|
||||
quadPosition *= 2.0;
|
||||
|
||||
float2 scaledAxis1 = view.axis1 * gSettings.w;
|
||||
float2 scaledAxis2 = view.axis2 * gSettings.w;
|
||||
float2 deltaScreenPosition =
|
||||
(quadPosition.x * scaledAxis1 + quadPosition.y * scaledAxis2) * 2.0 / gScreenParams.xy;
|
||||
(quadPosition.x * view.axis1 + quadPosition.y * view.axis2) * 2.0 / gScreenParams.xy;
|
||||
|
||||
output.position = view.clipPosition;
|
||||
output.position.xy += deltaScreenPosition * view.clipPosition.w;
|
||||
|
||||
@@ -143,7 +143,7 @@ float3 CalcCovariance2D(float3 worldPosition, float3 covariance0, float3 covaria
|
||||
focal / viewPosition.z, 0, -(focal * viewPosition.x) / (viewPosition.z * viewPosition.z),
|
||||
0, focal / viewPosition.z, -(focal * viewPosition.y) / (viewPosition.z * viewPosition.z),
|
||||
0, 0, 0);
|
||||
float3x3 worldToView = (float3x3)gView;
|
||||
float3x3 worldToView = transpose((float3x3)gView);
|
||||
float3x3 transform = mul(jacobian, worldToView);
|
||||
float3x3 covariance = float3x3(
|
||||
covariance0.x, covariance0.y, covariance0.z,
|
||||
@@ -253,6 +253,9 @@ void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||
float3 covariance0;
|
||||
float3 covariance1;
|
||||
CalcCovariance3D(rotationScale, covariance0, covariance1);
|
||||
float splatScaleSquared = gSettings.w * gSettings.w;
|
||||
covariance0 *= splatScaleSquared;
|
||||
covariance1 *= splatScaleSquared;
|
||||
float3 covariance2D = CalcCovariance2D(position, covariance0, covariance1);
|
||||
DecomposeCovariance(covariance2D, view.axis1, view.axis2);
|
||||
|
||||
|
||||
@@ -1140,6 +1140,118 @@ bool App::InitializeDebugDrawResources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::InitializeCompositeResources() {
|
||||
D3D12_RESOURCE_DESC splatRenderTargetDesc = {};
|
||||
splatRenderTargetDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
splatRenderTargetDesc.Alignment = 0;
|
||||
splatRenderTargetDesc.Width = static_cast<UINT64>(m_width);
|
||||
splatRenderTargetDesc.Height = static_cast<UINT>(m_height);
|
||||
splatRenderTargetDesc.DepthOrArraySize = 1;
|
||||
splatRenderTargetDesc.MipLevels = 1;
|
||||
splatRenderTargetDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
splatRenderTargetDesc.SampleDesc.Count = 1;
|
||||
splatRenderTargetDesc.SampleDesc.Quality = 0;
|
||||
splatRenderTargetDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
||||
splatRenderTargetDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
||||
|
||||
if (!m_splatRenderTargetTexture.Initialize(
|
||||
m_device.GetDevice(),
|
||||
splatRenderTargetDesc,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET)) {
|
||||
m_lastErrorMessage = L"Failed to create the splat accumulation render target texture.";
|
||||
return false;
|
||||
}
|
||||
m_splatRenderTargetTexture.SetState(ResourceStates::RenderTarget);
|
||||
|
||||
ResourceViewDesc renderTargetViewDesc = {};
|
||||
renderTargetViewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||
renderTargetViewDesc.format = static_cast<uint32_t>(Format::R16G16B16A16_Float);
|
||||
m_splatRenderTargetRtv.reset(static_cast<D3D12ResourceView*>(
|
||||
m_device.CreateRenderTargetView(&m_splatRenderTargetTexture, renderTargetViewDesc)));
|
||||
if (!m_splatRenderTargetRtv) {
|
||||
m_lastErrorMessage = L"Failed to create the splat accumulation render target view.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_splatRenderTargetSrv.reset(static_cast<D3D12ResourceView*>(
|
||||
m_device.CreateShaderResourceView(&m_splatRenderTargetTexture, renderTargetViewDesc)));
|
||||
if (!m_splatRenderTargetSrv) {
|
||||
m_lastErrorMessage = L"Failed to create the splat accumulation shader resource view.";
|
||||
return false;
|
||||
}
|
||||
|
||||
DescriptorSetLayoutBinding bindings[1] = {};
|
||||
bindings[0].binding = 0;
|
||||
bindings[0].type = static_cast<uint32_t>(DescriptorType::SRV);
|
||||
bindings[0].count = 1;
|
||||
bindings[0].visibility = static_cast<uint32_t>(ShaderVisibility::Pixel);
|
||||
bindings[0].resourceDimension = ResourceViewDimension::Texture2D;
|
||||
|
||||
DescriptorSetLayoutDesc setLayout = {};
|
||||
setLayout.bindings = bindings;
|
||||
setLayout.bindingCount = 1;
|
||||
|
||||
RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = &setLayout;
|
||||
pipelineLayoutDesc.setLayoutCount = 1;
|
||||
|
||||
m_compositePipelineLayout = m_device.CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_compositePipelineLayout == nullptr) {
|
||||
m_lastErrorMessage = L"Failed to create the composite pipeline layout.";
|
||||
return false;
|
||||
}
|
||||
|
||||
DescriptorPoolDesc poolDesc = {};
|
||||
poolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||
poolDesc.descriptorCount = 1;
|
||||
poolDesc.shaderVisible = true;
|
||||
m_compositeDescriptorPool = m_device.CreateDescriptorPool(poolDesc);
|
||||
if (m_compositeDescriptorPool == nullptr) {
|
||||
m_lastErrorMessage = L"Failed to create the composite descriptor pool.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_compositeDescriptorSet = m_compositeDescriptorPool->AllocateSet(setLayout);
|
||||
if (m_compositeDescriptorSet == nullptr) {
|
||||
m_lastErrorMessage = L"Failed to allocate the composite descriptor set.";
|
||||
return false;
|
||||
}
|
||||
m_compositeDescriptorSet->Update(0, m_splatRenderTargetSrv.get());
|
||||
|
||||
GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = m_compositePipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(Format::Unknown);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
pipelineDesc.sampleQuality = 0;
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(CullMode::None);
|
||||
pipelineDesc.depthStencilState.depthTestEnable = false;
|
||||
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
||||
pipelineDesc.blendState.blendEnable = true;
|
||||
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(BlendFactor::SrcAlpha);
|
||||
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(BlendFactor::InvSrcAlpha);
|
||||
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(BlendFactor::One);
|
||||
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(BlendFactor::InvSrcAlpha);
|
||||
|
||||
pipelineDesc.vertexShader.fileName = ResolveShaderPath(L"CompositeVS.hlsl").wstring();
|
||||
pipelineDesc.vertexShader.entryPoint = L"MainVS";
|
||||
pipelineDesc.vertexShader.profile = L"vs_5_0";
|
||||
|
||||
pipelineDesc.fragmentShader.fileName = ResolveShaderPath(L"CompositePS.hlsl").wstring();
|
||||
pipelineDesc.fragmentShader.entryPoint = L"MainPS";
|
||||
pipelineDesc.fragmentShader.profile = L"ps_5_0";
|
||||
|
||||
m_compositePipelineState = m_device.CreatePipelineState(pipelineDesc);
|
||||
if (m_compositePipelineState == nullptr) {
|
||||
m_lastErrorMessage = L"Failed to create the composite pipeline state.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void App::ShutdownGaussianGpuResources() {
|
||||
m_gaussianColorView.reset();
|
||||
m_gaussianShView.reset();
|
||||
@@ -1256,6 +1368,20 @@ void App::ShutdownDebugDrawResources() {
|
||||
ShutdownAndDelete(m_debugPipelineLayout);
|
||||
}
|
||||
|
||||
void App::ShutdownCompositeResources() {
|
||||
if (m_compositeDescriptorSet != nullptr) {
|
||||
m_compositeDescriptorSet->Shutdown();
|
||||
delete m_compositeDescriptorSet;
|
||||
m_compositeDescriptorSet = nullptr;
|
||||
}
|
||||
ShutdownAndDelete(m_compositeDescriptorPool);
|
||||
ShutdownAndDelete(m_compositePipelineState);
|
||||
ShutdownAndDelete(m_compositePipelineLayout);
|
||||
m_splatRenderTargetSrv.reset();
|
||||
m_splatRenderTargetRtv.reset();
|
||||
m_splatRenderTargetTexture.Shutdown();
|
||||
}
|
||||
|
||||
void App::Shutdown() {
|
||||
AppendTrace("Shutdown: begin");
|
||||
if (!m_isInitialized && m_hwnd == nullptr) {
|
||||
@@ -1670,8 +1796,8 @@ void App::RenderFrame(bool captureScreenshot) {
|
||||
|
||||
const int currentBackBufferIndex = m_swapChain.GetCurrentBackBufferIndex();
|
||||
D3D12Texture& backBuffer = m_swapChain.GetBackBuffer(currentBackBufferIndex);
|
||||
|
||||
m_commandList.TransitionBarrier(backBuffer.GetResource(), ResourceStates::Present, ResourceStates::RenderTarget);
|
||||
backBuffer.SetState(ResourceStates::RenderTarget);
|
||||
|
||||
const CPUDescriptorHandle rtvCpuHandle = m_rtvHeap.GetCPUDescriptorHandle(currentBackBufferIndex);
|
||||
const CPUDescriptorHandle dsvCpuHandle = m_dsvHeap.GetCPUDescriptorHandle(0);
|
||||
@@ -2034,12 +2160,14 @@ void App::RenderFrame(bool captureScreenshot) {
|
||||
m_commandAllocator.Reset();
|
||||
m_commandList.Reset();
|
||||
m_commandList.TransitionBarrier(backBuffer.GetResource(), ResourceStates::RenderTarget, ResourceStates::Present);
|
||||
backBuffer.SetState(ResourceStates::Present);
|
||||
m_commandList.Close();
|
||||
void* presentCommandLists[] = { &m_commandList };
|
||||
AppendTrace("RenderFrame: execute final present-transition list");
|
||||
m_commandQueue.ExecuteCommandLists(1, presentCommandLists);
|
||||
} else {
|
||||
m_commandList.TransitionBarrier(backBuffer.GetResource(), ResourceStates::RenderTarget, ResourceStates::Present);
|
||||
backBuffer.SetState(ResourceStates::Present);
|
||||
m_commandList.Close();
|
||||
void* commandLists[] = { &m_commandList };
|
||||
AppendTrace("RenderFrame: execute+present");
|
||||
|
||||
180
docs/plan/NewEditor_宿主重构计划_2026-04-13.md
Normal file
180
docs/plan/NewEditor_宿主重构计划_2026-04-13.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# NewEditor 宿主重构计划
|
||||
|
||||
## 目标
|
||||
|
||||
把 `new_editor` 当前的窗口宿主从“功能可运行的过渡方案”收敛成可长期演进的 Editor 宿主架构,核心目标如下:
|
||||
|
||||
1. 主显示链最终统一到纯 D3D12。
|
||||
2. 窗口线程只负责消息和状态,不承担 GPU 重活。
|
||||
3. live resize 必须真实更新,但不能再走同步阻塞窗口线程的路径。
|
||||
4. Editor shell 和 Scene/Game viewport 统一纳入宿主合成层。
|
||||
5. 当前 `D3D11On12 + D2D` 只允许作为过渡路径,不能继续加深耦合。
|
||||
|
||||
## 当前问题
|
||||
|
||||
### 1. 宿主职责混在 `Application`
|
||||
|
||||
当前 `Application` 同时承担:
|
||||
|
||||
- Win32 消息调度
|
||||
- 宿主运行时状态
|
||||
- resize / dpi / deferred render 调度
|
||||
- editor 状态更新
|
||||
- present 前后的宿主控制
|
||||
|
||||
这会导致宿主问题难以单独定位和演进。
|
||||
|
||||
### 2. 主显示链过厚
|
||||
|
||||
当前主路径仍然依赖:
|
||||
|
||||
- D3D12 viewport 渲染
|
||||
- swapchain backbuffer
|
||||
- D3D11On12 wrapped resource
|
||||
- D2D 绘制 shell
|
||||
- present
|
||||
|
||||
这条链在 live resize、frame pacing、backbuffer 生命周期上都偏重。
|
||||
|
||||
### 3. resize 热路径仍然偏保守
|
||||
|
||||
虽然已经把 `WM_SIZE` 从直接同步 resize 调整为 deferred render 触发,但 resize 热路径里仍存在:
|
||||
|
||||
- backbuffer interop target 重建
|
||||
- swapchain resize
|
||||
- 资源生命周期收束
|
||||
|
||||
这还不是最终架构。
|
||||
|
||||
## 重构阶段
|
||||
|
||||
## 阶段 1:宿主分层
|
||||
|
||||
### 目标
|
||||
|
||||
先把结构理顺,停止继续把宿主逻辑堆进 `Application`。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 拆出宿主运行时状态对象。
|
||||
2. 拆出窗口消息调度与 deferred render 调度。
|
||||
3. 让 `Application` 只保留 editor 业务更新与高层协作职责。
|
||||
4. 为后续 HostRenderer / HostCompositor 留清晰边界。
|
||||
|
||||
### 完成标准
|
||||
|
||||
1. resize / dpi / deferred render / interactive resize 状态不再散落在 `Application` 成员里。
|
||||
2. `Application` 的宿主状态字段明显减少。
|
||||
3. 现有功能与布局不回退。
|
||||
|
||||
## 阶段 2:建立 HostRenderer / HostCompositor 边界
|
||||
|
||||
### 目标
|
||||
|
||||
把宿主渲染拆成明确两层:
|
||||
|
||||
1. `HostRenderer`
|
||||
责任:device / queue / swapchain / backbuffer / fence / present
|
||||
2. `HostCompositor`
|
||||
责任:把 shell draw data、viewport texture、icon/text 统一合成到 backbuffer
|
||||
|
||||
### 任务
|
||||
|
||||
1. 停止让 `NativeRenderer` 既像窗口绘制器又像过渡 compositor。
|
||||
2. 把“主窗口显示”和“UI 绘制命令解释”职责显式拆开。
|
||||
3. 为纯 D3D12 compositor 做接口准备。
|
||||
|
||||
### 完成标准
|
||||
|
||||
1. `HostRenderer` 不依赖 D2D 语义。
|
||||
2. `HostCompositor` 成为唯一的宿主 UI 合成入口。
|
||||
3. 现有 `NativeRenderer` 明确退化为过渡层或 fallback。
|
||||
|
||||
## 阶段 3:主显示链切换到纯 D3D12
|
||||
|
||||
### 目标
|
||||
|
||||
去掉 `D3D11On12 + D2D` 在主显示链中的核心地位。
|
||||
|
||||
### 任务
|
||||
|
||||
1. shell 矩形、线条、图像、文字统一进入 D3D12 UI compositor。
|
||||
2. Scene/Game viewport 作为普通 SRV 输入参与同一条 compositor pass。
|
||||
3. backbuffer 只通过 D3D12 呈现。
|
||||
|
||||
### 完成标准
|
||||
|
||||
1. 主窗口显示不再依赖 D2D。
|
||||
2. resize 时不再重建 D3D11On12 backbuffer interop target。
|
||||
3. `new_editor` 主显示链可在没有 D3D11On12 的条件下工作。
|
||||
|
||||
## 阶段 4:重写 resize 状态机
|
||||
|
||||
### 目标
|
||||
|
||||
做到真实 live resize,但不阻塞窗口线程。
|
||||
|
||||
### 任务
|
||||
|
||||
1. `WM_SIZE` 只更新最新目标尺寸。
|
||||
2. render tick 只消费最新尺寸,不处理过期尺寸。
|
||||
3. resize 不允许在消息处理里做 GPU 等待。
|
||||
4. resize / present / viewport surface 生命周期统一到宿主状态机。
|
||||
|
||||
### 完成标准
|
||||
|
||||
1. 拖动窗口边界时不黑屏。
|
||||
2. 不出现黑白垃圾区。
|
||||
3. 窗口拖动体感明显优于当前实现。
|
||||
|
||||
## 阶段 5:去掉 resize 路径里的全队列等待
|
||||
|
||||
### 目标
|
||||
|
||||
去掉当前最重的同步点。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 改成 per-backbuffer / per-frame 生命周期管理。
|
||||
2. 只等待必须退休的资源代际。
|
||||
3. 禁止 resize 路径里的整队列 idle 等待。
|
||||
|
||||
### 完成标准
|
||||
|
||||
1. resize 期间 CPU/GPU 同步明显减少。
|
||||
2. live resize 手感继续改善。
|
||||
|
||||
## 阶段 6:viewport 生命周期收口
|
||||
|
||||
### 目标
|
||||
|
||||
让 Scene/Game viewport 与外层宿主真正解耦。
|
||||
|
||||
### 任务
|
||||
|
||||
1. viewport render target 长期存活,不跟着宿主链大拆大建。
|
||||
2. 宿主 compositor 只采样 viewport 结果,不干预其内部资源生命周期。
|
||||
3. Scene/Game 在 interactive resize 期间保持稳定。
|
||||
|
||||
### 完成标准
|
||||
|
||||
1. viewport 不再因宿主 resize 逻辑出现黑屏或闪烁。
|
||||
2. 宿主和 viewport 各自职责明确。
|
||||
|
||||
## 执行顺序
|
||||
|
||||
1. 先完成阶段 1。
|
||||
2. 然后搭好阶段 2 的 HostRenderer / HostCompositor 边界。
|
||||
3. 再推进阶段 3,切主显示链到纯 D3D12。
|
||||
4. 最后做阶段 4、5、6 的性能与生命周期收口。
|
||||
|
||||
## 当前落点
|
||||
|
||||
当前阶段 1 已完成,阶段 2 已开始收口:
|
||||
|
||||
1. 已新增 `HostRuntimeState`,把宿主 DPI / interactive resize / pending resize / deferred render 状态从 `Application` 中抽离。
|
||||
2. 已新增 `WindowMessageDispatcher`,把 `WndProc` 中的宿主消息调度与 deferred render 调度拆到 `Host` 层。
|
||||
3. 已把 `D3D12WindowRenderLoop` 从悬空 helper 升级为主窗口帧编排入口,开始统一 `BeginFrame / viewport render / UI present / fallback / resize interop` 这条链。
|
||||
4. 已把 viewport render target 资源工厂从 `ProductViewportRenderTargets.h` 收成独立 manager,`ProductViewportHostService` 只保留请求/返回 frame 的业务外壳。
|
||||
5. 已把 shader resource descriptor 分配职责从 `D3D12WindowRenderer` 抽到独立的 `D3D12ShaderResourceDescriptorAllocator`,减少窗口呈现器的非 swapchain 职责。
|
||||
6. 下一步进入阶段 2 主体:继续拆 `NativeRenderer` 中的窗口互操作 / present 组合路径,把 D2D UI 光栅化与 D3D11On12 窗口合成进一步分离。
|
||||
283
docs/plan/NewEditor_方案1无边框宿主采用计划_2026-04-14.md
Normal file
283
docs/plan/NewEditor_方案1无边框宿主采用计划_2026-04-14.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# NewEditor 方案1无边框宿主采用计划
|
||||
|
||||
日期: `2026-04-14`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
把 `new_editor` 从当前的原生 `WS_OVERLAPPEDWINDOW + DWM + HWND swapchain` 宿主模式,重构为:
|
||||
|
||||
- 无边框顶层窗口
|
||||
- 自绘标题栏与边框
|
||||
- 应用自己接管 move / resize / maximize / restore / hit-test
|
||||
- 宿主渲染链与窗口尺寸变化由同一套状态机驱动
|
||||
|
||||
核心目的只有一个:
|
||||
|
||||
尽可能消除当前原生窗口 live resize 期间那种“旧帧被系统先拉伸,再等新帧补上”的视觉变形。
|
||||
|
||||
## 2. 为什么必须走这条路
|
||||
|
||||
当前路径的问题不是 XCUI 布局树本身,而是宿主显示链顺序决定的:
|
||||
|
||||
1. Windows 先改变原生窗口外框尺寸。
|
||||
2. DWM 立即需要一张图去填新窗口区域。
|
||||
3. `new_editor` 再收到 `WM_SIZE`,之后才开始:
|
||||
- `ResizeBuffers`
|
||||
- backbuffer / interop target 处理
|
||||
- UI + viewport 合成
|
||||
- `Present`
|
||||
4. 这中间只要慢一个 compositor tick,就会看到旧尺寸帧被拉伸。
|
||||
|
||||
只要继续使用原生 non-client resize,这个问题就只能减轻,不能彻底归零。
|
||||
|
||||
## 3. 方案1的总思路
|
||||
|
||||
不要再让系统驱动 resize 交互。
|
||||
|
||||
改为:
|
||||
|
||||
1. 窗口本身使用无边框样式。
|
||||
2. 顶部标题栏、拖动区、8 个 resize grip 全部放到 client area。
|
||||
3. 鼠标拖动边界时,不进入系统的 `WM_ENTERSIZEMOVE` 模态循环。
|
||||
4. 应用自己维护一套 `WindowFrameController`:
|
||||
- 记录拖动起点
|
||||
- 计算目标外框矩形
|
||||
- 先驱动宿主渲染链切到目标尺寸
|
||||
- 再提交窗口矩形变化与新帧 present
|
||||
|
||||
这样窗口尺寸变化、swapchain 尺寸变化、UI 布局变化、present 节奏都由应用自己掌控,而不是被 Windows 原生外框拆成两段。
|
||||
|
||||
## 4. 重构边界
|
||||
|
||||
这次重构只动 `new_editor/app/Host` 宿主层,不把业务逻辑塞进去。
|
||||
|
||||
### 4.1 保留不动的层
|
||||
|
||||
- `XCEditor` 基础 UI 组件层
|
||||
- `new_editor/app/Workspace`
|
||||
- `new_editor/app/Panels`
|
||||
- `Viewport` 业务层
|
||||
|
||||
### 4.2 主要改造层
|
||||
|
||||
- [Application.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Application.cpp)
|
||||
- [Application.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Application.h)
|
||||
- [WindowMessageDispatcher.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/WindowMessageDispatcher.cpp)
|
||||
- [WindowMessageDispatcher.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/WindowMessageDispatcher.h)
|
||||
- [HostRuntimeState.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/HostRuntimeState.h)
|
||||
- [D3D12WindowRenderLoop.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowRenderLoop.cpp)
|
||||
- [D3D12WindowRenderer.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowRenderer.cpp)
|
||||
- [D3D12WindowSwapChainPresenter.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp)
|
||||
|
||||
### 4.3 新增宿主对象
|
||||
|
||||
建议新增以下文件:
|
||||
|
||||
- `new_editor/app/Host/WindowFrameMetrics.h`
|
||||
- `new_editor/app/Host/WindowFrameController.h`
|
||||
- `new_editor/app/Host/WindowFrameController.cpp`
|
||||
- `new_editor/app/Host/WindowFrameInteractionState.h`
|
||||
- `new_editor/app/Host/BorderlessWindowStyle.h`
|
||||
|
||||
职责划分:
|
||||
|
||||
- `WindowFrameMetrics`
|
||||
- 统一管理标题栏高度、resize 边缘厚度、阴影扩展、caption button 区域
|
||||
- `WindowFrameInteractionState`
|
||||
- 记录当前是否在 move / resize
|
||||
- 记录激活边、起始窗口矩形、起始鼠标屏幕坐标
|
||||
- `WindowFrameController`
|
||||
- 处理 hit-test、开始拖动、更新拖动、结束拖动
|
||||
- 计算目标窗口矩形
|
||||
- 输出“本帧宿主需要切到什么尺寸”
|
||||
- `BorderlessWindowStyle`
|
||||
- 集中处理 `WS_POPUP / WS_THICKFRAME / WS_CAPTION` 等样式组合与 DWM 扩展
|
||||
|
||||
## 5. 分阶段执行
|
||||
|
||||
## 阶段 A:把窗口宿主从原生外框切到无边框
|
||||
|
||||
### 目标
|
||||
|
||||
先完成“视觉上还是一个正常窗口,但 non-client 已经不再由系统绘制”。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 创建窗口时移除 `WS_OVERLAPPEDWINDOW`,改为适合无边框的样式组合。
|
||||
2. 顶部工具栏正式承担标题栏职责。
|
||||
3. 处理:
|
||||
- 最小化
|
||||
- 最大化
|
||||
- 还原
|
||||
- 关闭
|
||||
4. 保留 Windows 阴影、任务栏行为、Alt+Tab 行为。
|
||||
|
||||
### 验收
|
||||
|
||||
1. 窗口外框由 XCUI 自己绘制。
|
||||
2. 顶部区域可拖动移动窗口。
|
||||
3. 基本窗口控制按钮可用。
|
||||
|
||||
## 阶段 B:接管 hit-test 与 8 向 resize 手势
|
||||
|
||||
### 目标
|
||||
|
||||
不再依赖系统 non-client resize。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 在 client area 内定义:
|
||||
- 左 / 右 / 上 / 下
|
||||
- 左上 / 右上 / 左下 / 右下
|
||||
的 resize 热区。
|
||||
2. 鼠标 hover 时稳定切换对应 cursor。
|
||||
3. 鼠标按下后进入 `WindowFrameInteractionState`。
|
||||
4. 鼠标移动时由 `WindowFrameController` 计算目标外框矩形。
|
||||
|
||||
### 验收
|
||||
|
||||
1. 8 个方向 resize 都能工作。
|
||||
2. cursor 不闪烁。
|
||||
3. 不再进入 `WM_ENTERSIZEMOVE / WM_EXITSIZEMOVE` 原生模态链。
|
||||
|
||||
## 阶段 C:把窗口尺寸变化改成“应用驱动”
|
||||
|
||||
### 目标
|
||||
|
||||
让窗口矩形变化和新尺寸帧提交进入同一条应用控制链。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 取消当前“完全依赖 `WM_SIZE` 才处理 resize”的模式。
|
||||
2. 鼠标拖动过程中,先由 `WindowFrameController` 产出目标 client size。
|
||||
3. 宿主渲染层按目标 size:
|
||||
- resize swapchain
|
||||
- rebuild backbuffer view
|
||||
- 重新布局 UI
|
||||
- render + present
|
||||
4. 然后再提交窗口矩形更新。
|
||||
|
||||
这里的关键不是“永远先后顺序绝对固定”,而是宿主要从“被动响应 Windows resize”改成“主动推进目标尺寸帧”。
|
||||
|
||||
### 验收
|
||||
|
||||
1. live resize 期间不再有明显的整窗拉伸。
|
||||
2. 内部 UI 不再跟随旧帧一起被整体放大缩小。
|
||||
3. resize 时不黑屏、不闪退。
|
||||
|
||||
## 阶段 D:把 Scene / Game viewport 也纳入同一节奏
|
||||
|
||||
### 目标
|
||||
|
||||
避免窗口外层尺寸变化和 viewport target 更新脱节。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 宿主 resize 状态机输出统一尺寸变更事件。
|
||||
2. `ProductViewportHostService` 消费同一目标尺寸。
|
||||
3. viewport render target 与 host swapchain 的 resize 节奏统一。
|
||||
|
||||
### 验收
|
||||
|
||||
1. 调外层窗口尺寸时,Scene / Game 内部画布不再滞后。
|
||||
2. 不再出现外层变了、内部蓝底画布慢一拍才追上的情况。
|
||||
|
||||
## 阶段 E:补齐无边框窗口的系统行为
|
||||
|
||||
### 目标
|
||||
|
||||
把“能用”补到“能长期替代正式编辑器宿主”。
|
||||
|
||||
### 任务
|
||||
|
||||
1. 双击标题栏最大化 / 还原。
|
||||
2. 拖到屏幕顶部最大化。
|
||||
3. 屏幕边缘吸附。
|
||||
4. 多显示器 DPI / work area 正确处理。
|
||||
5. 最小窗口尺寸约束。
|
||||
6. 系统菜单与快捷键兼容。
|
||||
|
||||
### 验收
|
||||
|
||||
1. 宿主交互接近常规桌面应用。
|
||||
2. 不因无边框而丢失基本桌面体验。
|
||||
|
||||
## 6. 架构原则
|
||||
|
||||
### 6.1 不允许把窗口交互逻辑混回 `Application`
|
||||
|
||||
`Application` 只能做:
|
||||
|
||||
- 应用生命周期
|
||||
- 编辑器业务装配
|
||||
- 驱动一帧更新与渲染
|
||||
|
||||
窗口移动、边框 hit-test、resize 手势状态机都必须沉淀到 `Host`。
|
||||
|
||||
### 6.2 不允许为了“先跑通”堆事件补丁
|
||||
|
||||
禁止继续沿这些方向堆补丁:
|
||||
|
||||
- `WM_SIZE` 后疯狂补 render
|
||||
- `ValidateRect` / `InvalidateRect` 来回试
|
||||
- `DwmFlush` 之类的消息尾部补丁
|
||||
- 在 resize 过程中乱加全局 GPU idle
|
||||
|
||||
这些都只能缓解,不能把宿主控制权拿回来。
|
||||
|
||||
### 6.3 宿主渲染链必须单向清晰
|
||||
|
||||
目标链路必须收敛成:
|
||||
|
||||
`WindowFrameController -> Host resize state -> swapchain/backbuffer resize -> UI + viewport compose -> present`
|
||||
|
||||
而不是:
|
||||
|
||||
`Windows non-client -> WM_SIZE -> 若干临时补丁 -> 侥幸赶上 compositor`
|
||||
|
||||
## 7. 风险点
|
||||
|
||||
### 7.1 这不是小修
|
||||
|
||||
这是宿主交互模型切换,不是单点 bugfix。
|
||||
|
||||
### 7.2 需要额外补齐桌面窗口语义
|
||||
|
||||
无边框之后,很多系统行为都要自己接:
|
||||
|
||||
- caption drag
|
||||
- 双击最大化
|
||||
- hit-test
|
||||
- snap
|
||||
- monitor work area
|
||||
- DPI 变更
|
||||
|
||||
### 7.3 需要严格做阶段验收
|
||||
|
||||
每一阶段都必须编译并人工验证,不然很容易把宿主架构搞乱。
|
||||
|
||||
## 8. 推荐执行顺序
|
||||
|
||||
1. 阶段 A:无边框窗口切换
|
||||
2. 阶段 B:hit-test / resize 手势
|
||||
3. 阶段 C:应用驱动 resize 主链
|
||||
4. 阶段 D:viewport resize 节奏统一
|
||||
5. 阶段 E:补系统级桌面行为
|
||||
|
||||
## 9. 收口标准
|
||||
|
||||
满足以下条件,才算方案1真正落地:
|
||||
|
||||
1. `new_editor` 已彻底脱离原生 non-client resize。
|
||||
2. 窗口拖边 resize 时,整窗拉伸变形基本消失。
|
||||
3. Scene / Game viewport 尺寸变化与外层窗口同步。
|
||||
4. resize 过程中无黑屏、无崩溃、无 cursor 闪烁。
|
||||
5. 顶部标题栏、最大化、吸附、DPI 行为达到正式编辑器可用水平。
|
||||
|
||||
## 10. 结论
|
||||
|
||||
如果目标是“尽可能彻底消除 resize 变形”,就不该继续在当前原生外框链路上修修补补。
|
||||
|
||||
正确主线是:
|
||||
|
||||
把 `new_editor` 宿主改成无边框、自管标题栏、自管 resize、自管 present 节奏的 editor shell。
|
||||
310
docs/plan/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md
Normal file
310
docs/plan/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Renderer C++层第一阶段收口计划
|
||||
|
||||
日期: `2026-04-13`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
这份文档是 `Render Graph` 之前的最后一轮 `native render kernel v1` 收口计划。
|
||||
|
||||
目标不是继续往当前 `Rendering` 层里塞新功能,而是先把下面几件事彻底做清楚:
|
||||
|
||||
- `request -> frame plan -> execution` 三层语义分开。
|
||||
- `RenderSceneExtractor -> CullingResults -> RendererList -> DrawSettings` 这条数据组织链正式化。
|
||||
- `BuiltinForwardPipeline` 从“大杂烩渲染器”收成“协调器 + 子能力”。
|
||||
- `Editor` 注入点、阴影、主场景、后处理之间的边界说清楚。
|
||||
- 给下一步 `C++ Render Graph` 留出稳定输入,而不是把当前混乱直接 graph 化。
|
||||
|
||||
结论先写在前面:
|
||||
|
||||
- `Render Graph` 应该做在 `C++ native` 层。
|
||||
- 现在还不应该直接开做 `Render Graph` 或 `Deferred Renderer`。
|
||||
- 当前更优先的是把现有渲染层整理干净,再往 SRP/URP 方向演进。
|
||||
|
||||
## 2. 当前阶段已经完成的收口
|
||||
|
||||
截至 `2026-04-13` 晚间,已经落地的内容:
|
||||
|
||||
- `CameraRenderRequest -> CameraFramePlan` 已经分层,`CameraRenderer` 开始正式消费 plan。
|
||||
- `CullingResults / RendererList` contract 已经进入 `RenderSceneData`。
|
||||
- `Forward / Depth / ObjectId` 已经优先走 `RendererList`,旧 `visibleItems` 只保留过渡 fallback。
|
||||
- `RendererListUtils` 已经把可见项遍历规则收口成公共工具。
|
||||
- `Gaussian / Volumetric` 已经从 `BuiltinForwardPipeline` 中抽成统一 `SceneRenderFeaturePass`。
|
||||
- `FrameExecutionContext / ScenePhase / DrawSettings` 已经引入。
|
||||
- `RenderPipeline` 已经支持新的 `FrameExecutionContext` 入口,旧三参数入口保留为兼容适配层。
|
||||
- `BuiltinForwardPipeline` 已经开始按 scene phase 组织主场景执行。
|
||||
- `CameraRenderer` 主场景阶段已经走新的 execution context,而不是继续只传三参数。
|
||||
|
||||
本轮验证结果:
|
||||
|
||||
- `XCEditor` 已重新编译通过。
|
||||
- `rendering_unit_tests` 已重新编译通过。
|
||||
- `editor_tests` 已重新编译通过。
|
||||
- `editor_tests --gtest_filter=*ViewportRenderFlow*:*SceneViewportRenderPassBundle* --gtest_brief=1` 通过 `14/14`。
|
||||
- `rendering_unit_tests --gtest_filter=*BuiltinForwardPipeline*:*RenderPass*:*CameraSceneRenderer*:*Shadow*:*ObjectId* --gtest_brief=1` 通过 `67/68`。
|
||||
- 唯一失败仍然是既有老问题:
|
||||
`BuiltinForwardPipeline_Test.OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions`
|
||||
|
||||
## 3. 为什么现在还不能直接上 Render Graph
|
||||
|
||||
现在直接上 `Render Graph`,本质上是在 graph 里继续承接当前的隐式耦合,问题只会换个位置存在。
|
||||
|
||||
当前还没完全收口的核心问题不是“没有 graph”,而是下面这些边界还不够正式:
|
||||
|
||||
- `CameraRenderer` 和各类 scene pass 之间,谁负责组织阶段,谁负责执行,还没有完全模块化。
|
||||
- `Shadow` 相关逻辑仍然分散在 planner、camera execution、pipeline、surface cache 几处。
|
||||
- `BuiltinForwardPipeline` 虽然已经开始瘦身,但还没有彻底收成一个明确的 coordinator。
|
||||
- `Editor` 的扩展点虽然已可用,但 contract 还偏“约定式”,还不够像以后给 `C# SRP` 暴露的正式接口。
|
||||
- 目录结构仍然在暴露旧历史:一些“子系统”目前还是文件堆,不是真正的模块。
|
||||
|
||||
如果这些问题不先解决,`Render Graph` 只会变成一个新的承压层,后面再拆更贵。
|
||||
|
||||
## 4. Render Graph 和未来 SRP/URP 的边界
|
||||
|
||||
未来的推荐结构仍然是这条线:
|
||||
|
||||
`RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline`
|
||||
|
||||
其中边界应当明确成这样:
|
||||
|
||||
- `C++ native` 保留:
|
||||
- `Render Graph`
|
||||
- `CullingResults / RendererList`
|
||||
- draw / dispatch context
|
||||
- GPU resource cache
|
||||
- shadow map / atlas 执行能力
|
||||
- Gaussian 排序与 working set
|
||||
- Volume 资源上传与底层执行
|
||||
- `URP-like` 包层保留:
|
||||
- feature 编排
|
||||
- pass 注入顺序
|
||||
- renderer feature 配置
|
||||
- 用户级 pipeline 组合
|
||||
- 未来的 `C#` 自定义渲染管线 API
|
||||
|
||||
这意味着:
|
||||
|
||||
- 阴影、Gaussian、Volumetric 不是整块搬去包层。
|
||||
- 包层更像 Unity 的 `URP renderer` 和 `renderer feature`。
|
||||
- native 层更像 Unity 没公开给用户、但真正负责执行和图资源生命周期的内核。
|
||||
|
||||
## 5. 第一阶段还差哪些工作才能收口
|
||||
|
||||
### 5.1 Shadow 子系统正式化
|
||||
|
||||
这是当前最值得先做的一块。
|
||||
|
||||
现状问题:
|
||||
|
||||
- 阴影规划在 `Planning`。
|
||||
- 阴影 surface/cache 在 `Execution/Caches`。
|
||||
- 阴影执行又是 standalone pass。
|
||||
- 主场景采样阴影的状态切换仍在 `BuiltinForwardPipeline`。
|
||||
|
||||
这说明阴影现在“能跑”,但还不是一个正式子系统。
|
||||
|
||||
收口目标:
|
||||
|
||||
- 明确 `shadow planning / shadow resources / shadow execution / shadow sampling contract` 四层边界。
|
||||
- 把方向光阴影相关共享数据收成一组正式类型,而不是散落在多个调用点上。
|
||||
- 为以后扩展 `cascades / shadow atlas / additional light shadows / baked shadowmask` 留出位置。
|
||||
|
||||
### 5.2 Editor 注入点正式化
|
||||
|
||||
现状问题:
|
||||
|
||||
- 当前有 `pre scene / post scene / overlay / object id / outline / grid` 多种 editor 路径。
|
||||
- 这些能力已经能工作,但还没有统一成“正式 stage 注入 contract”。
|
||||
|
||||
收口目标:
|
||||
|
||||
- 明确哪些注入点属于 runtime 正式阶段,哪些属于 editor-only extension。
|
||||
- 让 `CameraFramePlan` 对这些阶段的描述更加稳定。
|
||||
- 给以后 `C# renderer feature` 的注入点设计提供 native 对照物。
|
||||
|
||||
### 5.3 BuiltinForwardPipeline 继续瘦身
|
||||
|
||||
本轮已经完成第一刀,但还没收完。
|
||||
|
||||
还需要继续做的事情:
|
||||
|
||||
- 把 forward scene phase 执行和底层 draw/resource 逻辑彻底分开。
|
||||
- 把 forward renderer 自己的内部资源、skybox、feature 协调代码进一步收拢。
|
||||
- 保证 `BuiltinForwardPipeline` 自己只表达“阶段顺序和协调”,不再继续膨胀。
|
||||
|
||||
### 5.4 旧 fallback 的裁边
|
||||
|
||||
当前有一些过渡兼容层是故意保留的:
|
||||
|
||||
- `RenderPipeline::Render(context, surface, sceneData)` 旧入口
|
||||
- `visibleItems` fallback
|
||||
- `BuildRenderRequests` 旧习惯入口
|
||||
|
||||
这些现在不该硬删,因为会影响现有 tests 和 editor 链路。
|
||||
|
||||
但第一阶段收口前,至少要做到:
|
||||
|
||||
- 新主链默认只走正式 contract。
|
||||
- 旧入口只作为 adapter,不再新增依赖。
|
||||
- 每个 fallback 都有明确退出条件。
|
||||
|
||||
### 5.5 测试和文档补齐
|
||||
|
||||
当前已有回归还不够系统。
|
||||
|
||||
还需要补的重点测试:
|
||||
|
||||
- `FrameExecutionContext` 是否完整透传 source surface / source color view / state。
|
||||
- `BuiltinForwardPipeline` scene phase 顺序是否稳定。
|
||||
- feature pass 的 `Prepare -> Execute` 顺序是否稳定。
|
||||
- editor 注入点在有后处理和无后处理两种情况下是否仍能保持正确 source/destination surface。
|
||||
- shadow planning 和 main scene shadow sampling 之间的契约测试。
|
||||
|
||||
## 6. 下一步最优先该做什么
|
||||
|
||||
下一步最优先建议做:
|
||||
|
||||
`Shadow 子系统收口`
|
||||
|
||||
原因很直接:
|
||||
|
||||
- 它横跨 `Planning / Execution / Pipeline / Resources / Sampling`。
|
||||
- 它是以后 `Deferred / Light Baking / ShadowMask / Additional Light Shadows` 的共同基础。
|
||||
- 它也是未来 `Render Graph` 最敏感的一类资源依赖链。
|
||||
- 如果阴影先不收,后面 graph 化时 barrier、resource lifetime、pass dependency 都会更乱。
|
||||
|
||||
建议顺序:
|
||||
|
||||
1. 先把方向光阴影的 plan/data/resource/execute contract 固化。
|
||||
2. 再把 editor 注入 contract 固化。
|
||||
3. 然后做第一轮目录收拢。
|
||||
4. 最后判断这一阶段是否可以正式收口,进入 `Render Graph` 设计。
|
||||
|
||||
## 7. 当前 Rendering 目录结构存在的问题
|
||||
|
||||
### 7.1 `FrameData` 目录太杂
|
||||
|
||||
现在 `FrameData` 里同时混着:
|
||||
|
||||
- camera/environment 数据
|
||||
- scene snapshot
|
||||
- visible item
|
||||
- culling results
|
||||
- renderer list utils
|
||||
|
||||
这说明它不是一个明确模块,而是“跟帧有关的先放这”。
|
||||
|
||||
建议方向:
|
||||
|
||||
- `FrameData` 保留纯只读帧快照类型。
|
||||
- 把 `CullingResults / RendererList / Filtering / Sorting / DrawSettings` 逐步收进更明确的 `Execution` 或 `Culling` 子域。
|
||||
- `RendererListUtils` 这种执行辅助逻辑,不适合长期留在 `FrameData`。
|
||||
|
||||
### 7.2 `Passes` 目录混合了三类东西
|
||||
|
||||
现在 `Passes` 里同时有:
|
||||
|
||||
- 主场景 pass
|
||||
- fullscreen/post-process pass
|
||||
- editor pass
|
||||
|
||||
这会让“runtime 正式渲染能力”和“editor 辅助渲染能力”继续混在一起。
|
||||
|
||||
建议方向:
|
||||
|
||||
- `Passes/Scene`
|
||||
- `Passes/Fullscreen`
|
||||
- `Passes/Editor`
|
||||
- `Passes/Shadow`
|
||||
|
||||
不一定要一次性全搬,但后面新增文件不应继续平铺。
|
||||
|
||||
### 7.3 `BuiltinForwardPipeline` 还没形成真正子模块
|
||||
|
||||
当前 forward 相关代码已经分散在:
|
||||
|
||||
- `BuiltinForwardPipeline.h/.cpp`
|
||||
- `BuiltinForwardPipelineSkybox.cpp`
|
||||
- `Internal/BuiltinForwardPipelineResources.cpp`
|
||||
|
||||
这已经具备“应该单独成目录”的条件。
|
||||
|
||||
建议方向:
|
||||
|
||||
- `Rendering/Pipelines/BuiltinForward/`
|
||||
- `BuiltinForwardPipeline.h`
|
||||
- `BuiltinForwardPipeline.cpp`
|
||||
- `BuiltinForwardPipelineScene.cpp`
|
||||
- `BuiltinForwardPipelineResources.cpp`
|
||||
- `BuiltinForwardPipelineSkybox.cpp`
|
||||
|
||||
这样后面 forward / deferred / universal renderer 才能并列演进。
|
||||
|
||||
### 7.4 `Shadow` 目录还不算真正的 shadow 子系统
|
||||
|
||||
目前虽然已经有 `Rendering/Shadow`,但从全链路看,阴影的责任仍然散在其他目录。
|
||||
|
||||
建议方向:
|
||||
|
||||
- `Shadow` 目录最终至少容纳:
|
||||
- planning types
|
||||
- shared frame data
|
||||
- runtime resources / caches
|
||||
- shadow executor or shadow renderer
|
||||
|
||||
### 7.5 `Internal` 边界不稳定
|
||||
|
||||
现在 `src/Rendering/Internal` 更像历史遗留缓冲区。
|
||||
|
||||
长期风险:
|
||||
|
||||
- 真正属于 forward 的内部实现放在全局 `Internal`
|
||||
- 真正属于 volume / splat / shadow 的内部实现也可能继续堆进去
|
||||
|
||||
建议方向:
|
||||
|
||||
- 全局 `Internal` 只保留跨模块公共 helper。
|
||||
- 各子系统自己的内部文件放回各自目录。
|
||||
|
||||
## 8. 建议的第一轮目录收拢动作
|
||||
|
||||
这一轮只做低风险收拢,不做大搬家。
|
||||
|
||||
建议动作:
|
||||
|
||||
- 把 `BuiltinForwardPipeline*` 相关实现收成 `Pipelines/BuiltinForward/` 子目录。
|
||||
- 给 `Passes` 至少先分出 `Editor` 和 `Fullscreen` 两个子目录。
|
||||
- 给 `Shadow` 补一组正式 shared types,再决定是否移动更多实现文件。
|
||||
- 暂时不大动 `include` 层 public 路径,先保证编译和引用稳定。
|
||||
|
||||
原则:
|
||||
|
||||
- 先按职责拆,再按目录搬。
|
||||
- 不为了“好看”做纯目录手术。
|
||||
- 每次收拢都必须带编译和回归。
|
||||
|
||||
## 9. 第一阶段收口完成的判定标准
|
||||
|
||||
满足下面这些条件,就可以认为 `Render Graph` 之前这一阶段基本收口:
|
||||
|
||||
- `CameraFramePlan` 成为主执行入口,旧 request 入口只保留兼容壳。
|
||||
- `FrameExecutionContext / ScenePhase / DrawSettings` 成为主场景执行正式 contract。
|
||||
- `CullingResults / RendererList` 成为主要 draw organization contract。
|
||||
- `BuiltinForwardPipeline` 被收成 coordinator,而不是继续长成总垃圾堆。
|
||||
- `Shadow` 成为清晰子系统,而不是散点逻辑。
|
||||
- `Editor` 注入点形成稳定 contract。
|
||||
- 目录结构不再继续鼓励“新逻辑直接往老文件里塞”。
|
||||
- 相关 tests 和编译链稳定。
|
||||
|
||||
到这一步,再开始 `C++ Render Graph`,代价才是可控的。
|
||||
|
||||
## 10. 当前建议
|
||||
|
||||
当前最佳路线不变:
|
||||
|
||||
- 短期:继续按 Unity 的 `SRP + URP` 方向做 native 基座。
|
||||
- 中期:先在 `C++` 做出稳定的 `Render Graph + RendererList + Feature Injection` 内核。
|
||||
- 中长期:再暴露 `C#` 自定义渲染管线接口,做你自己的 `URP-like` 包层。
|
||||
|
||||
眼下最正确的动作不是“赶紧补 deferred”,也不是“先把所有特效搬去包层”,而是:
|
||||
|
||||
`先把当前 native rendering layer 整理成一个真正可扩展的内核。`
|
||||
462
docs/plan/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md
Normal file
462
docs/plan/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# Renderer C++层第一阶段重构计划(Render Graph 前)
|
||||
|
||||
日期:`2026-04-13`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
这份计划只处理 `Render Graph` 之前的那一步,也就是把当前 `Rendering` 原生主链先整理成一个可长期演进的正式架构。
|
||||
|
||||
这一步的目标不是新增大功能,而是先把下面这些边界收口:
|
||||
|
||||
1. `SceneRenderer / CameraRenderer / SceneRenderRequestPlanner` 的职责边界
|
||||
2. `RenderSceneExtractor / RenderSceneData / VisibleRenderItem` 的数据边界
|
||||
3. `RenderPipeline / RenderPass / BuiltinForwardPipeline` 的执行边界
|
||||
4. Runtime 主链与 Editor 注入点的边界
|
||||
|
||||
这一步做完之后,下一阶段才适合引入真正的 `Render Graph`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 关于 Render Graph 的结论
|
||||
|
||||
结论很明确:
|
||||
|
||||
1. `Render Graph` 应该先做在 `C++ native` 层
|
||||
2. 它不应该直接作为第一阶段的一部分
|
||||
3. 第一阶段的任务,是先把当前 native renderer 收口到足够清晰,避免后面只是把现有混乱机械搬进 graph
|
||||
|
||||
后面的目标结构应该是:
|
||||
|
||||
`RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline`
|
||||
|
||||
其中:
|
||||
|
||||
1. `Render Graph` 属于 `Native Render Kernel`
|
||||
2. `C#` 层以后可以编排 pass,但图资源生命周期、资源状态、barrier、跨 pass 读写依赖仍应先由 native graph 掌控
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前代码里的真实主链
|
||||
|
||||
当前项目并不是没有架构,而是已经形成了一条手工编排的 mini-SRP 主链:
|
||||
|
||||
1. `SceneRenderRequestPlanner`
|
||||
- 收集 camera
|
||||
- 生成 `CameraRenderRequest`
|
||||
- 负责方向光阴影规划参数
|
||||
2. `SceneRenderer`
|
||||
- 调 planner
|
||||
- 解析 final color policy
|
||||
- 为 post-process / final-output 附加 fullscreen stage request
|
||||
3. `CameraRenderer`
|
||||
- 做 shadow request resolve
|
||||
- 提取 `RenderSceneData`
|
||||
- 按固定 stage 顺序执行
|
||||
4. `BuiltinForwardPipeline`
|
||||
- 负责主场景绘制
|
||||
- 同时背着 shader 绑定、PSO 缓存、材质资源解析、skybox、splat、volumetric 等大量职责
|
||||
|
||||
对应的当前入口主要是:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h`
|
||||
2. `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h`
|
||||
3. `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h`
|
||||
4. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||
5. `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h`
|
||||
6. `engine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h`
|
||||
7. `engine/include/XCEngine/Rendering/RenderPipeline.h`
|
||||
8. `engine/include/XCEngine/Rendering/RenderPipelineAsset.h`
|
||||
9. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
||||
10. `engine/src/Rendering/Execution/SceneRenderer.cpp`
|
||||
11. `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||
12. `engine/src/Rendering/Extraction/RenderSceneExtractor.cpp`
|
||||
13. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`
|
||||
14. `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp`
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前真正的问题
|
||||
|
||||
当前主要不是“功能太少”,而是“职责层次还不够正式”。
|
||||
|
||||
### 4.1 request、plan、execution 混在一起
|
||||
|
||||
`CameraRenderRequest` 现在既像外部请求,又像内部执行计划,还夹带了一部分 runtime surface / fullscreen chain 组织信息。
|
||||
`SceneRenderer` 也同时承担了 request build、final color resolve、fullscreen intermediate surface ownership 这几类事情。
|
||||
|
||||
这样的问题是:
|
||||
|
||||
1. 后面很难区分“用户想渲染什么”和“引擎决定怎么执行”
|
||||
2. Editor 也会继续直接依赖底层 stage 细节
|
||||
3. Render Graph 以后很难接到一个稳定的 frame plan 输入
|
||||
|
||||
### 4.2 extraction 数据还偏早期
|
||||
|
||||
`RenderSceneExtractor` 现在能工作,但产物仍然偏“把可见对象收集成数组”。
|
||||
它还没有正式长成后续 SRP / Render Graph 真正需要的那种中间层:
|
||||
|
||||
1. `CullingResults`
|
||||
2. `RendererList`
|
||||
3. `FilteringSettings`
|
||||
4. `SortingSettings`
|
||||
5. `DrawSettings`
|
||||
|
||||
如果这层不先正式化,后面 forward、deferred、shadow、object-id、editor overlay 都会继续各自拿 `visibleItems` 做自己的隐式解释。
|
||||
|
||||
### 4.3 BuiltinForwardPipeline 过重
|
||||
|
||||
当前 `BuiltinForwardPipeline` 不只是一个 renderer,它已经同时包含:
|
||||
|
||||
1. 主场景 pass 编排
|
||||
2. 材质资源布局解析
|
||||
3. descriptor set 组织
|
||||
4. pipeline state cache
|
||||
5. lighting / shadow 绑定
|
||||
6. skybox 逻辑
|
||||
7. gaussian splat 特殊路径
|
||||
8. volumetric 特殊路径
|
||||
|
||||
这会带来两个后果:
|
||||
|
||||
1. 后面任何主场景新能力都会继续往这个类里堆
|
||||
2. 未来要抽 `Universal Renderer` 或切出 `Deferred` 时,拆分成本会很高
|
||||
|
||||
### 4.4 runtime 主线和 editor 扩展点还没正式隔离
|
||||
|
||||
现在 Editor 已经深度依赖 request stage 注入点,这个方向本身没错,但 contract 还不够正式。
|
||||
如果不先收口,后面 graph 化时 editor 特殊路径会变成额外复杂度来源。
|
||||
|
||||
### 4.5 pipeline contract 还不够像未来的 SRP 底座
|
||||
|
||||
当前 `RenderPipeline` 还是:
|
||||
|
||||
`Render(context, surface, sceneData)`
|
||||
|
||||
这个接口对当前 builtin forward 足够,但对未来这些东西不够:
|
||||
|
||||
1. renderer list
|
||||
2. graph resource declaration
|
||||
3. per-frame context
|
||||
4. pass feature 注入
|
||||
5. 多渲染路径共存
|
||||
|
||||
第一阶段不要求一步到位,但至少要把它朝这个方向整理。
|
||||
|
||||
---
|
||||
|
||||
## 5. 本阶段明确不做的事
|
||||
|
||||
为了保证收口不失控,这一阶段明确不做下面这些:
|
||||
|
||||
1. 不引入真正运行时 `Render Graph`
|
||||
2. 不做完整 `Deferred Renderer`
|
||||
3. 不做 `C# SRP` 暴露
|
||||
4. 不重写 RHI
|
||||
5. 不新增大型渲染特性来打断架构收口
|
||||
|
||||
本阶段只做一件事:
|
||||
|
||||
`把当前 native rendering layer 整理成 Render Graph 之前的正式形态`
|
||||
|
||||
---
|
||||
|
||||
## 6. 第一阶段完成后应该达到什么状态
|
||||
|
||||
本阶段完成后,native renderer 至少应满足下面这些条件:
|
||||
|
||||
1. 外部 request、内部 frame plan、实际 execution 三层语义分开
|
||||
2. extraction / culling / draw organization 有正式中间层,不再只靠裸 `visibleItems`
|
||||
3. `BuiltinForwardPipeline` 明显瘦身,不再继续当总垃圾桶
|
||||
4. runtime pass 与 editor pass 有明确注入 contract
|
||||
5. 后续 `Render Graph` 能接一个稳定的 `Frame Plan + Renderer Lists + Resource Needs` 输入
|
||||
|
||||
---
|
||||
|
||||
## 7. 第一阶段的重构原则
|
||||
|
||||
### 7.1 先 formalize,再 graphize
|
||||
|
||||
先把 contract 和边界做清楚,再做 graph。
|
||||
不要反过来。
|
||||
|
||||
### 7.2 不破坏现有主链闭环
|
||||
|
||||
当前 `Scene / Game / Editor Viewport` 已经能跑通,第一阶段不能为了“好看”把现有可运行链条打散。
|
||||
|
||||
### 7.3 新中间层优先 native internal
|
||||
|
||||
这一阶段新补的 `Frame Plan`、`CullingResults`、`RendererList`、`DrawSettings` 都先是 C++ 内部契约,不急着暴露给 C#。
|
||||
|
||||
### 7.4 先解耦职责,再决定目录长相
|
||||
|
||||
不要先做大范围目录搬家。
|
||||
先把职责拆开,目录只是最后的外显结果。
|
||||
|
||||
### 7.5 editor contract 正式化,但不去特殊化主链
|
||||
|
||||
Editor 的 object-id、outline、grid、overlay 依然要保留,但它们应该依附在正式 contract 上,而不是继续共享 runtime 的隐式细节。
|
||||
|
||||
---
|
||||
|
||||
## 8. 工作包拆分
|
||||
|
||||
## 工作包 A:request / frame plan 分层
|
||||
|
||||
目标:
|
||||
|
||||
把“用户想渲染什么”和“引擎这帧怎么执行”拆开。
|
||||
|
||||
建议结果:
|
||||
|
||||
1. 保留 `CameraRenderRequest` 作为外部请求描述
|
||||
2. 新增内部 `CameraFramePlan` 或等价类型,承载:
|
||||
- 实际启用的 stage
|
||||
- fullscreen chain
|
||||
- resolved target/surface
|
||||
- 阴影计划结果
|
||||
- editor 注入点
|
||||
3. `SceneRenderer` 只负责:
|
||||
- 请求收集
|
||||
- 请求排序
|
||||
- request -> frame plan 转换
|
||||
4. `CameraRenderer` 只负责:
|
||||
- frame plan 执行
|
||||
- 调 scene extraction
|
||||
- 调 pipeline / standalone pass
|
||||
|
||||
优先涉及文件:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||
2. `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h`
|
||||
3. `engine/src/Rendering/Execution/SceneRenderer.cpp`
|
||||
4. `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h`
|
||||
5. `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||
|
||||
完成标准:
|
||||
|
||||
1. request 不再混入过多 runtime-owned intermediate state
|
||||
2. frame plan 成为 camera 执行的正式输入
|
||||
3. SceneRenderer 和 CameraRenderer 的边界可以一句话说清
|
||||
|
||||
## 工作包 B:scene extraction -> culling results / renderer list
|
||||
|
||||
目标:
|
||||
|
||||
把当前 `RenderSceneExtractor` 的数组式输出,推进为后续 SRP/RenderGraph 可用的原生中间层。
|
||||
|
||||
建议结果:
|
||||
|
||||
1. 在 native 层引入等价于以下概念的类型:
|
||||
- `CullingResults`
|
||||
- `RendererListDesc`
|
||||
- `RendererList`
|
||||
- `FilteringSettings`
|
||||
- `SortingSettings`
|
||||
- `DrawSettings`
|
||||
2. `RenderSceneData` 不再承担过多“什么都往里塞”的职责
|
||||
3. `VisibleRenderItem` 继续保留为底层记录,但不再直接成为所有 pass 的唯一公共输入
|
||||
4. shadow、main scene、object-id 等路径逐步改为消费 `RendererList`
|
||||
|
||||
优先涉及文件:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h`
|
||||
2. `engine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h`
|
||||
3. `engine/src/Rendering/Extraction/RenderSceneExtractor.cpp`
|
||||
4. `engine/include/XCEngine/Rendering/Extraction/`
|
||||
5. `engine/include/XCEngine/Rendering/Builtin/`
|
||||
|
||||
完成标准:
|
||||
|
||||
1. opaque、transparent、shadow、object-id 至少能共用同一套 draw organization contract
|
||||
2. 后面新增 deferred 时,不需要再发明第二套“可见对象数组解释逻辑”
|
||||
|
||||
## 工作包 C:pipeline contract 收口
|
||||
|
||||
目标:
|
||||
|
||||
把当前 `RenderPipeline / RenderPass / RenderPipelineAsset` 收口到更像未来 SRP native kernel 的形态。
|
||||
|
||||
建议结果:
|
||||
|
||||
1. 继续保留 `RenderPipelineAsset -> RenderPipeline` 这条总方向
|
||||
2. 明确区分:
|
||||
- pipeline asset:配置与默认策略
|
||||
- pipeline runtime:本帧执行
|
||||
- standalone pass:独立功能 pass
|
||||
3. 给主场景 pipeline 引入更正式的执行输入,而不是继续只吃 `surface + sceneData`
|
||||
4. 为下一阶段 graph 化预留清晰输入面:
|
||||
- frame context
|
||||
- renderer lists
|
||||
- per-frame resources
|
||||
|
||||
优先涉及文件:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/RenderPipeline.h`
|
||||
2. `engine/include/XCEngine/Rendering/RenderPipelineAsset.h`
|
||||
3. `engine/include/XCEngine/Rendering/RenderPass.h`
|
||||
4. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
||||
|
||||
完成标准:
|
||||
|
||||
1. 主 pipeline 输入面能明显映射到未来 `Universal Renderer`
|
||||
2. standalone pass 与 main scene pipeline 的边界清楚
|
||||
|
||||
## 工作包 D:BuiltinForwardPipeline 瘦身
|
||||
|
||||
目标:
|
||||
|
||||
把 `BuiltinForwardPipeline` 从“大一统实现类”拆成可长期维护的 renderer 内部模块。
|
||||
|
||||
建议结果:
|
||||
|
||||
1. 按职责切成内部模块或 helper:
|
||||
- main scene draw
|
||||
- skybox draw
|
||||
- lighting binding
|
||||
- shadow binding
|
||||
- material binding
|
||||
- pipeline state cache
|
||||
- per-frame resource cache
|
||||
2. `GaussianSplat` 和 `Volumetric` 继续保留,但从“主 pipeline 杂糅逻辑”变成清晰的 feature-style 子模块
|
||||
3. `BuiltinForwardPipelineResources.cpp` 里和运行时编排无关的资源/绑定组织进一步下沉
|
||||
|
||||
优先涉及文件:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
||||
2. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`
|
||||
3. `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp`
|
||||
4. `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h`
|
||||
|
||||
完成标准:
|
||||
|
||||
1. `BuiltinForwardPipeline` 主类显著变薄
|
||||
2. 新增一种主场景能力时,不需要继续把所有逻辑堆回同一个 cpp
|
||||
|
||||
## 工作包 E:editor 注入点正式化
|
||||
|
||||
目标:
|
||||
|
||||
保留当前 editor 渲染能力,但把它从“依赖阶段细节”变成“依赖正式 extension contract”。
|
||||
|
||||
建议结果:
|
||||
|
||||
1. 继续保留:
|
||||
- object-id
|
||||
- outline
|
||||
- grid
|
||||
- overlay
|
||||
2. 把这些能力明确分成:
|
||||
- runtime stage
|
||||
- editor-only extension stage
|
||||
3. 收口 `RenderPassSequence` 在 request 上的使用边界,避免后续 graph 化时出现“谁都能往 request 里塞东西”的状态
|
||||
|
||||
优先涉及文件:
|
||||
|
||||
1. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||
2. `editor/src/Viewport/SceneViewportRenderPlan.h`
|
||||
3. `editor/src/Viewport/`
|
||||
4. `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||
|
||||
完成标准:
|
||||
|
||||
1. Editor 仍能完整接入当前主链
|
||||
2. 但 editor 不再直接绑死 runtime 内部实现细节
|
||||
|
||||
## 工作包 F:测试与文档同步
|
||||
|
||||
目标:
|
||||
|
||||
把第一阶段重构做成可回归、可交接的正式收口,而不是一次只靠手工验证的大重排。
|
||||
|
||||
建议结果:
|
||||
|
||||
1. 补 `tests/Rendering/unit`
|
||||
2. 保底回归现有 `tests/Rendering/integration`
|
||||
3. 必要时补 editor viewport 级验证
|
||||
4. 更新 API 文档与后续计划入口
|
||||
|
||||
完成标准:
|
||||
|
||||
1. request / extractor / pipeline 边界都有对应测试覆盖
|
||||
2. 当前 forward、shadow、object-id、editor scene view 不回退
|
||||
|
||||
---
|
||||
|
||||
## 9. 执行顺序
|
||||
|
||||
必须按下面的顺序推进:
|
||||
|
||||
1. 先做工作包 A
|
||||
2. 再做工作包 B
|
||||
3. 再做工作包 C
|
||||
4. 然后做工作包 D
|
||||
5. 接着做工作包 E
|
||||
6. 最后统一做工作包 F
|
||||
|
||||
原因:
|
||||
|
||||
1. 不先分清 request 和 frame plan,后面所有 contract 都会悬空
|
||||
2. 不先把 extraction 推进到 `renderer list` 层,pipeline 收口只是表面整理
|
||||
3. 不先把 pipeline contract 稳住,`BuiltinForwardPipeline` 的瘦身会缺少稳定目标
|
||||
4. 不先把 native 主线收紧,editor contract 很容易继续沿着旧细节扩张
|
||||
|
||||
---
|
||||
|
||||
## 10. 建议新增或调整的 native 类型
|
||||
|
||||
第一阶段不要求名字完全按这个来,但建议至少出现这些等价层:
|
||||
|
||||
1. `CameraFramePlan`
|
||||
2. `FrameExecutionContext`
|
||||
3. `CullingResults`
|
||||
4. `RendererListDesc`
|
||||
5. `RendererList`
|
||||
6. `DrawSettings`
|
||||
7. `FilteringSettings`
|
||||
8. `SortingSettings`
|
||||
9. `EditorRenderExtensions` 或等价 editor 注入描述
|
||||
|
||||
注意:
|
||||
|
||||
1. 这些类型先是 `C++ internal contract`
|
||||
2. 这一阶段不急着桥接到 `managed`
|
||||
3. 下一阶段 `Render Graph` 会直接消费其中一部分
|
||||
|
||||
---
|
||||
|
||||
## 11. 这一阶段完成的验收标准
|
||||
|
||||
当以下条件同时成立时,这一阶段才算完成:
|
||||
|
||||
1. `SceneRenderer / CameraRenderer / Planner` 三者职责边界稳定
|
||||
2. native 渲染主链内部已经形成 request、frame plan、execution 三层
|
||||
3. `RenderSceneExtractor` 不再只是输出裸数组,而是能支持正式的 draw organization
|
||||
4. `BuiltinForwardPipeline` 不再承担明显超载职责
|
||||
5. editor 的注入点已经正规化,且没有破坏当前 Scene View / Game View 能力
|
||||
6. 现有 forward、shadow、post-process、final-output、object-id、overlay 主路径回归通过
|
||||
|
||||
---
|
||||
|
||||
## 12. 下一阶段如何接 Render Graph
|
||||
|
||||
等本阶段完成后,下一阶段才进入真正的 `Render Graph`。
|
||||
|
||||
那时的接法应该是:
|
||||
|
||||
1. `CameraFramePlan` 提供本帧逻辑阶段与 feature 需求
|
||||
2. `CullingResults / RendererList` 提供 draw 输入
|
||||
3. `FrameExecutionContext` 提供本帧统一资源上下文
|
||||
4. `Render Graph` 负责:
|
||||
- pass declaration
|
||||
- resource creation/import
|
||||
- read/write dependency
|
||||
- barrier / lifetime
|
||||
- transient resource reuse
|
||||
|
||||
也就是说,`Render Graph` 不是来替代第一阶段,而是建立在第一阶段之上。
|
||||
|
||||
---
|
||||
|
||||
## 13. 一句话结论
|
||||
|
||||
当前最佳路线不是立刻补 `Deferred`,也不是立刻补 `Render Graph`,而是先把你现有这条 native rendering 主链整理成真正的 `Render Kernel v1`;这一步做实了,后面的 `Render Graph`、`Universal Renderer`、`C# SRP` 才会接得稳。
|
||||
@@ -101,18 +101,18 @@ inline SceneViewportRenderPlanBuildResult BuildSceneViewportRenderPlan(
|
||||
inline void ApplySceneViewportRenderPlan(
|
||||
const ViewportRenderTargets& targets,
|
||||
SceneViewportRenderPlan& plan,
|
||||
Rendering::CameraRenderRequest& request) {
|
||||
Rendering::CameraFramePlan& framePlan) {
|
||||
ApplySceneViewportRenderRequestSetup(
|
||||
targets,
|
||||
&plan.postScenePasses,
|
||||
request);
|
||||
framePlan);
|
||||
|
||||
if (plan.HasOverlayPasses()) {
|
||||
request.overlayPasses = &plan.overlayPasses;
|
||||
framePlan.overlayPasses = &plan.overlayPasses;
|
||||
}
|
||||
|
||||
request.hasClearColorOverride = plan.hasClearColorOverride;
|
||||
request.clearColorOverride = plan.clearColorOverride;
|
||||
framePlan.request.hasClearColorOverride = plan.hasClearColorOverride;
|
||||
framePlan.request.clearColorOverride = plan.clearColorOverride;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "ViewportHostRenderTargets.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
||||
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -142,31 +142,31 @@ inline SceneViewportSelectionOutlineStyle BuildSceneViewportSelectionOutlineStyl
|
||||
inline void ApplySceneViewportRenderRequestSetup(
|
||||
const ViewportRenderTargets& targets,
|
||||
Rendering::RenderPassSequence* postPasses,
|
||||
Rendering::CameraRenderRequest& request) {
|
||||
request.preScenePasses = nullptr;
|
||||
request.postScenePasses = nullptr;
|
||||
request.overlayPasses = nullptr;
|
||||
request.objectId = {};
|
||||
Rendering::CameraFramePlan& plan) {
|
||||
plan.preScenePasses = nullptr;
|
||||
plan.postScenePasses = nullptr;
|
||||
plan.overlayPasses = nullptr;
|
||||
plan.request.objectId = {};
|
||||
|
||||
if (postPasses != nullptr && postPasses->GetPassCount() > 0) {
|
||||
request.postScenePasses = postPasses;
|
||||
plan.postScenePasses = postPasses;
|
||||
}
|
||||
|
||||
if (targets.objectIdView == nullptr || targets.objectIdDepthView == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
request.objectId.surface = BuildViewportObjectIdSurface(targets);
|
||||
request.objectId.surface.SetRenderArea(request.surface.GetRenderArea());
|
||||
plan.request.objectId.surface = BuildViewportObjectIdSurface(targets);
|
||||
plan.request.objectId.surface.SetRenderArea(plan.request.surface.GetRenderArea());
|
||||
}
|
||||
|
||||
inline void MarkSceneViewportRenderSuccess(
|
||||
ViewportRenderTargets& targets,
|
||||
const Rendering::CameraRenderRequest& request) {
|
||||
const Rendering::CameraFramePlan& plan) {
|
||||
targets.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||
targets.objectIdState = RHI::ResourceStates::PixelShaderResource;
|
||||
targets.selectionMaskState = RHI::ResourceStates::PixelShaderResource;
|
||||
targets.hasValidObjectIdFrame = request.objectId.IsRequested();
|
||||
targets.hasValidObjectIdFrame = plan.request.objectId.IsRequested();
|
||||
}
|
||||
|
||||
inline void MarkGameViewportRenderSuccess(ViewportRenderTargets& targets) {
|
||||
|
||||
@@ -591,9 +591,9 @@ private:
|
||||
SceneViewportRenderState sceneState = {};
|
||||
BuildSceneViewportRenderState(entry, context, sceneState);
|
||||
|
||||
std::vector<Rendering::CameraRenderRequest> requests =
|
||||
m_sceneRenderer->BuildRenderRequests(*scene, m_sceneViewCamera.camera, renderContext, surface);
|
||||
if (requests.empty()) {
|
||||
std::vector<Rendering::CameraFramePlan> plans =
|
||||
m_sceneRenderer->BuildFramePlans(*scene, m_sceneViewCamera.camera, renderContext, surface);
|
||||
if (plans.empty()) {
|
||||
ApplyViewportRenderFailure(
|
||||
entry,
|
||||
renderContext,
|
||||
@@ -602,9 +602,9 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplySceneViewportRenderPlan(entry.renderTargets, sceneState.renderPlan, requests[0]);
|
||||
ApplySceneViewportRenderPlan(entry.renderTargets, sceneState.renderPlan, plans[0]);
|
||||
|
||||
if (!m_sceneRenderer->Render(requests)) {
|
||||
if (!m_sceneRenderer->Render(plans)) {
|
||||
ApplyViewportRenderFailure(
|
||||
entry,
|
||||
renderContext,
|
||||
@@ -613,7 +613,7 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
MarkSceneViewportRenderSuccess(entry.renderTargets, requests[0]);
|
||||
MarkSceneViewportRenderSuccess(entry.renderTargets, plans[0]);
|
||||
const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount();
|
||||
context.GetSceneManager().NotifySceneViewportFramePresented(pendingAsyncLoads);
|
||||
if (entry.statusText.empty()) {
|
||||
|
||||
@@ -478,11 +478,19 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassContract.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraFramePlan.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraFrameStage.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraRenderer.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/DirectionalShadowExecutionState.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/DrawSettings.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/FrameExecutionContext.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/FrameStageRenderRequest.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/ScenePhase.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/SceneRenderer.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/CullingResults.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/RenderCameraData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/RenderSceneData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/RendererListUtils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleVolumeItem.h
|
||||
@@ -500,8 +508,11 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipeline.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipelineAsset.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurface.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/RenderResourceCache.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Shadow/DirectionalShadowData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Shadow/DirectionalShadowRuntime.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h
|
||||
@@ -537,9 +548,13 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneExtractor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneUtility.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Caches/RenderResourceCache.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/CameraFramePlanBuilder.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/CameraFramePlanBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/SceneRenderRequestPlanner.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/DirectionalShadowPlanning.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Shadow/DirectionalShadowData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Shadow/DirectionalShadowRuntime.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/SceneRenderer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSkybox.cpp
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
|
||||
@@ -14,6 +14,16 @@ namespace Rendering {
|
||||
struct DirectionalShadowRenderPlan;
|
||||
struct RenderContext;
|
||||
|
||||
struct DirectionalShadowSurfaceAllocation {
|
||||
RenderSurface surface = {};
|
||||
RHI::RHIResourceView* depthShaderView = nullptr;
|
||||
|
||||
bool IsValid() const {
|
||||
return surface.GetDepthAttachment() != nullptr &&
|
||||
depthShaderView != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class DirectionalShadowSurfaceCache {
|
||||
public:
|
||||
DirectionalShadowSurfaceCache() = default;
|
||||
@@ -21,10 +31,9 @@ public:
|
||||
DirectionalShadowSurfaceCache& operator=(const DirectionalShadowSurfaceCache&) = delete;
|
||||
~DirectionalShadowSurfaceCache();
|
||||
|
||||
bool EnsureSurface(const RenderContext& context, const DirectionalShadowRenderPlan& plan);
|
||||
|
||||
const RenderSurface& GetSurface() const { return m_surface; }
|
||||
RHI::RHIResourceView* GetDepthShaderView() const { return m_depthShaderView; }
|
||||
const DirectionalShadowSurfaceAllocation* Resolve(
|
||||
const RenderContext& context,
|
||||
const DirectionalShadowRenderPlan& plan);
|
||||
|
||||
private:
|
||||
bool Matches(const RenderContext& context, const DirectionalShadowRenderPlan& plan) const;
|
||||
@@ -35,8 +44,7 @@ private:
|
||||
uint32_t m_height = 0;
|
||||
RHI::RHITexture* m_depthTexture = nullptr;
|
||||
RHI::RHIResourceView* m_depthView = nullptr;
|
||||
RHI::RHIResourceView* m_depthShaderView = nullptr;
|
||||
RenderSurface m_surface = {};
|
||||
DirectionalShadowSurfaceAllocation m_allocation = {};
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
181
engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h
Normal file
181
engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h
Normal file
@@ -0,0 +1,181 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/CameraFrameStage.h>
|
||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
struct CameraFramePlan {
|
||||
CameraRenderRequest request = {};
|
||||
ShadowCasterRenderRequest shadowCaster = {};
|
||||
DirectionalShadowRenderPlan directionalShadow = {};
|
||||
PostProcessRenderRequest postProcess = {};
|
||||
FinalOutputRenderRequest finalOutput = {};
|
||||
ResolvedFinalColorPolicy finalColorPolicy = {};
|
||||
RenderPassSequence* preScenePasses = nullptr;
|
||||
RenderPassSequence* postScenePasses = nullptr;
|
||||
RenderPassSequence* overlayPasses = nullptr;
|
||||
|
||||
static CameraFramePlan FromRequest(const CameraRenderRequest& request) {
|
||||
CameraFramePlan plan = {};
|
||||
plan.request = request;
|
||||
plan.shadowCaster = request.shadowCaster;
|
||||
plan.directionalShadow = request.directionalShadow;
|
||||
plan.postProcess = request.postProcess;
|
||||
plan.finalOutput = request.finalOutput;
|
||||
plan.finalColorPolicy = request.finalColorPolicy;
|
||||
plan.preScenePasses = request.preScenePasses;
|
||||
plan.postScenePasses = request.postScenePasses;
|
||||
plan.overlayPasses = request.overlayPasses;
|
||||
return plan;
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return request.IsValid();
|
||||
}
|
||||
|
||||
bool HasFrameStage(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return preScenePasses != nullptr;
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return shadowCaster.IsRequested() || directionalShadow.IsValid();
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return request.depthOnly.IsRequested();
|
||||
case CameraFrameStage::MainScene:
|
||||
return true;
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested();
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested();
|
||||
case CameraFrameStage::ObjectId:
|
||||
return request.objectId.IsRequested();
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return postScenePasses != nullptr;
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return overlayPasses != nullptr;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RenderPassSequence* GetPassSequence(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return preScenePasses;
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.passes;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.passes;
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return postScenePasses;
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return overlayPasses;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ScenePassRenderRequest* GetScenePassRequest(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return &shadowCaster;
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return &request.depthOnly;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ObjectIdRenderRequest* GetObjectIdRequest(CameraFrameStage stage) const {
|
||||
return stage == CameraFrameStage::ObjectId ? &request.objectId : nullptr;
|
||||
}
|
||||
|
||||
const RenderSurface& GetMainSceneSurface() const {
|
||||
if (postProcess.IsRequested()) {
|
||||
return postProcess.sourceSurface;
|
||||
}
|
||||
|
||||
if (finalOutput.IsRequested()) {
|
||||
return finalOutput.sourceSurface;
|
||||
}
|
||||
|
||||
return request.surface;
|
||||
}
|
||||
|
||||
const RenderSurface& GetFinalCompositedSurface() const {
|
||||
if (finalOutput.IsRequested()) {
|
||||
return finalOutput.destinationSurface;
|
||||
}
|
||||
|
||||
if (postProcess.IsRequested()) {
|
||||
return postProcess.destinationSurface;
|
||||
}
|
||||
|
||||
return request.surface;
|
||||
}
|
||||
|
||||
const RenderSurface* GetOutputSurface(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
case CameraFrameStage::MainScene:
|
||||
return &GetMainSceneSurface();
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return shadowCaster.IsRequested() ? &shadowCaster.surface : nullptr;
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return request.depthOnly.IsRequested() ? &request.depthOnly.surface : nullptr;
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? &postProcess.destinationSurface : nullptr;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? &finalOutput.destinationSurface : nullptr;
|
||||
case CameraFrameStage::ObjectId:
|
||||
return request.objectId.IsRequested() ? &request.objectId.surface : nullptr;
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return &GetFinalCompositedSurface();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const RenderSurface* GetSourceSurface(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? &postProcess.sourceSurface : nullptr;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? &finalOutput.sourceSurface : nullptr;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* GetSourceColorView(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? postProcess.sourceColorView : nullptr;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? finalOutput.sourceColorView : nullptr;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::ResourceStates GetSourceColorState(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? postProcess.sourceColorState : RHI::ResourceStates::Common;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? finalOutput.sourceColorState : RHI::ResourceStates::Common;
|
||||
default:
|
||||
return RHI::ResourceStates::Common;
|
||||
}
|
||||
}
|
||||
|
||||
bool RequiresIntermediateSceneColor() const {
|
||||
return postProcess.IsRequested() || finalOutput.IsRequested();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
enum class CameraFrameStage : uint8_t {
|
||||
PreScenePasses,
|
||||
ShadowCaster,
|
||||
DepthOnly,
|
||||
MainScene,
|
||||
PostProcess,
|
||||
FinalOutput,
|
||||
ObjectId,
|
||||
PostScenePasses,
|
||||
OverlayPasses
|
||||
};
|
||||
|
||||
struct CameraFrameStageInfo {
|
||||
CameraFrameStage stage = CameraFrameStage::MainScene;
|
||||
const char* name = "";
|
||||
bool runtimeStage = true;
|
||||
};
|
||||
|
||||
inline constexpr std::array<CameraFrameStageInfo, 9> kOrderedCameraFrameStages = { {
|
||||
{ CameraFrameStage::PreScenePasses, "PreScenePasses", false },
|
||||
{ CameraFrameStage::ShadowCaster, "ShadowCaster", true },
|
||||
{ CameraFrameStage::DepthOnly, "DepthOnly", true },
|
||||
{ CameraFrameStage::MainScene, "MainScene", true },
|
||||
{ CameraFrameStage::PostProcess, "PostProcess", true },
|
||||
{ CameraFrameStage::FinalOutput, "FinalOutput", true },
|
||||
{ CameraFrameStage::ObjectId, "ObjectId", false },
|
||||
{ CameraFrameStage::PostScenePasses, "PostScenePasses", false },
|
||||
{ CameraFrameStage::OverlayPasses, "OverlayPasses", false }
|
||||
} };
|
||||
|
||||
inline constexpr const char* GetCameraFrameStageName(CameraFrameStage stage) {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return "PreScenePasses";
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return "ShadowCaster";
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return "DepthOnly";
|
||||
case CameraFrameStage::MainScene:
|
||||
return "MainScene";
|
||||
case CameraFrameStage::PostProcess:
|
||||
return "PostProcess";
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return "FinalOutput";
|
||||
case CameraFrameStage::ObjectId:
|
||||
return "ObjectId";
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return "PostScenePasses";
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return "OverlayPasses";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||
#include <XCEngine/Rendering/Extraction/RenderSceneExtractor.h>
|
||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderPipeline.h>
|
||||
|
||||
@@ -19,7 +19,8 @@ class RHIResourceView;
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
class DirectionalShadowSurfaceCache;
|
||||
struct DirectionalShadowExecutionState;
|
||||
class DirectionalShadowRuntime;
|
||||
class FullscreenPassSurfaceCache;
|
||||
class RenderSurface;
|
||||
class RenderPipelineAsset;
|
||||
@@ -48,21 +49,17 @@ public:
|
||||
RenderPass* GetDepthOnlyPass() const { return m_depthOnlyPass.get(); }
|
||||
RenderPass* GetShadowCasterPass() const { return m_shadowCasterPass.get(); }
|
||||
|
||||
bool Render(const CameraRenderRequest& request);
|
||||
bool Render(const CameraFramePlan& plan);
|
||||
|
||||
private:
|
||||
void ResetPipeline(std::unique_ptr<RenderPipeline> pipeline);
|
||||
bool ResolveShadowCasterRequest(
|
||||
const CameraRenderRequest& request,
|
||||
ShadowCasterRenderRequest& outResolvedShadowCaster,
|
||||
RHI::RHIResourceView*& outShadowMapView);
|
||||
bool BuildSceneDataForRequest(
|
||||
const CameraRenderRequest& request,
|
||||
RHI::RHIResourceView* shadowMapView,
|
||||
bool BuildSceneDataForPlan(
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowExecutionState& shadowState,
|
||||
RenderSceneData& outSceneData);
|
||||
bool ExecuteRenderPlan(
|
||||
const CameraRenderRequest& request,
|
||||
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowExecutionState& shadowState,
|
||||
const RenderSceneData& sceneData);
|
||||
|
||||
RenderSceneExtractor m_sceneExtractor;
|
||||
@@ -71,7 +68,7 @@ private:
|
||||
std::unique_ptr<RenderPass> m_objectIdPass;
|
||||
std::unique_ptr<RenderPass> m_depthOnlyPass;
|
||||
std::unique_ptr<RenderPass> m_shadowCasterPass;
|
||||
std::unique_ptr<DirectionalShadowSurfaceCache> m_directionalShadowSurface;
|
||||
std::unique_ptr<DirectionalShadowRuntime> m_directionalShadowRuntime;
|
||||
std::unique_ptr<FullscreenPassSurfaceCache> m_postProcessSurfaceCache;
|
||||
std::unique_ptr<FullscreenPassSurfaceCache> m_finalOutputSurfaceCache;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/FrameStageRenderRequest.h>
|
||||
#include <XCEngine/Rendering/Shadow/DirectionalShadowData.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
struct DirectionalShadowExecutionState {
|
||||
ShadowCasterRenderRequest shadowCasterRequest = {};
|
||||
RenderDirectionalShadowData shadowData = {};
|
||||
|
||||
bool HasShadowPass() const {
|
||||
return shadowCasterRequest.IsRequested();
|
||||
}
|
||||
|
||||
bool HasShadowSampling() const {
|
||||
return shadowData.IsValid();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
15
engine/include/XCEngine/Rendering/Execution/DrawSettings.h
Normal file
15
engine/include/XCEngine/Rendering/Execution/DrawSettings.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/ScenePhase.h>
|
||||
#include <XCEngine/Rendering/FrameData/CullingResults.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
struct DrawSettings {
|
||||
ScenePhase scenePhase = ScenePhase::Opaque;
|
||||
RendererListType rendererListType = RendererListType::AllVisible;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/ScenePhase.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
class RHIResourceView;
|
||||
} // namespace RHI
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
struct RenderSceneData;
|
||||
class RenderSurface;
|
||||
|
||||
struct FrameExecutionContext {
|
||||
FrameExecutionContext(
|
||||
const RenderContext& inRenderContext,
|
||||
const RenderSurface& inSurface,
|
||||
const RenderSceneData& inSceneData,
|
||||
const RenderSurface* inSourceSurface = nullptr,
|
||||
RHI::RHIResourceView* inSourceColorView = nullptr,
|
||||
RHI::ResourceStates inSourceColorState = RHI::ResourceStates::Common)
|
||||
: renderContext(inRenderContext)
|
||||
, surface(inSurface)
|
||||
, sceneData(inSceneData)
|
||||
, sourceSurface(inSourceSurface)
|
||||
, sourceColorView(inSourceColorView)
|
||||
, sourceColorState(inSourceColorState) {
|
||||
}
|
||||
|
||||
const RenderContext& renderContext;
|
||||
const RenderSurface& surface;
|
||||
const RenderSceneData& sceneData;
|
||||
const RenderSurface* sourceSurface = nullptr;
|
||||
RHI::RHIResourceView* sourceColorView = nullptr;
|
||||
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||
};
|
||||
|
||||
struct ScenePhaseExecutionContext {
|
||||
ScenePhaseExecutionContext(
|
||||
const FrameExecutionContext& inFrameContext,
|
||||
ScenePhase inScenePhase,
|
||||
bool inSampledDirectionalShadow = false)
|
||||
: frameContext(inFrameContext)
|
||||
, scenePhase(inScenePhase)
|
||||
, sampledDirectionalShadow(inSampledDirectionalShadow) {
|
||||
}
|
||||
|
||||
FrameExecutionContext frameContext;
|
||||
ScenePhase scenePhase = ScenePhase::Opaque;
|
||||
bool sampledDirectionalShadow = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
class RHIResourceView;
|
||||
} // namespace RHI
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
struct ScenePassRenderRequest {
|
||||
RenderSurface surface;
|
||||
RenderClearFlags clearFlags = RenderClearFlags::Depth;
|
||||
bool hasClearColorOverride = false;
|
||||
Math::Color clearColorOverride = Math::Color::Black();
|
||||
bool hasCameraDataOverride = false;
|
||||
RenderCameraData cameraDataOverride = {};
|
||||
|
||||
bool IsRequested() const {
|
||||
return surface.GetDepthAttachment() != nullptr ||
|
||||
!surface.GetColorAttachments().empty();
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return surface.GetDepthAttachment() != nullptr &&
|
||||
(colorAttachments.empty() || colorAttachments[0] != nullptr) &&
|
||||
surface.GetRenderAreaWidth() > 0 &&
|
||||
surface.GetRenderAreaHeight() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
using DepthOnlyRenderRequest = ScenePassRenderRequest;
|
||||
using ShadowCasterRenderRequest = ScenePassRenderRequest;
|
||||
|
||||
inline bool HasValidColorTarget(const RenderSurface& surface) {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return !colorAttachments.empty() &&
|
||||
colorAttachments[0] != nullptr &&
|
||||
surface.GetRenderAreaWidth() > 0 &&
|
||||
surface.GetRenderAreaHeight() > 0;
|
||||
}
|
||||
|
||||
inline bool HasValidSurfaceSampleDescription(const RenderSurface& surface) {
|
||||
const uint32_t sampleCount = surface.GetSampleCount();
|
||||
const uint32_t sampleQuality = surface.GetSampleQuality();
|
||||
return sampleCount > 0u &&
|
||||
(sampleCount > 1u || sampleQuality == 0u);
|
||||
}
|
||||
|
||||
inline bool HasValidSingleSampleColorSource(const RenderSurface& surface) {
|
||||
return HasValidColorTarget(surface) &&
|
||||
HasValidSurfaceSampleDescription(surface) &&
|
||||
surface.GetSampleCount() == 1u &&
|
||||
surface.GetSampleQuality() == 0u;
|
||||
}
|
||||
|
||||
struct ObjectIdRenderRequest {
|
||||
RenderSurface surface;
|
||||
|
||||
bool IsRequested() const {
|
||||
return !surface.GetColorAttachments().empty();
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return !colorAttachments.empty() &&
|
||||
colorAttachments[0] != nullptr &&
|
||||
surface.GetDepthAttachment() != nullptr &&
|
||||
surface.GetRenderAreaWidth() > 0 &&
|
||||
surface.GetRenderAreaHeight() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct FullscreenPassRenderRequest {
|
||||
RenderSurface sourceSurface;
|
||||
RHI::RHIResourceView* sourceColorView = nullptr;
|
||||
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||
RenderSurface destinationSurface;
|
||||
RenderPassSequence* passes = nullptr;
|
||||
|
||||
size_t GetPassCount() const {
|
||||
return passes != nullptr ? passes->GetPassCount() : 0u;
|
||||
}
|
||||
|
||||
bool IsRequested() const {
|
||||
return passes != nullptr;
|
||||
}
|
||||
|
||||
bool RequiresIntermediateSurface() const {
|
||||
return GetPassCount() > 1u;
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
const bool sourceStateIsUsable =
|
||||
sourceSurface.IsAutoTransitionEnabled() ||
|
||||
sourceColorState == RHI::ResourceStates::PixelShaderResource;
|
||||
return passes != nullptr &&
|
||||
HasValidSingleSampleColorSource(sourceSurface) &&
|
||||
sourceColorView != nullptr &&
|
||||
sourceStateIsUsable &&
|
||||
HasValidColorTarget(destinationSurface) &&
|
||||
HasValidSurfaceSampleDescription(destinationSurface);
|
||||
}
|
||||
};
|
||||
|
||||
using PostProcessRenderRequest = FullscreenPassRenderRequest;
|
||||
using FinalOutputRenderRequest = FullscreenPassRenderRequest;
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
40
engine/include/XCEngine/Rendering/Execution/ScenePhase.h
Normal file
40
engine/include/XCEngine/Rendering/Execution/ScenePhase.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Types.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
enum class ScenePhase : Core::uint32 {
|
||||
Opaque = 0,
|
||||
Skybox = 1,
|
||||
Feature = 2,
|
||||
Transparent = 3,
|
||||
EditorExtension = 4,
|
||||
PostProcess = 5,
|
||||
FinalOutput = 6
|
||||
};
|
||||
|
||||
inline const char* ToString(ScenePhase scenePhase) {
|
||||
switch (scenePhase) {
|
||||
case ScenePhase::Opaque:
|
||||
return "Opaque";
|
||||
case ScenePhase::Skybox:
|
||||
return "Skybox";
|
||||
case ScenePhase::Feature:
|
||||
return "Feature";
|
||||
case ScenePhase::Transparent:
|
||||
return "Transparent";
|
||||
case ScenePhase::EditorExtension:
|
||||
return "EditorExtension";
|
||||
case ScenePhase::PostProcess:
|
||||
return "PostProcess";
|
||||
case ScenePhase::FinalOutput:
|
||||
return "FinalOutput";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||
#include <XCEngine/Rendering/Execution/CameraRenderer.h>
|
||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
||||
#include <XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h>
|
||||
@@ -15,8 +16,8 @@ class Scene;
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
class CameraFramePlanBuilder;
|
||||
class RenderPipelineAsset;
|
||||
class FullscreenPassSurfaceCache;
|
||||
|
||||
class SceneRenderer {
|
||||
public:
|
||||
@@ -30,14 +31,23 @@ public:
|
||||
RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); }
|
||||
const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); }
|
||||
|
||||
// Legacy compatibility adapters retained for callers that still consume CameraRenderRequest.
|
||||
std::vector<CameraRenderRequest> BuildRenderRequests(
|
||||
const Components::Scene& scene,
|
||||
Components::CameraComponent* overrideCamera,
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface);
|
||||
std::vector<CameraFramePlan> BuildFramePlans(
|
||||
const Components::Scene& scene,
|
||||
Components::CameraComponent* overrideCamera,
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface);
|
||||
|
||||
// Legacy compatibility adapters retained for callers that still submit CameraRenderRequest.
|
||||
bool Render(const CameraRenderRequest& request);
|
||||
bool Render(const std::vector<CameraRenderRequest>& requests);
|
||||
bool Render(const CameraFramePlan& plan);
|
||||
bool Render(const std::vector<CameraFramePlan>& plans);
|
||||
bool Render(
|
||||
const Components::Scene& scene,
|
||||
Components::CameraComponent* overrideCamera,
|
||||
@@ -45,18 +55,12 @@ public:
|
||||
const RenderSurface& surface);
|
||||
|
||||
private:
|
||||
void PrepareOwnedFullscreenStageState(size_t requestCount);
|
||||
void ResolveCameraFinalColorPolicies(
|
||||
std::vector<CameraRenderRequest>& requests) const;
|
||||
void AttachFullscreenStageRequests(
|
||||
const RenderContext& context,
|
||||
std::vector<CameraRenderRequest>& requests);
|
||||
std::vector<CameraFramePlan> CreateFramePlansFromLegacyRequests(
|
||||
const std::vector<CameraRenderRequest>& requests) const;
|
||||
|
||||
SceneRenderRequestPlanner m_requestPlanner;
|
||||
CameraRenderer m_cameraRenderer;
|
||||
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedPostProcessSequences;
|
||||
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedFinalOutputSequences;
|
||||
std::vector<std::unique_ptr<FullscreenPassSurfaceCache>> m_ownedFullscreenStageSurfaces;
|
||||
std::unique_ptr<CameraFramePlanBuilder> m_framePlanBuilder;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
85
engine/include/XCEngine/Rendering/FrameData/CullingResults.h
Normal file
85
engine/include/XCEngine/Rendering/FrameData/CullingResults.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
enum class RendererListType : Core::uint32 {
|
||||
AllVisible = 0,
|
||||
Opaque = 1,
|
||||
Transparent = 2,
|
||||
ShadowCaster = 3,
|
||||
ObjectId = 4
|
||||
};
|
||||
|
||||
enum class RendererSortMode : Core::uint32 {
|
||||
None = 0,
|
||||
FrontToBack = 1,
|
||||
BackToFront = 2
|
||||
};
|
||||
|
||||
struct FilteringSettings {
|
||||
Core::int32 renderQueueMin = std::numeric_limits<Core::int32>::lowest();
|
||||
Core::int32 renderQueueMax = std::numeric_limits<Core::int32>::max();
|
||||
bool requireShadowCasting = false;
|
||||
bool requireRenderObjectId = false;
|
||||
};
|
||||
|
||||
struct SortingSettings {
|
||||
RendererSortMode sortMode = RendererSortMode::None;
|
||||
};
|
||||
|
||||
struct RendererListDesc {
|
||||
RendererListType type = RendererListType::AllVisible;
|
||||
FilteringSettings filtering = {};
|
||||
SortingSettings sorting = {};
|
||||
};
|
||||
|
||||
struct RendererList {
|
||||
RendererListDesc desc = {};
|
||||
std::vector<Core::uint32> visibleRenderItemIndices;
|
||||
|
||||
bool Empty() const {
|
||||
return visibleRenderItemIndices.empty();
|
||||
}
|
||||
|
||||
size_t Size() const {
|
||||
return visibleRenderItemIndices.size();
|
||||
}
|
||||
};
|
||||
|
||||
struct CullingResults {
|
||||
std::vector<RendererList> rendererLists;
|
||||
|
||||
void Clear() {
|
||||
rendererLists.clear();
|
||||
}
|
||||
|
||||
RendererList* FindRendererList(RendererListType type) {
|
||||
for (RendererList& rendererList : rendererLists) {
|
||||
if (rendererList.desc.type == type) {
|
||||
return &rendererList;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const RendererList* FindRendererList(RendererListType type) const {
|
||||
for (const RendererList& rendererList : rendererLists) {
|
||||
if (rendererList.desc.type == type) {
|
||||
return &rendererList;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -3,9 +3,10 @@
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/Rendering/FrameData/CullingResults.h>
|
||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||
#include <XCEngine/Rendering/FrameData/RenderEnvironmentData.h>
|
||||
#include <XCEngine/Rendering/Shadow/DirectionalShadowSettings.h>
|
||||
#include <XCEngine/Rendering/Shadow/DirectionalShadowData.h>
|
||||
#include <XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h>
|
||||
#include <XCEngine/Rendering/FrameData/VisibleRenderItem.h>
|
||||
#include <XCEngine/Rendering/FrameData/VisibleVolumeItem.h>
|
||||
@@ -20,10 +21,6 @@ namespace Components {
|
||||
class CameraComponent;
|
||||
} // namespace Components
|
||||
|
||||
namespace RHI {
|
||||
class RHIResourceView;
|
||||
} // namespace RHI
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
struct RenderDirectionalLightData {
|
||||
@@ -52,42 +49,6 @@ struct RenderAdditionalLightData {
|
||||
float spotAngle = 0.0f;
|
||||
};
|
||||
|
||||
struct RenderDirectionalShadowMapMetrics {
|
||||
Math::Vector2 inverseMapSize = Math::Vector2::Zero();
|
||||
float worldTexelSize = 0.0f;
|
||||
float padding = 0.0f;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(RenderDirectionalShadowMapMetrics) == sizeof(float) * 4u,
|
||||
"RenderDirectionalShadowMapMetrics must stay float4-sized for GPU constant layout");
|
||||
|
||||
struct RenderDirectionalShadowSamplingData {
|
||||
float enabled = 0.0f;
|
||||
DirectionalShadowSamplingSettings settings = {};
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u,
|
||||
"RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout");
|
||||
|
||||
struct RenderDirectionalShadowCasterBiasData {
|
||||
DirectionalShadowCasterBiasSettings settings = {};
|
||||
};
|
||||
|
||||
struct RenderDirectionalShadowData {
|
||||
bool enabled = false;
|
||||
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
|
||||
RenderDirectionalShadowMapMetrics mapMetrics = {};
|
||||
RenderDirectionalShadowSamplingData sampling = {};
|
||||
RenderDirectionalShadowCasterBiasData casterBias = {};
|
||||
RHI::RHIResourceView* shadowMap = nullptr;
|
||||
|
||||
bool IsValid() const {
|
||||
return enabled && shadowMap != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderLightingData {
|
||||
static constexpr uint32_t kMaxAdditionalLightCount = 8u;
|
||||
|
||||
@@ -115,6 +76,7 @@ struct RenderSceneData {
|
||||
RenderEnvironmentData environment;
|
||||
RenderLightingData lighting;
|
||||
Resources::ShaderKeywordSet globalShaderKeywords;
|
||||
CullingResults cullingResults;
|
||||
std::vector<VisibleRenderItem> visibleItems;
|
||||
std::vector<VisibleGaussianSplatItem> visibleGaussianSplats;
|
||||
std::vector<VisibleVolumeItem> visibleVolumes;
|
||||
@@ -122,6 +84,22 @@ struct RenderSceneData {
|
||||
bool HasCamera() const {
|
||||
return camera != nullptr;
|
||||
}
|
||||
|
||||
RendererList* FindRendererList(RendererListType type) {
|
||||
return cullingResults.FindRendererList(type);
|
||||
}
|
||||
|
||||
const RendererList* FindRendererList(RendererListType type) const {
|
||||
return cullingResults.FindRendererList(type);
|
||||
}
|
||||
|
||||
const VisibleRenderItem* TryGetVisibleRenderItem(Core::uint32 index) const {
|
||||
if (index >= visibleItems.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &visibleItems[index];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
145
engine/include/XCEngine/Rendering/FrameData/RendererListUtils.h
Normal file
145
engine/include/XCEngine/Rendering/FrameData/RendererListUtils.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Rendering/FrameData/RenderSceneData.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
inline FilteringSettings BuildDefaultFilteringSettings(RendererListType type) {
|
||||
FilteringSettings filtering = {};
|
||||
|
||||
switch (type) {
|
||||
case RendererListType::AllVisible:
|
||||
break;
|
||||
case RendererListType::Opaque:
|
||||
filtering.renderQueueMax =
|
||||
static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent) - 1;
|
||||
break;
|
||||
case RendererListType::Transparent:
|
||||
filtering.renderQueueMin =
|
||||
static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent);
|
||||
break;
|
||||
case RendererListType::ShadowCaster:
|
||||
filtering.requireShadowCasting = true;
|
||||
break;
|
||||
case RendererListType::ObjectId:
|
||||
filtering.requireRenderObjectId = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return filtering;
|
||||
}
|
||||
|
||||
inline SortingSettings BuildDefaultSortingSettings(RendererListType type) {
|
||||
SortingSettings sorting = {};
|
||||
|
||||
switch (type) {
|
||||
case RendererListType::Opaque:
|
||||
case RendererListType::ShadowCaster:
|
||||
case RendererListType::ObjectId:
|
||||
sorting.sortMode = RendererSortMode::FrontToBack;
|
||||
break;
|
||||
case RendererListType::Transparent:
|
||||
sorting.sortMode = RendererSortMode::BackToFront;
|
||||
break;
|
||||
case RendererListType::AllVisible:
|
||||
sorting.sortMode = RendererSortMode::None;
|
||||
break;
|
||||
}
|
||||
|
||||
return sorting;
|
||||
}
|
||||
|
||||
inline RendererListDesc BuildDefaultRendererListDesc(RendererListType type) {
|
||||
RendererListDesc desc = {};
|
||||
desc.type = type;
|
||||
desc.filtering = BuildDefaultFilteringSettings(type);
|
||||
desc.sorting = BuildDefaultSortingSettings(type);
|
||||
return desc;
|
||||
}
|
||||
|
||||
inline bool MatchesFilteringSettings(
|
||||
const VisibleRenderItem& visibleItem,
|
||||
const FilteringSettings& filtering) {
|
||||
if (visibleItem.renderQueue < filtering.renderQueueMin ||
|
||||
visibleItem.renderQueue > filtering.renderQueueMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filtering.requireShadowCasting &&
|
||||
visibleItem.meshRenderer != nullptr &&
|
||||
!visibleItem.meshRenderer->GetCastShadows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filtering.requireRenderObjectId &&
|
||||
!IsValidRenderObjectId(visibleItem.renderObjectId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool MatchesRendererListDesc(
|
||||
const VisibleRenderItem& visibleItem,
|
||||
const RendererListDesc& desc) {
|
||||
return MatchesFilteringSettings(visibleItem, desc.filtering);
|
||||
}
|
||||
|
||||
inline RendererList BuildRendererList(
|
||||
RendererListType type,
|
||||
const std::vector<VisibleRenderItem>& visibleItems) {
|
||||
RendererList rendererList = {};
|
||||
rendererList.desc = BuildDefaultRendererListDesc(type);
|
||||
rendererList.visibleRenderItemIndices.reserve(visibleItems.size());
|
||||
|
||||
for (Core::uint32 visibleItemIndex = 0;
|
||||
visibleItemIndex < static_cast<Core::uint32>(visibleItems.size());
|
||||
++visibleItemIndex) {
|
||||
if (!MatchesRendererListDesc(
|
||||
visibleItems[visibleItemIndex],
|
||||
rendererList.desc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rendererList.visibleRenderItemIndices.push_back(visibleItemIndex);
|
||||
}
|
||||
|
||||
return rendererList;
|
||||
}
|
||||
|
||||
template <typename Visitor>
|
||||
inline void VisitRendererListVisibleItems(
|
||||
const RenderSceneData& sceneData,
|
||||
RendererListType rendererListType,
|
||||
Visitor&& visitor) {
|
||||
const RendererList* rendererList = sceneData.FindRendererList(rendererListType);
|
||||
if (rendererList != nullptr) {
|
||||
for (Core::uint32 visibleItemIndex : rendererList->visibleRenderItemIndices) {
|
||||
const VisibleRenderItem* visibleItem =
|
||||
sceneData.TryGetVisibleRenderItem(visibleItemIndex);
|
||||
if (visibleItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visitor(*visibleItem);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const RendererListDesc fallbackDesc = BuildDefaultRendererListDesc(rendererListType);
|
||||
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
|
||||
if (!MatchesRendererListDesc(visibleItem, fallbackDesc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visitor(visibleItem);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
110
engine/include/XCEngine/Rendering/Graph/RenderGraph.h
Normal file
110
engine/include/XCEngine/Rendering/Graph/RenderGraph.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Graph/RenderGraphTypes.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class RenderGraphPassBuilder;
|
||||
class RenderGraphBuilder;
|
||||
class RenderGraphCompiler;
|
||||
class RenderGraphExecutor;
|
||||
|
||||
class RenderGraph {
|
||||
public:
|
||||
void Reset();
|
||||
|
||||
size_t GetTextureCount() const {
|
||||
return m_textures.size();
|
||||
}
|
||||
|
||||
size_t GetPassCount() const {
|
||||
return m_passes.size();
|
||||
}
|
||||
|
||||
private:
|
||||
struct TextureResource {
|
||||
Containers::String name;
|
||||
RenderGraphTextureDesc desc = {};
|
||||
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||
RHI::RHIResourceView* importedView = nullptr;
|
||||
RenderGraphImportedTextureOptions importedOptions = {};
|
||||
};
|
||||
|
||||
struct TextureAccess {
|
||||
RenderGraphTextureHandle texture = {};
|
||||
RenderGraphAccessMode mode = RenderGraphAccessMode::Read;
|
||||
};
|
||||
|
||||
struct PassNode {
|
||||
Containers::String name;
|
||||
RenderGraphPassType type = RenderGraphPassType::Raster;
|
||||
std::vector<TextureAccess> accesses;
|
||||
RenderGraphExecuteCallback executeCallback = {};
|
||||
};
|
||||
|
||||
std::vector<TextureResource> m_textures;
|
||||
std::vector<PassNode> m_passes;
|
||||
|
||||
friend class RenderGraphBuilder;
|
||||
friend class RenderGraphPassBuilder;
|
||||
friend class RenderGraphCompiler;
|
||||
friend class RenderGraphExecutor;
|
||||
};
|
||||
|
||||
class RenderGraphPassBuilder {
|
||||
public:
|
||||
void ReadTexture(RenderGraphTextureHandle texture);
|
||||
void WriteTexture(RenderGraphTextureHandle texture);
|
||||
void SetExecuteCallback(RenderGraphExecuteCallback callback);
|
||||
|
||||
private:
|
||||
RenderGraphPassBuilder(RenderGraph* graph, RenderGraphPassHandle passHandle);
|
||||
|
||||
RenderGraph* m_graph = nullptr;
|
||||
RenderGraphPassHandle m_passHandle = {};
|
||||
|
||||
friend class RenderGraphBuilder;
|
||||
};
|
||||
|
||||
class RenderGraphBuilder {
|
||||
public:
|
||||
explicit RenderGraphBuilder(RenderGraph& graph)
|
||||
: m_graph(graph) {
|
||||
}
|
||||
|
||||
void Reset();
|
||||
|
||||
RenderGraphTextureHandle ImportTexture(
|
||||
const Containers::String& name,
|
||||
const RenderGraphTextureDesc& desc,
|
||||
RHI::RHIResourceView* importedView = nullptr,
|
||||
const RenderGraphImportedTextureOptions& importedOptions = {});
|
||||
|
||||
RenderGraphTextureHandle CreateTransientTexture(
|
||||
const Containers::String& name,
|
||||
const RenderGraphTextureDesc& desc);
|
||||
|
||||
RenderGraphPassHandle AddRasterPass(
|
||||
const Containers::String& name,
|
||||
const std::function<void(RenderGraphPassBuilder&)>& setup);
|
||||
|
||||
RenderGraphPassHandle AddComputePass(
|
||||
const Containers::String& name,
|
||||
const std::function<void(RenderGraphPassBuilder&)>& setup);
|
||||
|
||||
private:
|
||||
RenderGraphPassHandle AddPass(
|
||||
const Containers::String& name,
|
||||
RenderGraphPassType type,
|
||||
const std::function<void(RenderGraphPassBuilder&)>& setup);
|
||||
|
||||
RenderGraph& m_graph;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Graph/RenderGraph.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class CompiledRenderGraph {
|
||||
public:
|
||||
void Reset();
|
||||
|
||||
size_t GetPassCount() const {
|
||||
return m_passes.size();
|
||||
}
|
||||
|
||||
const Containers::String& GetPassName(size_t index) const;
|
||||
RenderGraphPassType GetPassType(size_t index) const;
|
||||
bool TryGetTextureLifetime(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureLifetime& outLifetime) const;
|
||||
bool TryGetImportedTextureOptions(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphImportedTextureOptions& outOptions) const;
|
||||
bool TryGetTextureTransitionPlan(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureTransitionPlan& outPlan) const;
|
||||
|
||||
private:
|
||||
struct CompiledTextureAccess {
|
||||
RenderGraphTextureHandle texture = {};
|
||||
RenderGraphAccessMode mode = RenderGraphAccessMode::Read;
|
||||
RHI::ResourceStates requiredState = RHI::ResourceStates::Common;
|
||||
};
|
||||
|
||||
struct CompiledPass {
|
||||
Containers::String name;
|
||||
RenderGraphPassType type = RenderGraphPassType::Raster;
|
||||
Core::uint32 originalPassIndex = kInvalidRenderGraphHandle;
|
||||
std::vector<CompiledTextureAccess> accesses;
|
||||
RenderGraphExecuteCallback executeCallback = {};
|
||||
};
|
||||
|
||||
struct CompiledTexture {
|
||||
Containers::String name;
|
||||
RenderGraphTextureDesc desc = {};
|
||||
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||
RHI::RHIResourceView* importedView = nullptr;
|
||||
RenderGraphImportedTextureOptions importedOptions = {};
|
||||
};
|
||||
|
||||
std::vector<CompiledPass> m_passes;
|
||||
std::vector<CompiledTexture> m_textures;
|
||||
std::vector<RenderGraphTextureLifetime> m_textureLifetimes;
|
||||
std::vector<RenderGraphTextureTransitionPlan> m_textureTransitionPlans;
|
||||
|
||||
friend class RenderGraphCompiler;
|
||||
friend class RenderGraphExecutor;
|
||||
friend class RenderGraphRuntimeResources;
|
||||
};
|
||||
|
||||
class RenderGraphCompiler {
|
||||
public:
|
||||
static bool Compile(
|
||||
const RenderGraph& graph,
|
||||
CompiledRenderGraph& outCompiledGraph,
|
||||
Containers::String* outErrorMessage = nullptr);
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class RenderGraphExecutor {
|
||||
public:
|
||||
static bool Execute(
|
||||
const CompiledRenderGraph& graph,
|
||||
const RenderContext& renderContext,
|
||||
Containers::String* outErrorMessage = nullptr);
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
114
engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h
Normal file
114
engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h
Normal file
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
class RHIResourceView;
|
||||
class RHITexture;
|
||||
} // namespace RHI
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
class RenderGraphRuntimeResources;
|
||||
|
||||
constexpr Core::uint32 kInvalidRenderGraphHandle = std::numeric_limits<Core::uint32>::max();
|
||||
|
||||
enum class RenderGraphPassType : Core::uint8 {
|
||||
Raster = 0,
|
||||
Compute = 1
|
||||
};
|
||||
|
||||
enum class RenderGraphTextureKind : Core::uint8 {
|
||||
Imported = 0,
|
||||
Transient = 1
|
||||
};
|
||||
|
||||
enum class RenderGraphAccessMode : Core::uint8 {
|
||||
Read = 0,
|
||||
Write = 1
|
||||
};
|
||||
|
||||
enum class RenderGraphTextureViewType : Core::uint8 {
|
||||
RenderTarget = 0,
|
||||
ShaderResource = 1
|
||||
};
|
||||
|
||||
struct RenderGraphTextureHandle {
|
||||
Core::uint32 index = kInvalidRenderGraphHandle;
|
||||
|
||||
bool IsValid() const {
|
||||
return index != kInvalidRenderGraphHandle;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderGraphPassHandle {
|
||||
Core::uint32 index = kInvalidRenderGraphHandle;
|
||||
|
||||
bool IsValid() const {
|
||||
return index != kInvalidRenderGraphHandle;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderGraphTextureDesc {
|
||||
Core::uint32 width = 0u;
|
||||
Core::uint32 height = 0u;
|
||||
Core::uint32 format = static_cast<Core::uint32>(RHI::Format::Unknown);
|
||||
Core::uint32 textureType = static_cast<Core::uint32>(RHI::TextureType::Texture2D);
|
||||
Core::uint32 sampleCount = 1u;
|
||||
Core::uint32 sampleQuality = 0u;
|
||||
|
||||
bool IsValid() const {
|
||||
return width > 0u &&
|
||||
height > 0u &&
|
||||
format != static_cast<Core::uint32>(RHI::Format::Unknown) &&
|
||||
sampleCount > 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderGraphTextureLifetime {
|
||||
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||
bool used = false;
|
||||
Core::uint32 firstPassIndex = kInvalidRenderGraphHandle;
|
||||
Core::uint32 lastPassIndex = kInvalidRenderGraphHandle;
|
||||
};
|
||||
|
||||
struct RenderGraphImportedTextureOptions {
|
||||
RHI::ResourceStates initialState = RHI::ResourceStates::Common;
|
||||
RHI::ResourceStates finalState = RHI::ResourceStates::Common;
|
||||
bool graphOwnsTransitions = false;
|
||||
};
|
||||
|
||||
struct RenderGraphTextureTransitionPlan {
|
||||
bool graphOwnsTransitions = false;
|
||||
bool hasFirstAccessState = false;
|
||||
bool hasLastAccessState = false;
|
||||
RHI::ResourceStates initialState = RHI::ResourceStates::Common;
|
||||
RHI::ResourceStates firstAccessState = RHI::ResourceStates::Common;
|
||||
RHI::ResourceStates lastAccessState = RHI::ResourceStates::Common;
|
||||
RHI::ResourceStates finalState = RHI::ResourceStates::Common;
|
||||
};
|
||||
|
||||
struct RenderGraphExecutionContext {
|
||||
const RenderContext& renderContext;
|
||||
const RenderGraphRuntimeResources* runtimeResources = nullptr;
|
||||
|
||||
RHI::RHIResourceView* ResolveTextureView(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureViewType viewType) const;
|
||||
bool TryGetTextureDesc(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureDesc& outDesc) const;
|
||||
bool IsTransientTexture(RenderGraphTextureHandle handle) const;
|
||||
};
|
||||
|
||||
using RenderGraphExecuteCallback = std::function<void(const RenderGraphExecutionContext&)>;
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Math/Matrix4.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
||||
#include <XCEngine/Rendering/FrameData/CullingResults.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
@@ -46,6 +47,7 @@ protected:
|
||||
BuiltinMaterialPass passType,
|
||||
Containers::String builtinShaderPath);
|
||||
|
||||
virtual RendererListType GetRendererListType() const;
|
||||
virtual bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/SceneRenderFeaturePass.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||
@@ -40,12 +40,16 @@ class BuiltinGaussianSplatPassResources;
|
||||
|
||||
namespace Passes {
|
||||
|
||||
class BuiltinGaussianSplatPass final : public RenderPass {
|
||||
class BuiltinGaussianSplatPass final : public SceneRenderFeaturePass {
|
||||
public:
|
||||
~BuiltinGaussianSplatPass() override;
|
||||
|
||||
const char* GetName() const override;
|
||||
bool Initialize(const RenderContext& context) override;
|
||||
bool IsActive(const RenderSceneData& sceneData) const override;
|
||||
bool Prepare(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) override;
|
||||
bool PrepareGaussianSplatResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData);
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
const char* GetName() const override;
|
||||
|
||||
protected:
|
||||
RendererListType GetRendererListType() const override;
|
||||
bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/SceneRenderFeaturePass.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||
@@ -33,7 +33,7 @@ struct RenderLightingData;
|
||||
|
||||
namespace Passes {
|
||||
|
||||
class BuiltinVolumetricPass final : public RenderPass {
|
||||
class BuiltinVolumetricPass final : public SceneRenderFeaturePass {
|
||||
public:
|
||||
~BuiltinVolumetricPass() override;
|
||||
|
||||
@@ -41,6 +41,10 @@ public:
|
||||
|
||||
const char* GetName() const override;
|
||||
bool Initialize(const RenderContext& context) override;
|
||||
bool IsActive(const RenderSceneData& sceneData) const override;
|
||||
bool Prepare(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) override;
|
||||
bool PrepareVolumeResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData);
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/DrawSettings.h>
|
||||
#include <XCEngine/Rendering/Execution/FrameExecutionContext.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderPipeline.h>
|
||||
#include <XCEngine/Rendering/RenderPipelineAsset.h>
|
||||
#include <XCEngine/Rendering/SceneRenderFeaturePass.h>
|
||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
@@ -50,16 +53,21 @@ public:
|
||||
BuiltinForwardPipeline();
|
||||
~BuiltinForwardPipeline() override;
|
||||
|
||||
using RenderPipeline::Render;
|
||||
|
||||
static RHI::InputLayoutDesc BuildInputLayout();
|
||||
|
||||
bool Initialize(const RenderContext& context) override;
|
||||
void Shutdown() override;
|
||||
bool Render(const FrameExecutionContext& executionContext) override;
|
||||
bool Render(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData) override;
|
||||
|
||||
private:
|
||||
using ForwardSceneFeaturePassArray = std::array<SceneRenderFeaturePass*, 2>;
|
||||
|
||||
struct OwnedDescriptorSet {
|
||||
RHI::RHIDescriptorPool* pool = nullptr;
|
||||
RHI::RHIDescriptorSet* set = nullptr;
|
||||
@@ -263,6 +271,19 @@ private:
|
||||
};
|
||||
|
||||
bool EnsureInitialized(const RenderContext& context);
|
||||
ForwardSceneFeaturePassArray CollectForwardSceneFeaturePasses() const;
|
||||
bool InitializeForwardSceneFeaturePasses(const RenderContext& context);
|
||||
void ShutdownForwardSceneFeaturePasses();
|
||||
bool PrepareForwardSceneFeaturePasses(
|
||||
const FrameExecutionContext& executionContext) const;
|
||||
bool ExecuteForwardSceneFeaturePasses(
|
||||
const ScenePhaseExecutionContext& executionContext) const;
|
||||
ScenePhaseExecutionContext BuildScenePhaseExecutionContext(
|
||||
const FrameExecutionContext& executionContext,
|
||||
ScenePhase scenePhase) const;
|
||||
DrawSettings BuildDrawSettings(ScenePhase scenePhase) const;
|
||||
bool ExecuteForwardScene(const FrameExecutionContext& executionContext);
|
||||
bool ExecuteScenePhase(const ScenePhaseExecutionContext& executionContext);
|
||||
bool CreatePipelineResources(const RenderContext& context);
|
||||
void DestroyPipelineResources();
|
||||
static bool TryResolveSurfacePassType(
|
||||
@@ -315,18 +336,14 @@ private:
|
||||
bool HasProceduralSkybox(const RenderSceneData& sceneData) const;
|
||||
bool BeginForwardScenePass(const RenderPassContext& context);
|
||||
void EndForwardScenePass(const RenderPassContext& context);
|
||||
bool ExecuteForwardOpaquePass(const RenderPassContext& context);
|
||||
bool ExecuteForwardOpaquePass(const ScenePhaseExecutionContext& context);
|
||||
bool ExecuteForwardSkyboxPass(const RenderPassContext& context);
|
||||
bool ExecuteForwardTransparentPass(const RenderPassContext& context);
|
||||
bool ExecuteForwardTransparentPass(const ScenePhaseExecutionContext& context);
|
||||
bool DrawVisibleItems(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
bool drawTransparentItems);
|
||||
const FrameExecutionContext& executionContext,
|
||||
const DrawSettings& drawSettings);
|
||||
bool DrawVisibleItem(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const FrameExecutionContext& executionContext,
|
||||
const VisibleRenderItem& visibleItem);
|
||||
bool EnsureSkyboxResources(const RenderContext& context);
|
||||
bool CreateSkyboxResources(const RenderContext& context);
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||
#include <XCEngine/Rendering/Execution/FrameStageRenderRequest.h>
|
||||
#include <XCEngine/Rendering/Planning/FinalColorSettings.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/Rendering/Shadow/DirectionalShadowSettings.h>
|
||||
#include <XCEngine/Rendering/Shadow/DirectionalShadowData.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -16,189 +13,8 @@ class CameraComponent;
|
||||
class Scene;
|
||||
} // namespace Components
|
||||
|
||||
namespace RHI {
|
||||
class RHIResourceView;
|
||||
} // namespace RHI
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
enum class CameraFrameStage : uint8_t {
|
||||
PreScenePasses,
|
||||
ShadowCaster,
|
||||
DepthOnly,
|
||||
MainScene,
|
||||
PostProcess,
|
||||
FinalOutput,
|
||||
ObjectId,
|
||||
PostScenePasses,
|
||||
OverlayPasses
|
||||
};
|
||||
|
||||
struct CameraFrameStageInfo {
|
||||
CameraFrameStage stage = CameraFrameStage::MainScene;
|
||||
const char* name = "";
|
||||
bool runtimeStage = true;
|
||||
};
|
||||
|
||||
inline constexpr std::array<CameraFrameStageInfo, 9> kOrderedCameraFrameStages = {{
|
||||
{ CameraFrameStage::PreScenePasses, "PreScenePasses", false },
|
||||
{ CameraFrameStage::ShadowCaster, "ShadowCaster", true },
|
||||
{ CameraFrameStage::DepthOnly, "DepthOnly", true },
|
||||
{ CameraFrameStage::MainScene, "MainScene", true },
|
||||
{ CameraFrameStage::PostProcess, "PostProcess", true },
|
||||
{ CameraFrameStage::FinalOutput, "FinalOutput", true },
|
||||
{ CameraFrameStage::ObjectId, "ObjectId", false },
|
||||
{ CameraFrameStage::PostScenePasses, "PostScenePasses", false },
|
||||
{ CameraFrameStage::OverlayPasses, "OverlayPasses", false }
|
||||
}};
|
||||
|
||||
inline constexpr const char* GetCameraFrameStageName(CameraFrameStage stage) {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return "PreScenePasses";
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return "ShadowCaster";
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return "DepthOnly";
|
||||
case CameraFrameStage::MainScene:
|
||||
return "MainScene";
|
||||
case CameraFrameStage::PostProcess:
|
||||
return "PostProcess";
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return "FinalOutput";
|
||||
case CameraFrameStage::ObjectId:
|
||||
return "ObjectId";
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return "PostScenePasses";
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return "OverlayPasses";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct ScenePassRenderRequest {
|
||||
RenderSurface surface;
|
||||
RenderClearFlags clearFlags = RenderClearFlags::Depth;
|
||||
bool hasClearColorOverride = false;
|
||||
Math::Color clearColorOverride = Math::Color::Black();
|
||||
bool hasCameraDataOverride = false;
|
||||
RenderCameraData cameraDataOverride = {};
|
||||
|
||||
bool IsRequested() const {
|
||||
return surface.GetDepthAttachment() != nullptr ||
|
||||
!surface.GetColorAttachments().empty();
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return surface.GetDepthAttachment() != nullptr &&
|
||||
(colorAttachments.empty() || colorAttachments[0] != nullptr) &&
|
||||
surface.GetRenderAreaWidth() > 0 &&
|
||||
surface.GetRenderAreaHeight() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
using DepthOnlyRenderRequest = ScenePassRenderRequest;
|
||||
using ShadowCasterRenderRequest = ScenePassRenderRequest;
|
||||
|
||||
inline bool HasValidColorTarget(const RenderSurface& surface) {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return !colorAttachments.empty() &&
|
||||
colorAttachments[0] != nullptr &&
|
||||
surface.GetRenderAreaWidth() > 0 &&
|
||||
surface.GetRenderAreaHeight() > 0;
|
||||
}
|
||||
|
||||
inline bool HasValidSurfaceSampleDescription(const RenderSurface& surface) {
|
||||
const uint32_t sampleCount = surface.GetSampleCount();
|
||||
const uint32_t sampleQuality = surface.GetSampleQuality();
|
||||
return sampleCount > 0u &&
|
||||
(sampleCount > 1u || sampleQuality == 0u);
|
||||
}
|
||||
|
||||
inline bool HasValidSingleSampleColorSource(const RenderSurface& surface) {
|
||||
return HasValidColorTarget(surface) &&
|
||||
HasValidSurfaceSampleDescription(surface) &&
|
||||
surface.GetSampleCount() == 1u &&
|
||||
surface.GetSampleQuality() == 0u;
|
||||
}
|
||||
|
||||
struct DirectionalShadowRenderPlan {
|
||||
bool enabled = false;
|
||||
Math::Vector3 lightDirection = Math::Vector3::Back();
|
||||
Math::Vector3 focusPoint = Math::Vector3::Zero();
|
||||
float orthographicHalfExtent = 0.0f;
|
||||
float texelWorldSize = 0.0f;
|
||||
float nearClipPlane = 0.1f;
|
||||
float farClipPlane = 0.0f;
|
||||
DirectionalShadowSamplingSettings sampling = {};
|
||||
DirectionalShadowCasterBiasSettings casterBias = {};
|
||||
uint32_t mapWidth = 0;
|
||||
uint32_t mapHeight = 0;
|
||||
RenderCameraData cameraData = {};
|
||||
|
||||
bool IsValid() const {
|
||||
return enabled &&
|
||||
mapWidth > 0 &&
|
||||
mapHeight > 0 &&
|
||||
cameraData.viewportWidth == mapWidth &&
|
||||
cameraData.viewportHeight == mapHeight;
|
||||
}
|
||||
};
|
||||
|
||||
struct ObjectIdRenderRequest {
|
||||
RenderSurface surface;
|
||||
|
||||
bool IsRequested() const {
|
||||
return !surface.GetColorAttachments().empty();
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return !colorAttachments.empty() &&
|
||||
colorAttachments[0] != nullptr &&
|
||||
surface.GetDepthAttachment() != nullptr &&
|
||||
surface.GetRenderAreaWidth() > 0 &&
|
||||
surface.GetRenderAreaHeight() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct FullscreenPassRenderRequest {
|
||||
RenderSurface sourceSurface;
|
||||
RHI::RHIResourceView* sourceColorView = nullptr;
|
||||
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||
RenderSurface destinationSurface;
|
||||
RenderPassSequence* passes = nullptr;
|
||||
|
||||
size_t GetPassCount() const {
|
||||
return passes != nullptr ? passes->GetPassCount() : 0u;
|
||||
}
|
||||
|
||||
bool IsRequested() const {
|
||||
return passes != nullptr;
|
||||
}
|
||||
|
||||
bool RequiresIntermediateSurface() const {
|
||||
return GetPassCount() > 1u;
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
const bool sourceStateIsUsable =
|
||||
sourceSurface.IsAutoTransitionEnabled() ||
|
||||
sourceColorState == RHI::ResourceStates::PixelShaderResource;
|
||||
return passes != nullptr &&
|
||||
HasValidSingleSampleColorSource(sourceSurface) &&
|
||||
sourceColorView != nullptr &&
|
||||
sourceStateIsUsable &&
|
||||
HasValidColorTarget(destinationSurface) &&
|
||||
HasValidSurfaceSampleDescription(destinationSurface);
|
||||
}
|
||||
};
|
||||
|
||||
using PostProcessRenderRequest = FullscreenPassRenderRequest;
|
||||
using FinalOutputRenderRequest = FullscreenPassRenderRequest;
|
||||
|
||||
struct CameraRenderRequest {
|
||||
const Components::Scene* scene = nullptr;
|
||||
Components::CameraComponent* camera = nullptr;
|
||||
@@ -220,147 +36,6 @@ struct CameraRenderRequest {
|
||||
RenderPassSequence* postScenePasses = nullptr;
|
||||
RenderPassSequence* overlayPasses = nullptr;
|
||||
|
||||
bool HasFrameStage(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return preScenePasses != nullptr;
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return shadowCaster.IsRequested() || directionalShadow.IsValid();
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return depthOnly.IsRequested();
|
||||
case CameraFrameStage::MainScene:
|
||||
return true;
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested();
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested();
|
||||
case CameraFrameStage::ObjectId:
|
||||
return objectId.IsRequested();
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return postScenePasses != nullptr;
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return overlayPasses != nullptr;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RenderPassSequence* GetPassSequence(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return preScenePasses;
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.passes;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.passes;
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return postScenePasses;
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return overlayPasses;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ScenePassRenderRequest* GetScenePassRequest(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return &shadowCaster;
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return &depthOnly;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const ObjectIdRenderRequest* GetObjectIdRequest(CameraFrameStage stage) const {
|
||||
return stage == CameraFrameStage::ObjectId ? &objectId : nullptr;
|
||||
}
|
||||
|
||||
const RenderSurface& GetMainSceneSurface() const {
|
||||
if (postProcess.IsRequested()) {
|
||||
return postProcess.sourceSurface;
|
||||
}
|
||||
|
||||
if (finalOutput.IsRequested()) {
|
||||
return finalOutput.sourceSurface;
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
const RenderSurface& GetFinalCompositedSurface() const {
|
||||
if (finalOutput.IsRequested()) {
|
||||
return finalOutput.destinationSurface;
|
||||
}
|
||||
|
||||
if (postProcess.IsRequested()) {
|
||||
return postProcess.destinationSurface;
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
const RenderSurface* GetOutputSurface(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
case CameraFrameStage::MainScene:
|
||||
return &GetMainSceneSurface();
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return shadowCaster.IsRequested() ? &shadowCaster.surface : nullptr;
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return depthOnly.IsRequested() ? &depthOnly.surface : nullptr;
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? &postProcess.destinationSurface : nullptr;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? &finalOutput.destinationSurface : nullptr;
|
||||
case CameraFrameStage::ObjectId:
|
||||
return objectId.IsRequested() ? &objectId.surface : nullptr;
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return &GetFinalCompositedSurface();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const RenderSurface* GetSourceSurface(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? &postProcess.sourceSurface : nullptr;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? &finalOutput.sourceSurface : nullptr;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* GetSourceColorView(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? postProcess.sourceColorView : nullptr;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? finalOutput.sourceColorView : nullptr;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::ResourceStates GetSourceColorState(CameraFrameStage stage) const {
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PostProcess:
|
||||
return postProcess.IsRequested() ? postProcess.sourceColorState : RHI::ResourceStates::Common;
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return finalOutput.IsRequested() ? finalOutput.sourceColorState : RHI::ResourceStates::Common;
|
||||
default:
|
||||
return RHI::ResourceStates::Common;
|
||||
}
|
||||
}
|
||||
|
||||
bool RequiresIntermediateSceneColor() const {
|
||||
return postProcess.IsRequested() || finalOutput.IsRequested();
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return scene != nullptr &&
|
||||
camera != nullptr &&
|
||||
|
||||
@@ -24,7 +24,7 @@ struct DirectionalShadowPlanningSettings {
|
||||
float perspectiveFocusFactor = 1.0f;
|
||||
float orthographicFocusFactor = 2.0f;
|
||||
float minDepthRange = 20.0f;
|
||||
float boundsPadding = 1.0f;
|
||||
float boundsPadding = 0.5f;
|
||||
float minDepthPadding = 2.0f;
|
||||
DirectionalShadowSamplingSettings sampling = {};
|
||||
DirectionalShadowCasterBiasSettings casterBias = {};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/FrameExecutionContext.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -25,6 +26,23 @@ struct RenderPassContext {
|
||||
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||
};
|
||||
|
||||
inline RenderPassContext BuildRenderPassContext(
|
||||
const FrameExecutionContext& executionContext) {
|
||||
return {
|
||||
executionContext.renderContext,
|
||||
executionContext.surface,
|
||||
executionContext.sceneData,
|
||||
executionContext.sourceSurface,
|
||||
executionContext.sourceColorView,
|
||||
executionContext.sourceColorState
|
||||
};
|
||||
}
|
||||
|
||||
inline RenderPassContext BuildRenderPassContext(
|
||||
const ScenePhaseExecutionContext& executionContext) {
|
||||
return BuildRenderPassContext(executionContext.frameContext);
|
||||
}
|
||||
|
||||
class RenderPass {
|
||||
public:
|
||||
virtual ~RenderPass() = default;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/FrameExecutionContext.h>
|
||||
#include <XCEngine/Rendering/FrameData/RenderSceneData.h>
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
|
||||
@@ -14,6 +15,12 @@ public:
|
||||
|
||||
virtual bool Initialize(const RenderContext& context) = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual bool Render(const FrameExecutionContext& executionContext) {
|
||||
return Render(
|
||||
executionContext.renderContext,
|
||||
executionContext.surface,
|
||||
executionContext.sceneData);
|
||||
}
|
||||
virtual bool Render(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
|
||||
24
engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h
Normal file
24
engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class SceneRenderFeaturePass : public RenderPass {
|
||||
public:
|
||||
~SceneRenderFeaturePass() override = default;
|
||||
|
||||
virtual bool IsActive(const RenderSceneData& sceneData) const = 0;
|
||||
|
||||
virtual bool Prepare(
|
||||
const RenderContext& renderContext,
|
||||
const RenderSceneData& sceneData) {
|
||||
(void)renderContext;
|
||||
(void)sceneData;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Math/Matrix4.h>
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||
#include <XCEngine/Rendering/Shadow/DirectionalShadowSettings.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
class RHIResourceView;
|
||||
} // namespace RHI
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
struct DirectionalShadowRenderPlan {
|
||||
bool enabled = false;
|
||||
Math::Vector3 lightDirection = Math::Vector3::Back();
|
||||
Math::Vector3 focusPoint = Math::Vector3::Zero();
|
||||
float orthographicHalfExtent = 0.0f;
|
||||
float texelWorldSize = 0.0f;
|
||||
float nearClipPlane = 0.1f;
|
||||
float farClipPlane = 0.0f;
|
||||
DirectionalShadowSamplingSettings sampling = {};
|
||||
DirectionalShadowCasterBiasSettings casterBias = {};
|
||||
uint32_t mapWidth = 0;
|
||||
uint32_t mapHeight = 0;
|
||||
RenderCameraData cameraData = {};
|
||||
|
||||
bool IsValid() const {
|
||||
return enabled &&
|
||||
mapWidth > 0 &&
|
||||
mapHeight > 0 &&
|
||||
cameraData.viewportWidth == mapWidth &&
|
||||
cameraData.viewportHeight == mapHeight;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderDirectionalShadowMapMetrics {
|
||||
Math::Vector2 inverseMapSize = Math::Vector2::Zero();
|
||||
float worldTexelSize = 0.0f;
|
||||
float padding = 0.0f;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(RenderDirectionalShadowMapMetrics) == sizeof(float) * 4u,
|
||||
"RenderDirectionalShadowMapMetrics must stay float4-sized for GPU constant layout");
|
||||
|
||||
struct RenderDirectionalShadowSamplingData {
|
||||
float enabled = 0.0f;
|
||||
DirectionalShadowSamplingSettings settings = {};
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u,
|
||||
"RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout");
|
||||
|
||||
struct RenderDirectionalShadowCasterBiasData {
|
||||
DirectionalShadowCasterBiasSettings settings = {};
|
||||
};
|
||||
|
||||
struct RenderDirectionalShadowData {
|
||||
bool enabled = false;
|
||||
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
|
||||
RenderDirectionalShadowMapMetrics mapMetrics = {};
|
||||
RenderDirectionalShadowSamplingData sampling = {};
|
||||
RenderDirectionalShadowCasterBiasData casterBias = {};
|
||||
RHI::RHIResourceView* shadowMap = nullptr;
|
||||
|
||||
bool IsValid() const {
|
||||
return enabled && shadowMap != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
RenderDirectionalShadowData BuildRenderDirectionalShadowData(
|
||||
const DirectionalShadowRenderPlan& plan,
|
||||
RHI::RHIResourceView* shadowMapView);
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.h>
|
||||
#include <XCEngine/Rendering/Execution/DirectionalShadowExecutionState.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
struct CameraFramePlan;
|
||||
|
||||
class DirectionalShadowRuntime {
|
||||
public:
|
||||
DirectionalShadowRuntime() = default;
|
||||
DirectionalShadowRuntime(const DirectionalShadowRuntime&) = delete;
|
||||
DirectionalShadowRuntime& operator=(const DirectionalShadowRuntime&) = delete;
|
||||
~DirectionalShadowRuntime() = default;
|
||||
|
||||
bool ResolveExecutionState(
|
||||
const CameraFramePlan& plan,
|
||||
DirectionalShadowExecutionState& outShadowState);
|
||||
|
||||
private:
|
||||
DirectionalShadowSurfaceCache m_surfaceCache;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -49,6 +49,25 @@ inline float ClampFiniteExtent(float value, float minValue, float maxValue) {
|
||||
return (std::clamp)(value, minValue, maxValue);
|
||||
}
|
||||
|
||||
inline float ResolveOverflowPrimaryExtentFromMinimums(
|
||||
float usableExtent,
|
||||
float preferredPrimaryMinimum,
|
||||
float preferredSecondaryMinimum) {
|
||||
const float clampedUsableExtent = ClampSplitterExtent(usableExtent);
|
||||
const float primaryMinimum = ClampSplitterExtent(preferredPrimaryMinimum);
|
||||
const float secondaryMinimum = ClampSplitterExtent(preferredSecondaryMinimum);
|
||||
const float totalMinimum = primaryMinimum + secondaryMinimum;
|
||||
if (clampedUsableExtent <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (totalMinimum <= 0.0f) {
|
||||
return clampedUsableExtent * 0.5f;
|
||||
}
|
||||
|
||||
return clampedUsableExtent * (primaryMinimum / totalMinimum);
|
||||
}
|
||||
|
||||
inline float GetMainExtent(const UISize& size, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? size.width : size.height;
|
||||
}
|
||||
@@ -108,8 +127,12 @@ inline float ClampSplitterRatio(
|
||||
float minimumPrimaryExtent = minPrimaryExtent;
|
||||
float maximumPrimaryExtent = usableExtent - minSecondaryExtent;
|
||||
if (minimumPrimaryExtent > maximumPrimaryExtent) {
|
||||
minimumPrimaryExtent = 0.0f;
|
||||
maximumPrimaryExtent = usableExtent;
|
||||
const float overflowPrimaryExtent =
|
||||
SplitterDetail::ResolveOverflowPrimaryExtentFromMinimums(
|
||||
usableExtent,
|
||||
options.minPrimaryExtent,
|
||||
options.minSecondaryExtent);
|
||||
return usableExtent <= 0.0f ? 0.5f : overflowPrimaryExtent / usableExtent;
|
||||
}
|
||||
|
||||
const float clampedPrimaryExtent = (std::clamp)(
|
||||
@@ -161,7 +184,20 @@ inline float ClampSplitterRatio(
|
||||
|
||||
minimumPrimaryExtent = (std::max)(minimumPrimaryExtent, minimumFromSecondary);
|
||||
maximumPrimaryExtent = (std::min)(maximumPrimaryExtent, maximumFromSecondary);
|
||||
const bool minimumsOverflow =
|
||||
SplitterDetail::ClampSplitterExtent(constraints.primaryMin) +
|
||||
SplitterDetail::ClampSplitterExtent(constraints.secondaryMin) >
|
||||
usableExtent;
|
||||
if (minimumPrimaryExtent > maximumPrimaryExtent) {
|
||||
if (minimumsOverflow) {
|
||||
const float overflowPrimaryExtent =
|
||||
SplitterDetail::ResolveOverflowPrimaryExtentFromMinimums(
|
||||
usableExtent,
|
||||
constraints.primaryMin,
|
||||
constraints.secondaryMin);
|
||||
return usableExtent <= 0.0f ? 0.5f : overflowPrimaryExtent / usableExtent;
|
||||
}
|
||||
|
||||
minimumPrimaryExtent = 0.0f;
|
||||
maximumPrimaryExtent = usableExtent;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Rendering/Caches/DirectionalShadowSurfaceCache.h"
|
||||
|
||||
#include "Rendering/Planning/CameraRenderRequest.h"
|
||||
#include "Rendering/RenderContext.h"
|
||||
#include "Rendering/Shadow/DirectionalShadowData.h"
|
||||
#include "RHI/RHIDevice.h"
|
||||
#include "RHI/RHIResourceView.h"
|
||||
#include "RHI/RHITexture.h"
|
||||
@@ -18,15 +19,15 @@ DirectionalShadowSurfaceCache::~DirectionalShadowSurfaceCache() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool DirectionalShadowSurfaceCache::EnsureSurface(
|
||||
const DirectionalShadowSurfaceAllocation* DirectionalShadowSurfaceCache::Resolve(
|
||||
const RenderContext& context,
|
||||
const DirectionalShadowRenderPlan& plan) {
|
||||
if (!context.IsValid() || !plan.IsValid()) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (Matches(context, plan)) {
|
||||
return true;
|
||||
return &m_allocation;
|
||||
}
|
||||
|
||||
RHI::TextureDesc depthDesc = {};
|
||||
@@ -43,7 +44,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
||||
|
||||
RHI::RHITexture* depthTexture = context.device->CreateTexture(depthDesc);
|
||||
if (depthTexture == nullptr) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RHI::ResourceViewDesc depthViewDesc = {};
|
||||
@@ -55,7 +56,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
||||
if (depthView == nullptr) {
|
||||
depthTexture->Shutdown();
|
||||
delete depthTexture;
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RHI::ResourceViewDesc depthShaderViewDesc = {};
|
||||
@@ -68,7 +69,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
||||
delete depthView;
|
||||
depthTexture->Shutdown();
|
||||
delete depthTexture;
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Reset();
|
||||
@@ -77,11 +78,11 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
||||
m_height = plan.mapHeight;
|
||||
m_depthTexture = depthTexture;
|
||||
m_depthView = depthView;
|
||||
m_depthShaderView = depthShaderView;
|
||||
m_surface = RenderSurface(plan.mapWidth, plan.mapHeight);
|
||||
m_surface.SetDepthAttachment(depthView);
|
||||
m_surface.SetSampleDesc(1u, 0u);
|
||||
return true;
|
||||
m_allocation.depthShaderView = depthShaderView;
|
||||
m_allocation.surface = RenderSurface(plan.mapWidth, plan.mapHeight);
|
||||
m_allocation.surface.SetDepthAttachment(depthView);
|
||||
m_allocation.surface.SetSampleDesc(1u, 0u);
|
||||
return &m_allocation;
|
||||
}
|
||||
|
||||
bool DirectionalShadowSurfaceCache::Matches(
|
||||
@@ -92,7 +93,7 @@ bool DirectionalShadowSurfaceCache::Matches(
|
||||
m_height == plan.mapHeight &&
|
||||
m_depthTexture != nullptr &&
|
||||
m_depthView != nullptr &&
|
||||
m_depthShaderView != nullptr;
|
||||
m_allocation.IsValid();
|
||||
}
|
||||
|
||||
void DirectionalShadowSurfaceCache::Reset() {
|
||||
@@ -102,10 +103,10 @@ void DirectionalShadowSurfaceCache::Reset() {
|
||||
m_depthView = nullptr;
|
||||
}
|
||||
|
||||
if (m_depthShaderView != nullptr) {
|
||||
m_depthShaderView->Shutdown();
|
||||
delete m_depthShaderView;
|
||||
m_depthShaderView = nullptr;
|
||||
if (m_allocation.depthShaderView != nullptr) {
|
||||
m_allocation.depthShaderView->Shutdown();
|
||||
delete m_allocation.depthShaderView;
|
||||
m_allocation.depthShaderView = nullptr;
|
||||
}
|
||||
|
||||
if (m_depthTexture != nullptr) {
|
||||
@@ -117,7 +118,7 @@ void DirectionalShadowSurfaceCache::Reset() {
|
||||
m_device = nullptr;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_surface = RenderSurface();
|
||||
m_allocation = {};
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
#include "Rendering/Execution/CameraRenderer.h"
|
||||
|
||||
#include "Components/CameraComponent.h"
|
||||
#include "Rendering/Caches/DirectionalShadowSurfaceCache.h"
|
||||
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||
#include "Rendering/Execution/DirectionalShadowExecutionState.h"
|
||||
#include "Rendering/Graph/RenderGraph.h"
|
||||
#include "Rendering/Graph/RenderGraphCompiler.h"
|
||||
#include "Rendering/Graph/RenderGraphExecutor.h"
|
||||
#include "Rendering/Passes/BuiltinDepthOnlyPass.h"
|
||||
#include "Rendering/Passes/BuiltinObjectIdPass.h"
|
||||
#include "Rendering/Passes/BuiltinShadowCasterPass.h"
|
||||
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||
#include "Rendering/RenderPipelineAsset.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Rendering/Shadow/DirectionalShadowRuntime.h"
|
||||
#include "RHI/RHIResourceView.h"
|
||||
#include "Scene/Scene.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr RHI::Format kRenderGraphImportedColorFormat = RHI::Format::R8G8B8A8_UNorm;
|
||||
constexpr RHI::Format kRenderGraphImportedDepthFormat = RHI::Format::D24_UNorm_S8_UInt;
|
||||
|
||||
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() {
|
||||
static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset =
|
||||
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
|
||||
@@ -45,6 +54,229 @@ std::unique_ptr<RenderPipeline> CreatePipelineFromAsset(
|
||||
return std::make_unique<Pipelines::BuiltinForwardPipeline>();
|
||||
}
|
||||
|
||||
struct RenderGraphImportedSurface {
|
||||
std::vector<RenderGraphTextureHandle> colorTextures = {};
|
||||
RenderGraphTextureHandle depthTexture = {};
|
||||
};
|
||||
|
||||
using RenderGraphImportedTextureRegistry =
|
||||
std::unordered_map<RHI::RHIResourceView*, RenderGraphTextureHandle>;
|
||||
|
||||
enum class RenderGraphSurfaceImportUsage {
|
||||
Source = 0,
|
||||
Output = 1
|
||||
};
|
||||
|
||||
Containers::String BuildRenderGraphResourceName(
|
||||
const Containers::String& surfaceName,
|
||||
const char* slotName,
|
||||
size_t index = 0u,
|
||||
bool indexed = false) {
|
||||
std::string name = surfaceName.CStr();
|
||||
name += '.';
|
||||
name += slotName;
|
||||
if (indexed) {
|
||||
name += std::to_string(index);
|
||||
}
|
||||
|
||||
return Containers::String(name.c_str());
|
||||
}
|
||||
|
||||
RenderGraphTextureDesc BuildImportedTextureDesc(
|
||||
const RenderSurface& surface,
|
||||
RHI::Format format) {
|
||||
RenderGraphTextureDesc desc = {};
|
||||
desc.width = surface.GetWidth() > 0u ? surface.GetWidth() : surface.GetRenderAreaWidth();
|
||||
desc.height = surface.GetHeight() > 0u ? surface.GetHeight() : surface.GetRenderAreaHeight();
|
||||
desc.format = static_cast<Core::uint32>(format);
|
||||
desc.textureType = static_cast<Core::uint32>(RHI::TextureType::Texture2D);
|
||||
desc.sampleCount = std::max(surface.GetSampleCount(), 1u);
|
||||
desc.sampleQuality = surface.GetSampleQuality();
|
||||
return desc;
|
||||
}
|
||||
|
||||
RenderGraphImportedTextureOptions BuildImportedTextureOptions(
|
||||
const RenderSurface& surface,
|
||||
bool isDepth,
|
||||
RenderGraphSurfaceImportUsage usage) {
|
||||
const RHI::ResourceStates beforeState =
|
||||
isDepth ? surface.GetDepthStateBefore() : surface.GetColorStateBefore();
|
||||
const RHI::ResourceStates afterState =
|
||||
isDepth ? surface.GetDepthStateAfter() : surface.GetColorStateAfter();
|
||||
|
||||
RenderGraphImportedTextureOptions options = {};
|
||||
options.initialState =
|
||||
usage == RenderGraphSurfaceImportUsage::Output
|
||||
? beforeState
|
||||
: afterState;
|
||||
options.finalState =
|
||||
usage == RenderGraphSurfaceImportUsage::Output
|
||||
? afterState
|
||||
: options.initialState;
|
||||
options.graphOwnsTransitions = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
RenderGraphTextureHandle ImportRenderGraphTexture(
|
||||
RenderGraphBuilder& builder,
|
||||
RenderGraphImportedTextureRegistry& registry,
|
||||
const Containers::String& name,
|
||||
const RenderGraphTextureDesc& desc,
|
||||
RHI::RHIResourceView* view,
|
||||
const RenderGraphImportedTextureOptions& importedOptions) {
|
||||
if (view == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto existing = registry.find(view);
|
||||
if (existing != registry.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
const RenderGraphTextureHandle handle =
|
||||
builder.ImportTexture(name, desc, view, importedOptions);
|
||||
registry.emplace(view, handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
RenderGraphImportedSurface ImportRenderGraphSurface(
|
||||
RenderGraphBuilder& builder,
|
||||
RenderGraphImportedTextureRegistry& registry,
|
||||
const Containers::String& surfaceName,
|
||||
const RenderSurface* surface,
|
||||
RenderGraphSurfaceImportUsage usage) {
|
||||
RenderGraphImportedSurface importedSurface = {};
|
||||
if (surface == nullptr) {
|
||||
return importedSurface;
|
||||
}
|
||||
|
||||
const RenderGraphTextureDesc colorDesc =
|
||||
BuildImportedTextureDesc(*surface, kRenderGraphImportedColorFormat);
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface->GetColorAttachments();
|
||||
importedSurface.colorTextures.reserve(colorAttachments.size());
|
||||
for (size_t colorIndex = 0; colorIndex < colorAttachments.size(); ++colorIndex) {
|
||||
RHI::RHIResourceView* colorAttachment = colorAttachments[colorIndex];
|
||||
if (colorAttachment == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
importedSurface.colorTextures.push_back(
|
||||
ImportRenderGraphTexture(
|
||||
builder,
|
||||
registry,
|
||||
BuildRenderGraphResourceName(surfaceName, "Color", colorIndex, true),
|
||||
colorDesc,
|
||||
colorAttachment,
|
||||
BuildImportedTextureOptions(
|
||||
*surface,
|
||||
false,
|
||||
usage)));
|
||||
}
|
||||
|
||||
if (RHI::RHIResourceView* depthAttachment = surface->GetDepthAttachment();
|
||||
depthAttachment != nullptr) {
|
||||
importedSurface.depthTexture =
|
||||
ImportRenderGraphTexture(
|
||||
builder,
|
||||
registry,
|
||||
BuildRenderGraphResourceName(surfaceName, "Depth"),
|
||||
BuildImportedTextureDesc(*surface, kRenderGraphImportedDepthFormat),
|
||||
depthAttachment,
|
||||
BuildImportedTextureOptions(
|
||||
*surface,
|
||||
true,
|
||||
usage));
|
||||
}
|
||||
|
||||
return importedSurface;
|
||||
}
|
||||
|
||||
RenderGraphTextureHandle GetPrimaryColorTexture(
|
||||
const RenderGraphImportedSurface& surface) {
|
||||
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||
if (texture.IsValid()) {
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ReadRenderGraphSurface(
|
||||
RenderGraphPassBuilder& passBuilder,
|
||||
const RenderGraphImportedSurface& surface) {
|
||||
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||
if (texture.IsValid()) {
|
||||
passBuilder.ReadTexture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
if (surface.depthTexture.IsValid()) {
|
||||
passBuilder.ReadTexture(surface.depthTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadRenderGraphColorSurface(
|
||||
RenderGraphPassBuilder& passBuilder,
|
||||
const RenderGraphImportedSurface& surface) {
|
||||
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||
if (texture.IsValid()) {
|
||||
passBuilder.ReadTexture(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRenderGraphSurface(
|
||||
RenderGraphPassBuilder& passBuilder,
|
||||
const RenderGraphImportedSurface& surface) {
|
||||
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||
if (texture.IsValid()) {
|
||||
passBuilder.WriteTexture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
if (surface.depthTexture.IsValid()) {
|
||||
passBuilder.WriteTexture(surface.depthTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRenderGraphColorSurface(
|
||||
RenderGraphPassBuilder& passBuilder,
|
||||
const RenderGraphImportedSurface& surface) {
|
||||
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||
if (texture.IsValid()) {
|
||||
passBuilder.WriteTexture(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsFullscreenSequenceStage(
|
||||
CameraFrameStage stage) {
|
||||
return stage == CameraFrameStage::PostProcess ||
|
||||
stage == CameraFrameStage::FinalOutput;
|
||||
}
|
||||
|
||||
Containers::String BuildRenderGraphSequencePassName(
|
||||
const Containers::String& stageName,
|
||||
size_t passIndex) {
|
||||
std::string name = stageName.CStr();
|
||||
name += ".Pass";
|
||||
name += std::to_string(passIndex);
|
||||
return Containers::String(name.c_str());
|
||||
}
|
||||
|
||||
RenderGraphTextureDesc BuildFullscreenTransientTextureDesc(
|
||||
const RenderSurface& surface) {
|
||||
RenderGraphTextureDesc desc = {};
|
||||
desc.width = surface.GetWidth() > 0u ? surface.GetWidth() : surface.GetRenderAreaWidth();
|
||||
desc.height = surface.GetHeight() > 0u ? surface.GetHeight() : surface.GetRenderAreaHeight();
|
||||
desc.format = static_cast<Core::uint32>(kRenderGraphImportedColorFormat);
|
||||
desc.textureType = static_cast<Core::uint32>(RHI::TextureType::Texture2D);
|
||||
desc.sampleCount = 1u;
|
||||
desc.sampleQuality = 0u;
|
||||
return desc;
|
||||
}
|
||||
|
||||
Resources::ShaderKeywordSet BuildSceneGlobalShaderKeywords(
|
||||
const RenderSceneData& sceneData) {
|
||||
Resources::ShaderKeywordSet keywords = {};
|
||||
@@ -173,6 +405,17 @@ private:
|
||||
bool m_failed = false;
|
||||
};
|
||||
|
||||
bool EnsureInitializedPassSequence(
|
||||
std::unique_ptr<ScopedInitializedPassSequence>& activeSequence,
|
||||
RenderPassSequence* sequence,
|
||||
const RenderContext& context) {
|
||||
if (activeSequence == nullptr) {
|
||||
activeSequence = std::make_unique<ScopedInitializedPassSequence>(sequence, context);
|
||||
}
|
||||
|
||||
return activeSequence->IsReady();
|
||||
}
|
||||
|
||||
struct CameraFrameExecutionState {
|
||||
RenderPipeline* pipeline = nullptr;
|
||||
RenderPass* objectIdPass = nullptr;
|
||||
@@ -221,8 +464,13 @@ bool ExecuteFullscreenPassSequenceStage(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sequence == nullptr || sequence->GetPassCount() <= 1u) {
|
||||
return activeSequence->Execute(passContext);
|
||||
if (sequence == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sequence->GetPassCount() <= 1u) {
|
||||
return sequence->GetPassCount() == 0u ||
|
||||
sequence->ExecutePass(0u, passContext);
|
||||
}
|
||||
|
||||
if (surfaceCache == nullptr ||
|
||||
@@ -287,136 +535,319 @@ bool ExecuteFullscreenPassSequenceStage(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryBuildRenderGraphTransientSurface(
|
||||
const RenderSurface& templateSurface,
|
||||
const RenderGraphExecutionContext& graphContext,
|
||||
RenderGraphTextureHandle textureHandle,
|
||||
RenderSurface& outSurface) {
|
||||
if (!textureHandle.IsValid() ||
|
||||
!graphContext.IsTransientTexture(textureHandle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderGraphTextureDesc textureDesc = {};
|
||||
RHI::RHIResourceView* renderTargetView =
|
||||
graphContext.ResolveTextureView(
|
||||
textureHandle,
|
||||
RenderGraphTextureViewType::RenderTarget);
|
||||
if (renderTargetView == nullptr ||
|
||||
!graphContext.TryGetTextureDesc(textureHandle, textureDesc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outSurface = templateSurface;
|
||||
CopyIntermediateSurfaceLayout(templateSurface, outSurface);
|
||||
outSurface.SetColorAttachment(renderTargetView);
|
||||
outSurface.SetAutoTransitionEnabled(false);
|
||||
outSurface.SetSampleDesc(textureDesc.sampleCount, textureDesc.sampleQuality);
|
||||
outSurface.SetColorStateBefore(RHI::ResourceStates::RenderTarget);
|
||||
outSurface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExecuteFullscreenPassSequencePass(
|
||||
RenderPassSequence* sequence,
|
||||
size_t passIndex,
|
||||
const RenderPassContext& passContext,
|
||||
const RenderGraphExecutionContext& graphContext,
|
||||
RenderGraphTextureHandle sourceColorHandle,
|
||||
RenderGraphTextureHandle outputColorHandle);
|
||||
|
||||
RenderPassContext BuildFrameStagePassContext(
|
||||
CameraFrameStage stage,
|
||||
const CameraRenderRequest& request,
|
||||
const CameraFramePlan& plan,
|
||||
const RenderSceneData& sceneData) {
|
||||
const RenderSurface* outputSurface = request.GetOutputSurface(stage);
|
||||
const RenderSurface* outputSurface = plan.GetOutputSurface(stage);
|
||||
return {
|
||||
request.context,
|
||||
outputSurface != nullptr ? *outputSurface : request.surface,
|
||||
plan.request.context,
|
||||
outputSurface != nullptr ? *outputSurface : plan.request.surface,
|
||||
sceneData,
|
||||
request.GetSourceSurface(stage),
|
||||
request.GetSourceColorView(stage),
|
||||
request.GetSourceColorState(stage)
|
||||
plan.GetSourceSurface(stage),
|
||||
plan.GetSourceColorView(stage),
|
||||
plan.GetSourceColorState(stage)
|
||||
};
|
||||
}
|
||||
|
||||
bool ExecuteFrameStage(
|
||||
CameraFrameStage stage,
|
||||
const CameraRenderRequest& request,
|
||||
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowExecutionState& shadowState,
|
||||
const RenderSceneData& sceneData,
|
||||
CameraFrameExecutionState& executionState) {
|
||||
const RenderPassContext passContext = BuildFrameStagePassContext(stage, request, sceneData);
|
||||
const RenderPassContext passContext = BuildFrameStagePassContext(stage, plan, sceneData);
|
||||
|
||||
switch (stage) {
|
||||
case CameraFrameStage::PreScenePasses:
|
||||
return ExecutePassSequenceStage(
|
||||
executionState.preScenePasses,
|
||||
request.GetPassSequence(stage),
|
||||
request.context,
|
||||
plan.GetPassSequence(stage),
|
||||
plan.request.context,
|
||||
passContext);
|
||||
case CameraFrameStage::ShadowCaster:
|
||||
return ExecuteScenePassRequest(
|
||||
executionState.shadowCasterPass,
|
||||
resolvedShadowCaster,
|
||||
request.context,
|
||||
shadowState.shadowCasterRequest,
|
||||
plan.request.context,
|
||||
sceneData);
|
||||
case CameraFrameStage::DepthOnly:
|
||||
return ExecuteScenePassRequest(
|
||||
executionState.depthOnlyPass,
|
||||
request.depthOnly,
|
||||
request.context,
|
||||
plan.request.depthOnly,
|
||||
plan.request.context,
|
||||
sceneData);
|
||||
case CameraFrameStage::MainScene:
|
||||
return executionState.pipeline != nullptr &&
|
||||
executionState.pipeline->Render(request.context, passContext.surface, sceneData);
|
||||
executionState.pipeline->Render(
|
||||
FrameExecutionContext(
|
||||
plan.request.context,
|
||||
passContext.surface,
|
||||
sceneData,
|
||||
passContext.sourceSurface,
|
||||
passContext.sourceColorView,
|
||||
passContext.sourceColorState));
|
||||
case CameraFrameStage::PostProcess:
|
||||
return ExecuteFullscreenPassSequenceStage(
|
||||
executionState.postProcessPasses,
|
||||
request.GetPassSequence(stage),
|
||||
request.context,
|
||||
plan.GetPassSequence(stage),
|
||||
plan.request.context,
|
||||
passContext,
|
||||
executionState.postProcessSurfaceCache);
|
||||
case CameraFrameStage::FinalOutput:
|
||||
return ExecuteFullscreenPassSequenceStage(
|
||||
executionState.finalOutputPasses,
|
||||
request.GetPassSequence(stage),
|
||||
request.context,
|
||||
plan.GetPassSequence(stage),
|
||||
plan.request.context,
|
||||
passContext,
|
||||
executionState.finalOutputSurfaceCache);
|
||||
case CameraFrameStage::ObjectId:
|
||||
return !request.objectId.IsRequested() ||
|
||||
return !plan.request.objectId.IsRequested() ||
|
||||
ExecuteStandalonePass(
|
||||
executionState.objectIdPass,
|
||||
request.context,
|
||||
request.objectId.surface,
|
||||
plan.request.context,
|
||||
plan.request.objectId.surface,
|
||||
sceneData);
|
||||
case CameraFrameStage::PostScenePasses:
|
||||
return ExecutePassSequenceStage(
|
||||
executionState.postScenePasses,
|
||||
request.GetPassSequence(stage),
|
||||
request.context,
|
||||
plan.GetPassSequence(stage),
|
||||
plan.request.context,
|
||||
passContext);
|
||||
case CameraFrameStage::OverlayPasses:
|
||||
return ExecutePassSequenceStage(
|
||||
executionState.overlayPasses,
|
||||
request.GetPassSequence(stage),
|
||||
request.context,
|
||||
plan.GetPassSequence(stage),
|
||||
plan.request.context,
|
||||
passContext);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RenderDirectionalShadowData BuildDirectionalShadowData(
|
||||
const DirectionalShadowRenderPlan& plan,
|
||||
RHI::RHIResourceView* shadowMapView) {
|
||||
RenderDirectionalShadowData shadowData = {};
|
||||
if (!plan.IsValid() || shadowMapView == nullptr) {
|
||||
return shadowData;
|
||||
bool ExecuteRenderGraphPlan(
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowExecutionState& shadowState,
|
||||
const RenderSceneData& sceneData,
|
||||
CameraFrameExecutionState& executionState) {
|
||||
RenderGraph graph = {};
|
||||
RenderGraphBuilder graphBuilder(graph);
|
||||
RenderGraphImportedTextureRegistry importedTextures = {};
|
||||
|
||||
bool stageExecutionSucceeded = true;
|
||||
for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) {
|
||||
if (!plan.HasFrameStage(stageInfo.stage) &&
|
||||
stageInfo.stage != CameraFrameStage::MainScene) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CameraFrameStage stage = stageInfo.stage;
|
||||
const Containers::String stageName(GetCameraFrameStageName(stageInfo.stage));
|
||||
RenderPassSequence* const stageSequence = plan.GetPassSequence(stage);
|
||||
|
||||
if (IsFullscreenSequenceStage(stage) &&
|
||||
stageSequence != nullptr &&
|
||||
stageSequence->GetPassCount() > 1u) {
|
||||
const RenderPassContext stagePassContext =
|
||||
BuildFrameStagePassContext(stage, plan, sceneData);
|
||||
const RenderGraphImportedSurface sourceSurface =
|
||||
ImportRenderGraphSurface(
|
||||
graphBuilder,
|
||||
importedTextures,
|
||||
stageName + ".Source",
|
||||
stagePassContext.sourceSurface,
|
||||
RenderGraphSurfaceImportUsage::Source);
|
||||
const RenderGraphImportedSurface outputSurface =
|
||||
ImportRenderGraphSurface(
|
||||
graphBuilder,
|
||||
importedTextures,
|
||||
stageName + ".Output",
|
||||
&stagePassContext.surface,
|
||||
RenderGraphSurfaceImportUsage::Output);
|
||||
RenderGraphTextureHandle currentSourceColor = GetPrimaryColorTexture(sourceSurface);
|
||||
const RenderGraphTextureHandle finalOutputColor =
|
||||
GetPrimaryColorTexture(outputSurface);
|
||||
const RenderGraphTextureDesc transientDesc =
|
||||
BuildFullscreenTransientTextureDesc(stagePassContext.surface);
|
||||
|
||||
for (size_t passIndex = 0; passIndex < stageSequence->GetPassCount(); ++passIndex) {
|
||||
const bool isLastPass = (passIndex + 1u) == stageSequence->GetPassCount();
|
||||
const size_t sequencePassIndex = passIndex;
|
||||
const RenderGraphTextureHandle passSourceColor = currentSourceColor;
|
||||
const RenderGraphTextureHandle passOutputColor =
|
||||
isLastPass
|
||||
? finalOutputColor
|
||||
: graphBuilder.CreateTransientTexture(
|
||||
BuildRenderGraphSequencePassName(stageName, sequencePassIndex) + ".Color",
|
||||
transientDesc);
|
||||
|
||||
graphBuilder.AddRasterPass(
|
||||
BuildRenderGraphSequencePassName(stageName, sequencePassIndex),
|
||||
[&, stage, sequencePassIndex, passSourceColor, passOutputColor](
|
||||
RenderGraphPassBuilder& passBuilder) {
|
||||
if (passSourceColor.IsValid()) {
|
||||
passBuilder.ReadTexture(passSourceColor);
|
||||
}
|
||||
if (passOutputColor.IsValid()) {
|
||||
passBuilder.WriteTexture(passOutputColor);
|
||||
}
|
||||
passBuilder.SetExecuteCallback(
|
||||
[&, stage, sequencePassIndex, passSourceColor, passOutputColor](
|
||||
const RenderGraphExecutionContext& executionContext) {
|
||||
if (!stageExecutionSucceeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<ScopedInitializedPassSequence>& activeSequence =
|
||||
stage == CameraFrameStage::PostProcess
|
||||
? executionState.postProcessPasses
|
||||
: executionState.finalOutputPasses;
|
||||
if (!EnsureInitializedPassSequence(
|
||||
activeSequence,
|
||||
plan.GetPassSequence(stage),
|
||||
plan.request.context)) {
|
||||
stageExecutionSucceeded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
stageExecutionSucceeded =
|
||||
ExecuteFullscreenPassSequencePass(
|
||||
plan.GetPassSequence(stage),
|
||||
sequencePassIndex,
|
||||
BuildFrameStagePassContext(stage, plan, sceneData),
|
||||
executionContext,
|
||||
passSourceColor,
|
||||
passOutputColor);
|
||||
});
|
||||
});
|
||||
|
||||
currentSourceColor = passOutputColor;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const RenderGraphImportedSurface sourceSurface =
|
||||
ImportRenderGraphSurface(
|
||||
graphBuilder,
|
||||
importedTextures,
|
||||
stageName + ".Source",
|
||||
plan.GetSourceSurface(stageInfo.stage),
|
||||
RenderGraphSurfaceImportUsage::Source);
|
||||
const RenderGraphImportedSurface outputSurface =
|
||||
ImportRenderGraphSurface(
|
||||
graphBuilder,
|
||||
importedTextures,
|
||||
stageName + ".Output",
|
||||
plan.GetOutputSurface(stageInfo.stage),
|
||||
RenderGraphSurfaceImportUsage::Output);
|
||||
|
||||
graphBuilder.AddRasterPass(
|
||||
stageName,
|
||||
[&, sourceSurface, outputSurface, stage](RenderGraphPassBuilder& passBuilder) {
|
||||
if (IsFullscreenSequenceStage(stage)) {
|
||||
ReadRenderGraphColorSurface(passBuilder, sourceSurface);
|
||||
WriteRenderGraphColorSurface(passBuilder, outputSurface);
|
||||
} else {
|
||||
ReadRenderGraphSurface(passBuilder, sourceSurface);
|
||||
WriteRenderGraphSurface(passBuilder, outputSurface);
|
||||
}
|
||||
passBuilder.SetExecuteCallback(
|
||||
[&, stage](const RenderGraphExecutionContext&) {
|
||||
if (!stageExecutionSucceeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
stageExecutionSucceeded = ExecuteFrameStage(
|
||||
stage,
|
||||
plan,
|
||||
shadowState,
|
||||
sceneData,
|
||||
executionState);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
shadowData.enabled = true;
|
||||
shadowData.viewProjection = plan.cameraData.viewProjection;
|
||||
shadowData.shadowMap = shadowMapView;
|
||||
const float texelWorldSize = plan.texelWorldSize > Math::EPSILON
|
||||
? plan.texelWorldSize
|
||||
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
|
||||
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
|
||||
: 0.0f);
|
||||
shadowData.mapMetrics.inverseMapSize = Math::Vector2(
|
||||
1.0f / static_cast<float>(plan.mapWidth),
|
||||
1.0f / static_cast<float>(plan.mapHeight));
|
||||
shadowData.mapMetrics.worldTexelSize = texelWorldSize;
|
||||
shadowData.sampling.enabled = 1.0f;
|
||||
shadowData.sampling.settings = plan.sampling;
|
||||
shadowData.casterBias.settings = plan.casterBias;
|
||||
return shadowData;
|
||||
CompiledRenderGraph compiledGraph = {};
|
||||
Containers::String errorMessage;
|
||||
if (!RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
Containers::String("CameraRenderer::Render failed: RenderGraph compile failed: ") +
|
||||
errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!RenderGraphExecutor::Execute(compiledGraph, plan.request.context, &errorMessage)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
Containers::String("CameraRenderer::Render failed: RenderGraph execute failed: ") +
|
||||
errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return stageExecutionSucceeded;
|
||||
}
|
||||
|
||||
RenderEnvironmentData BuildEnvironmentData(const CameraRenderRequest& request) {
|
||||
RenderEnvironmentData BuildEnvironmentData(const CameraFramePlan& plan) {
|
||||
RenderEnvironmentData environment = {};
|
||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
||||
if (request.camera == nullptr ||
|
||||
const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface();
|
||||
if (plan.request.camera == nullptr ||
|
||||
mainSceneSurface.GetDepthAttachment() == nullptr ||
|
||||
!HasRenderClearFlag(request.clearFlags, RenderClearFlags::Color) ||
|
||||
!request.camera->IsSkyboxEnabled() ||
|
||||
request.camera->GetProjectionType() != Components::CameraProjectionType::Perspective) {
|
||||
!HasRenderClearFlag(plan.request.clearFlags, RenderClearFlags::Color) ||
|
||||
!plan.request.camera->IsSkyboxEnabled() ||
|
||||
plan.request.camera->GetProjectionType() != Components::CameraProjectionType::Perspective) {
|
||||
return environment;
|
||||
}
|
||||
|
||||
if (const Resources::Material* skyboxMaterial = request.camera->GetSkyboxMaterial()) {
|
||||
if (const Resources::Material* skyboxMaterial = plan.request.camera->GetSkyboxMaterial()) {
|
||||
environment.mode = RenderEnvironmentMode::MaterialSkybox;
|
||||
environment.materialSkybox.material = skyboxMaterial;
|
||||
return environment;
|
||||
}
|
||||
|
||||
environment.mode = RenderEnvironmentMode::ProceduralSkybox;
|
||||
environment.skybox.topColor = request.camera->GetSkyboxTopColor();
|
||||
environment.skybox.horizonColor = request.camera->GetSkyboxHorizonColor();
|
||||
environment.skybox.bottomColor = request.camera->GetSkyboxBottomColor();
|
||||
environment.skybox.topColor = plan.request.camera->GetSkyboxTopColor();
|
||||
environment.skybox.horizonColor = plan.request.camera->GetSkyboxHorizonColor();
|
||||
environment.skybox.bottomColor = plan.request.camera->GetSkyboxBottomColor();
|
||||
return environment;
|
||||
}
|
||||
|
||||
@@ -443,6 +874,7 @@ CameraRenderer::CameraRenderer(
|
||||
, m_objectIdPass(std::move(objectIdPass))
|
||||
, m_depthOnlyPass(std::move(depthOnlyPass))
|
||||
, m_shadowCasterPass(std::move(shadowCasterPass))
|
||||
, m_directionalShadowRuntime(std::make_unique<DirectionalShadowRuntime>())
|
||||
, m_postProcessSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>())
|
||||
, m_finalOutputSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>()) {
|
||||
if (m_objectIdPass == nullptr) {
|
||||
@@ -462,6 +894,7 @@ CameraRenderer::CameraRenderer(std::shared_ptr<const RenderPipelineAsset> pipeli
|
||||
, m_objectIdPass(std::make_unique<Passes::BuiltinObjectIdPass>())
|
||||
, m_depthOnlyPass(CreateDefaultDepthOnlyPass())
|
||||
, m_shadowCasterPass(CreateDefaultShadowCasterPass())
|
||||
, m_directionalShadowRuntime(std::make_unique<DirectionalShadowRuntime>())
|
||||
, m_postProcessSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>())
|
||||
, m_finalOutputSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>()) {
|
||||
SetPipelineAsset(m_pipelineAsset);
|
||||
@@ -537,72 +970,105 @@ void CameraRenderer::ResetPipeline(std::unique_ptr<RenderPipeline> pipeline) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraRenderer::ResolveShadowCasterRequest(
|
||||
const CameraRenderRequest& request,
|
||||
ShadowCasterRenderRequest& outResolvedShadowCaster,
|
||||
RHI::RHIResourceView*& outShadowMapView) {
|
||||
outResolvedShadowCaster = request.shadowCaster;
|
||||
outShadowMapView = nullptr;
|
||||
|
||||
if (outResolvedShadowCaster.IsRequested()) {
|
||||
return outResolvedShadowCaster.IsValid();
|
||||
}
|
||||
|
||||
if (!request.directionalShadow.IsValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_directionalShadowSurface == nullptr) {
|
||||
m_directionalShadowSurface = std::make_unique<DirectionalShadowSurfaceCache>();
|
||||
}
|
||||
|
||||
if (!m_directionalShadowSurface->EnsureSurface(request.context, request.directionalShadow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outResolvedShadowCaster.surface = m_directionalShadowSurface->GetSurface();
|
||||
outResolvedShadowCaster.clearFlags = RenderClearFlags::Depth;
|
||||
if (!outResolvedShadowCaster.hasCameraDataOverride) {
|
||||
outResolvedShadowCaster.hasCameraDataOverride = true;
|
||||
outResolvedShadowCaster.cameraDataOverride = request.directionalShadow.cameraData;
|
||||
}
|
||||
|
||||
outShadowMapView = m_directionalShadowSurface->GetDepthShaderView();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CameraRenderer::BuildSceneDataForRequest(
|
||||
const CameraRenderRequest& request,
|
||||
RHI::RHIResourceView* shadowMapView,
|
||||
bool CameraRenderer::BuildSceneDataForPlan(
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowExecutionState& shadowState,
|
||||
RenderSceneData& outSceneData) {
|
||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
||||
const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface();
|
||||
outSceneData = m_sceneExtractor.ExtractForCamera(
|
||||
*request.scene,
|
||||
*request.camera,
|
||||
*plan.request.scene,
|
||||
*plan.request.camera,
|
||||
mainSceneSurface.GetRenderAreaWidth(),
|
||||
mainSceneSurface.GetRenderAreaHeight());
|
||||
if (!outSceneData.HasCamera()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.directionalShadow.IsValid()) {
|
||||
outSceneData.lighting.mainDirectionalShadow =
|
||||
BuildDirectionalShadowData(request.directionalShadow, shadowMapView);
|
||||
}
|
||||
outSceneData.lighting.mainDirectionalShadow = shadowState.shadowData;
|
||||
outSceneData.globalShaderKeywords = BuildSceneGlobalShaderKeywords(outSceneData);
|
||||
|
||||
outSceneData.cameraData.clearFlags = request.clearFlags;
|
||||
outSceneData.environment = BuildEnvironmentData(request);
|
||||
if (request.hasClearColorOverride) {
|
||||
outSceneData.cameraData.clearColor = request.clearColorOverride;
|
||||
outSceneData.cameraData.clearFlags = plan.request.clearFlags;
|
||||
outSceneData.environment = BuildEnvironmentData(plan);
|
||||
if (plan.request.hasClearColorOverride) {
|
||||
outSceneData.cameraData.clearColor = plan.request.clearColorOverride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool ExecuteFullscreenPassSequencePass(
|
||||
RenderPassSequence* sequence,
|
||||
size_t passIndex,
|
||||
const RenderPassContext& passContext,
|
||||
const RenderGraphExecutionContext& graphContext,
|
||||
RenderGraphTextureHandle sourceColorHandle,
|
||||
RenderGraphTextureHandle outputColorHandle) {
|
||||
if (sequence == nullptr || passIndex >= sequence->GetPassCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sequence->GetPassCount() <= 1u) {
|
||||
return sequence->ExecutePass(passIndex, passContext);
|
||||
}
|
||||
|
||||
const RenderSurface* currentSourceSurface = passContext.sourceSurface;
|
||||
RHI::RHIResourceView* currentSourceColorView = passContext.sourceColorView;
|
||||
RHI::ResourceStates currentSourceColorState = passContext.sourceColorState;
|
||||
RenderSurface transientSourceSurface = {};
|
||||
if (sourceColorHandle.IsValid() &&
|
||||
graphContext.IsTransientTexture(sourceColorHandle)) {
|
||||
if (!TryBuildRenderGraphTransientSurface(
|
||||
passContext.surface,
|
||||
graphContext,
|
||||
sourceColorHandle,
|
||||
transientSourceSurface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSourceSurface = &transientSourceSurface;
|
||||
currentSourceColorView =
|
||||
graphContext.ResolveTextureView(
|
||||
sourceColorHandle,
|
||||
RenderGraphTextureViewType::ShaderResource);
|
||||
if (currentSourceColorView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
currentSourceColorState = RHI::ResourceStates::PixelShaderResource;
|
||||
}
|
||||
|
||||
const RenderSurface* outputSurface = &passContext.surface;
|
||||
RenderSurface transientOutputSurface = {};
|
||||
if (outputColorHandle.IsValid() &&
|
||||
graphContext.IsTransientTexture(outputColorHandle)) {
|
||||
if (!TryBuildRenderGraphTransientSurface(
|
||||
passContext.surface,
|
||||
graphContext,
|
||||
outputColorHandle,
|
||||
transientOutputSurface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outputSurface = &transientOutputSurface;
|
||||
}
|
||||
|
||||
const RenderPassContext chainedContext = {
|
||||
passContext.renderContext,
|
||||
*outputSurface,
|
||||
passContext.sceneData,
|
||||
currentSourceSurface,
|
||||
currentSourceColorView,
|
||||
currentSourceColorState
|
||||
};
|
||||
return sequence->ExecutePass(passIndex, chainedContext);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CameraRenderer::ExecuteRenderPlan(
|
||||
const CameraRenderRequest& request,
|
||||
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
||||
const CameraFramePlan& plan,
|
||||
const DirectionalShadowExecutionState& shadowState,
|
||||
const RenderSceneData& sceneData) {
|
||||
CameraFrameExecutionState executionState = {};
|
||||
executionState.pipeline = m_pipeline.get();
|
||||
@@ -611,36 +1077,19 @@ bool CameraRenderer::ExecuteRenderPlan(
|
||||
executionState.shadowCasterPass = m_shadowCasterPass.get();
|
||||
executionState.postProcessSurfaceCache = m_postProcessSurfaceCache.get();
|
||||
executionState.finalOutputSurfaceCache = m_finalOutputSurfaceCache.get();
|
||||
|
||||
for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) {
|
||||
if (!request.HasFrameStage(stageInfo.stage) &&
|
||||
stageInfo.stage != CameraFrameStage::MainScene) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ExecuteFrameStage(
|
||||
stageInfo.stage,
|
||||
request,
|
||||
resolvedShadowCaster,
|
||||
sceneData,
|
||||
executionState)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return ExecuteRenderGraphPlan(plan, shadowState, sceneData, executionState);
|
||||
}
|
||||
|
||||
bool CameraRenderer::Render(
|
||||
const CameraRenderRequest& request) {
|
||||
if (!request.IsValid() || m_pipeline == nullptr) {
|
||||
const CameraFramePlan& plan) {
|
||||
if (!plan.IsValid() || m_pipeline == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: request invalid or pipeline missing");
|
||||
"CameraRenderer::Render failed: plan invalid or pipeline missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
||||
const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface();
|
||||
if (mainSceneSurface.GetRenderAreaWidth() == 0 ||
|
||||
mainSceneSurface.GetRenderAreaHeight() == 0) {
|
||||
Debug::Logger::Get().Error(
|
||||
@@ -648,53 +1097,53 @@ bool CameraRenderer::Render(
|
||||
"CameraRenderer::Render failed: main scene surface render area is empty");
|
||||
return false;
|
||||
}
|
||||
if (request.depthOnly.IsRequested() &&
|
||||
!request.depthOnly.IsValid()) {
|
||||
if (plan.request.depthOnly.IsRequested() &&
|
||||
!plan.request.depthOnly.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: depth-only request invalid");
|
||||
return false;
|
||||
}
|
||||
if (request.postProcess.IsRequested() &&
|
||||
!request.postProcess.IsValid()) {
|
||||
if (plan.postProcess.IsRequested() &&
|
||||
!plan.postProcess.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: post-process request invalid");
|
||||
return false;
|
||||
}
|
||||
if (request.finalOutput.IsRequested() &&
|
||||
!request.finalOutput.IsValid()) {
|
||||
if (plan.finalOutput.IsRequested() &&
|
||||
!plan.finalOutput.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: final-output request invalid");
|
||||
return false;
|
||||
}
|
||||
if (request.objectId.IsRequested() &&
|
||||
!request.objectId.IsValid()) {
|
||||
if (plan.request.objectId.IsRequested() &&
|
||||
!plan.request.objectId.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: object-id request invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
ShadowCasterRenderRequest resolvedShadowCaster = {};
|
||||
RHI::RHIResourceView* shadowMapView = nullptr;
|
||||
if (!ResolveShadowCasterRequest(request, resolvedShadowCaster, shadowMapView)) {
|
||||
DirectionalShadowExecutionState shadowState = {};
|
||||
if (m_directionalShadowRuntime == nullptr ||
|
||||
!m_directionalShadowRuntime->ResolveExecutionState(plan, shadowState)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: ResolveShadowCasterRequest returned false");
|
||||
"CameraRenderer::Render failed: DirectionalShadowRuntime::ResolveExecutionState returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderSceneData sceneData = {};
|
||||
if (!BuildSceneDataForRequest(request, shadowMapView, sceneData)) {
|
||||
if (!BuildSceneDataForPlan(plan, shadowState, sceneData)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: BuildSceneDataForRequest returned false");
|
||||
"CameraRenderer::Render failed: BuildSceneDataForPlan returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExecuteRenderPlan(request, resolvedShadowCaster, sceneData)) {
|
||||
if (!ExecuteRenderPlan(plan, shadowState, sceneData)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: ExecuteRenderPlan returned false");
|
||||
|
||||
@@ -1,82 +1,37 @@
|
||||
#include "Rendering/Execution/SceneRenderer.h"
|
||||
|
||||
#include "Components/CameraComponent.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||
#include "Rendering/Planning/CameraPostProcessPassFactory.h"
|
||||
#include "Rendering/Planning/FinalColorPassFactory.h"
|
||||
#include "Rendering/Planning/CameraFramePlanBuilder.h"
|
||||
#include "Rendering/Planning/SceneRenderRequestUtils.h"
|
||||
#include "Rendering/RenderPipelineAsset.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
namespace {
|
||||
|
||||
RenderSurface ConfigureFullscreenStageSurface(
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry& entry,
|
||||
const RenderSurface& templateSurface,
|
||||
bool copyDepthAttachment) {
|
||||
RenderSurface surface = entry.surface;
|
||||
if (copyDepthAttachment) {
|
||||
surface.SetDepthAttachment(templateSurface.GetDepthAttachment());
|
||||
surface.SetDepthStateBefore(templateSurface.GetDepthStateBefore());
|
||||
surface.SetDepthStateAfter(templateSurface.GetDepthStateAfter());
|
||||
if (templateSurface.HasClearColorOverride()) {
|
||||
surface.SetClearColorOverride(templateSurface.GetClearColorOverride());
|
||||
}
|
||||
}
|
||||
|
||||
if (templateSurface.HasCustomRenderArea()) {
|
||||
surface.SetRenderArea(templateSurface.GetRenderArea());
|
||||
} else {
|
||||
surface.ResetRenderArea();
|
||||
}
|
||||
|
||||
surface.SetColorStateBefore(entry.currentColorState);
|
||||
surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||
return surface;
|
||||
}
|
||||
|
||||
void UpdateTrackedFullscreenSurfaceState(
|
||||
std::vector<std::unique_ptr<FullscreenPassSurfaceCache>>& surfaceCaches,
|
||||
const RenderSurface* surface) {
|
||||
if (surface == nullptr || surface->GetColorAttachments().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* colorAttachment = surface->GetColorAttachments()[0];
|
||||
if (colorAttachment == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<FullscreenPassSurfaceCache>& cache : surfaceCaches) {
|
||||
if (cache == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t entryIndex = 0; entryIndex < cache->GetSurfaceCount(); ++entryIndex) {
|
||||
FullscreenPassSurfaceCache::SurfaceEntry* entry = cache->GetSurfaceEntry(entryIndex);
|
||||
if (entry == nullptr || entry->renderTargetView != colorAttachment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry->currentColorState = surface->GetColorStateAfter();
|
||||
return;
|
||||
}
|
||||
}
|
||||
bool CompareCameraFramePlans(
|
||||
const CameraFramePlan& lhs,
|
||||
const CameraFramePlan& rhs) {
|
||||
return SceneRenderRequestUtils::CompareCameraRenderRequests(
|
||||
lhs.request,
|
||||
rhs.request);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneRenderer::SceneRenderer() = default;
|
||||
SceneRenderer::SceneRenderer()
|
||||
: m_framePlanBuilder(std::make_unique<CameraFramePlanBuilder>()) {
|
||||
}
|
||||
|
||||
SceneRenderer::SceneRenderer(std::unique_ptr<RenderPipeline> pipeline)
|
||||
: m_cameraRenderer(std::move(pipeline)) {
|
||||
: m_cameraRenderer(std::move(pipeline))
|
||||
, m_framePlanBuilder(std::make_unique<CameraFramePlanBuilder>()) {
|
||||
}
|
||||
|
||||
SceneRenderer::SceneRenderer(std::shared_ptr<const RenderPipelineAsset> pipelineAsset)
|
||||
: m_cameraRenderer(std::move(pipelineAsset)) {
|
||||
: m_cameraRenderer(std::move(pipelineAsset))
|
||||
, m_framePlanBuilder(std::make_unique<CameraFramePlanBuilder>()) {
|
||||
}
|
||||
|
||||
SceneRenderer::~SceneRenderer() = default;
|
||||
@@ -89,49 +44,52 @@ void SceneRenderer::SetPipelineAsset(std::shared_ptr<const RenderPipelineAsset>
|
||||
m_cameraRenderer.SetPipelineAsset(std::move(pipelineAsset));
|
||||
}
|
||||
|
||||
std::vector<CameraRenderRequest> SceneRenderer::BuildRenderRequests(
|
||||
std::vector<CameraFramePlan> SceneRenderer::BuildFramePlans(
|
||||
const Components::Scene& scene,
|
||||
Components::CameraComponent* overrideCamera,
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface) {
|
||||
std::vector<CameraRenderRequest> requests =
|
||||
const std::vector<CameraRenderRequest> requests =
|
||||
m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface);
|
||||
ResolveCameraFinalColorPolicies(requests);
|
||||
AttachFullscreenStageRequests(context, requests);
|
||||
return requests;
|
||||
return m_framePlanBuilder != nullptr
|
||||
? m_framePlanBuilder->BuildPlans(requests, GetPipelineAsset())
|
||||
: std::vector<CameraFramePlan>();
|
||||
}
|
||||
|
||||
bool SceneRenderer::Render(const CameraRenderRequest& request) {
|
||||
return m_cameraRenderer.Render(request);
|
||||
bool SceneRenderer::Render(const CameraFramePlan& plan) {
|
||||
return m_cameraRenderer.Render(plan);
|
||||
}
|
||||
|
||||
bool SceneRenderer::Render(const std::vector<CameraRenderRequest>& requests) {
|
||||
if (requests.empty()) {
|
||||
bool SceneRenderer::Render(const std::vector<CameraFramePlan>& plans) {
|
||||
if (plans.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const CameraRenderRequest& request : requests) {
|
||||
if (!request.IsValid()) {
|
||||
for (const CameraFramePlan& plan : plans) {
|
||||
if (!plan.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CameraRenderRequest> sortedRequests = requests;
|
||||
SceneRenderRequestUtils::SortCameraRenderRequests(sortedRequests);
|
||||
std::vector<CameraFramePlan> sortedPlans = plans;
|
||||
std::stable_sort(
|
||||
sortedPlans.begin(),
|
||||
sortedPlans.end(),
|
||||
CompareCameraFramePlans);
|
||||
|
||||
bool rendered = false;
|
||||
for (const CameraRenderRequest& request : sortedRequests) {
|
||||
if (!m_cameraRenderer.Render(request)) {
|
||||
for (const CameraFramePlan& plan : sortedPlans) {
|
||||
if (!m_cameraRenderer.Render(plan)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateTrackedFullscreenSurfaceState(
|
||||
m_ownedFullscreenStageSurfaces,
|
||||
&request.GetMainSceneSurface());
|
||||
if (request.postProcess.IsRequested()) {
|
||||
UpdateTrackedFullscreenSurfaceState(
|
||||
m_ownedFullscreenStageSurfaces,
|
||||
&request.postProcess.destinationSurface);
|
||||
if (m_framePlanBuilder != nullptr) {
|
||||
m_framePlanBuilder->UpdateTrackedSurfaceState(&plan.GetMainSceneSurface());
|
||||
}
|
||||
if (plan.postProcess.IsRequested()) {
|
||||
if (m_framePlanBuilder != nullptr) {
|
||||
m_framePlanBuilder->UpdateTrackedSurfaceState(&plan.postProcess.destinationSurface);
|
||||
}
|
||||
}
|
||||
|
||||
rendered = true;
|
||||
@@ -145,133 +103,7 @@ bool SceneRenderer::Render(
|
||||
Components::CameraComponent* overrideCamera,
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface) {
|
||||
return Render(BuildRenderRequests(scene, overrideCamera, context, surface));
|
||||
}
|
||||
|
||||
void SceneRenderer::PrepareOwnedFullscreenStageState(size_t requestCount) {
|
||||
m_ownedPostProcessSequences.clear();
|
||||
m_ownedPostProcessSequences.resize(requestCount);
|
||||
m_ownedFinalOutputSequences.clear();
|
||||
m_ownedFinalOutputSequences.resize(requestCount);
|
||||
|
||||
if (m_ownedFullscreenStageSurfaces.size() < requestCount) {
|
||||
m_ownedFullscreenStageSurfaces.resize(requestCount);
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < requestCount; ++index) {
|
||||
if (m_ownedFullscreenStageSurfaces[index] == nullptr) {
|
||||
m_ownedFullscreenStageSurfaces[index] = std::make_unique<FullscreenPassSurfaceCache>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRenderer::ResolveCameraFinalColorPolicies(
|
||||
std::vector<CameraRenderRequest>& requests) const {
|
||||
const RenderPipelineAsset* pipelineAsset = GetPipelineAsset();
|
||||
const FinalColorSettings pipelineDefaults =
|
||||
pipelineAsset != nullptr ? pipelineAsset->GetDefaultFinalColorSettings() : FinalColorSettings();
|
||||
|
||||
for (CameraRenderRequest& request : requests) {
|
||||
if (request.camera == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
request.finalColorPolicy = ResolveFinalColorPolicy(
|
||||
pipelineDefaults,
|
||||
&request.camera->GetFinalColorOverrides());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRenderer::AttachFullscreenStageRequests(
|
||||
const RenderContext& context,
|
||||
std::vector<CameraRenderRequest>& requests) {
|
||||
PrepareOwnedFullscreenStageState(requests.size());
|
||||
|
||||
for (size_t index = 0; index < requests.size(); ++index) {
|
||||
CameraRenderRequest& request = requests[index];
|
||||
if (request.camera == nullptr ||
|
||||
request.context.device == nullptr ||
|
||||
!HasValidColorTarget(request.surface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPassSequence> postProcessSequence =
|
||||
BuildCameraPostProcessPassSequence(request.camera->GetPostProcessPasses());
|
||||
std::unique_ptr<RenderPassSequence> finalOutputSequence =
|
||||
BuildFinalColorPassSequence(request.finalColorPolicy);
|
||||
|
||||
const bool hasPostProcess =
|
||||
postProcessSequence != nullptr && postProcessSequence->GetPassCount() > 0u;
|
||||
const bool hasFinalOutput =
|
||||
finalOutputSequence != nullptr && finalOutputSequence->GetPassCount() > 0u;
|
||||
if (!hasPostProcess && !hasFinalOutput) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (request.surface.GetSampleCount() > 1u) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"SceneRenderer fullscreen post-process/final-output chain currently requires a single-sample main scene surface");
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = request.surface.GetColorAttachments();
|
||||
const RHI::Format colorFormat = colorAttachments[0]->GetFormat();
|
||||
if (colorFormat == RHI::Format::Unknown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t fullscreenSurfaceCount = hasPostProcess && hasFinalOutput ? 2u : 1u;
|
||||
FullscreenPassSurfaceCache* surfaceCache = m_ownedFullscreenStageSurfaces[index].get();
|
||||
if (surfaceCache == nullptr ||
|
||||
!surfaceCache->EnsureSurfaces(
|
||||
context,
|
||||
request.surface.GetWidth(),
|
||||
request.surface.GetHeight(),
|
||||
colorFormat,
|
||||
fullscreenSurfaceCount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry* sceneColorEntry = surfaceCache->GetSurfaceEntry(0u);
|
||||
if (sceneColorEntry == nullptr || sceneColorEntry->shaderResourceView == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry* postProcessOutputEntry =
|
||||
hasPostProcess && hasFinalOutput ? surfaceCache->GetSurfaceEntry(1u) : nullptr;
|
||||
if (hasPostProcess && hasFinalOutput &&
|
||||
(postProcessOutputEntry == nullptr || postProcessOutputEntry->shaderResourceView == nullptr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasPostProcess) {
|
||||
request.postProcess.sourceSurface =
|
||||
ConfigureFullscreenStageSurface(*sceneColorEntry, request.surface, true);
|
||||
request.postProcess.sourceColorView = sceneColorEntry->shaderResourceView;
|
||||
request.postProcess.sourceColorState = request.postProcess.sourceSurface.GetColorStateAfter();
|
||||
request.postProcess.destinationSurface =
|
||||
hasFinalOutput
|
||||
? ConfigureFullscreenStageSurface(*postProcessOutputEntry, request.surface, false)
|
||||
: request.surface;
|
||||
m_ownedPostProcessSequences[index] = std::move(postProcessSequence);
|
||||
request.postProcess.passes = m_ownedPostProcessSequences[index].get();
|
||||
}
|
||||
|
||||
if (hasFinalOutput) {
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry* finalOutputSourceEntry =
|
||||
hasPostProcess ? postProcessOutputEntry : sceneColorEntry;
|
||||
request.finalOutput.sourceSurface =
|
||||
hasPostProcess
|
||||
? request.postProcess.destinationSurface
|
||||
: ConfigureFullscreenStageSurface(*sceneColorEntry, request.surface, true);
|
||||
request.finalOutput.sourceColorView = finalOutputSourceEntry->shaderResourceView;
|
||||
request.finalOutput.sourceColorState = request.finalOutput.sourceSurface.GetColorStateAfter();
|
||||
request.finalOutput.destinationSurface = request.surface;
|
||||
m_ownedFinalOutputSequences[index] = std::move(finalOutputSequence);
|
||||
request.finalOutput.passes = m_ownedFinalOutputSequences[index].get();
|
||||
}
|
||||
}
|
||||
return Render(BuildFramePlans(scene, overrideCamera, context, surface));
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Components/CameraComponent.h"
|
||||
#include "Components/GameObject.h"
|
||||
#include "Components/LightComponent.h"
|
||||
#include "Rendering/FrameData/RendererListUtils.h"
|
||||
#include "Rendering/Extraction/RenderSceneUtility.h"
|
||||
#include "Scene/Scene.h"
|
||||
|
||||
@@ -125,6 +126,21 @@ bool CompareVisibleGaussianSplats(
|
||||
return CompareVisibleGaussianSplatsStable(lhs, rhs);
|
||||
}
|
||||
|
||||
void BuildRendererLists(RenderSceneData& sceneData) {
|
||||
sceneData.cullingResults.Clear();
|
||||
sceneData.cullingResults.rendererLists.reserve(5u);
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::AllVisible, sceneData.visibleItems));
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::Opaque, sceneData.visibleItems));
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::Transparent, sceneData.visibleItems));
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::ShadowCaster, sceneData.visibleItems));
|
||||
sceneData.cullingResults.rendererLists.push_back(
|
||||
BuildRendererList(RendererListType::ObjectId, sceneData.visibleItems));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderSceneData RenderSceneExtractor::Extract(
|
||||
@@ -165,6 +181,7 @@ RenderSceneData RenderSceneExtractor::Extract(
|
||||
sceneData.visibleVolumes.begin(),
|
||||
sceneData.visibleVolumes.end(),
|
||||
CompareVisibleVolumes);
|
||||
BuildRendererLists(sceneData);
|
||||
ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting);
|
||||
|
||||
return sceneData;
|
||||
@@ -208,6 +225,7 @@ RenderSceneData RenderSceneExtractor::ExtractForCamera(
|
||||
sceneData.visibleVolumes.begin(),
|
||||
sceneData.visibleVolumes.end(),
|
||||
CompareVisibleVolumes);
|
||||
BuildRendererLists(sceneData);
|
||||
ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting);
|
||||
|
||||
return sceneData;
|
||||
|
||||
120
engine/src/Rendering/Graph/RenderGraph.cpp
Normal file
120
engine/src/Rendering/Graph/RenderGraph.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include <XCEngine/Rendering/Graph/RenderGraph.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
void RenderGraph::Reset() {
|
||||
m_textures.clear();
|
||||
m_passes.clear();
|
||||
}
|
||||
|
||||
RenderGraphPassBuilder::RenderGraphPassBuilder(
|
||||
RenderGraph* graph,
|
||||
RenderGraphPassHandle passHandle)
|
||||
: m_graph(graph)
|
||||
, m_passHandle(passHandle) {
|
||||
}
|
||||
|
||||
void RenderGraphPassBuilder::ReadTexture(RenderGraphTextureHandle texture) {
|
||||
if (m_graph == nullptr || !m_passHandle.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderGraph::TextureAccess access = {};
|
||||
access.texture = texture;
|
||||
access.mode = RenderGraphAccessMode::Read;
|
||||
m_graph->m_passes[m_passHandle.index].accesses.push_back(access);
|
||||
}
|
||||
|
||||
void RenderGraphPassBuilder::WriteTexture(RenderGraphTextureHandle texture) {
|
||||
if (m_graph == nullptr || !m_passHandle.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderGraph::TextureAccess access = {};
|
||||
access.texture = texture;
|
||||
access.mode = RenderGraphAccessMode::Write;
|
||||
m_graph->m_passes[m_passHandle.index].accesses.push_back(access);
|
||||
}
|
||||
|
||||
void RenderGraphPassBuilder::SetExecuteCallback(RenderGraphExecuteCallback callback) {
|
||||
if (m_graph == nullptr || !m_passHandle.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_graph->m_passes[m_passHandle.index].executeCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void RenderGraphBuilder::Reset() {
|
||||
m_graph.Reset();
|
||||
}
|
||||
|
||||
RenderGraphTextureHandle RenderGraphBuilder::ImportTexture(
|
||||
const Containers::String& name,
|
||||
const RenderGraphTextureDesc& desc,
|
||||
RHI::RHIResourceView* importedView,
|
||||
const RenderGraphImportedTextureOptions& importedOptions) {
|
||||
RenderGraph::TextureResource resource = {};
|
||||
resource.name = name;
|
||||
resource.desc = desc;
|
||||
resource.kind = RenderGraphTextureKind::Imported;
|
||||
resource.importedView = importedView;
|
||||
resource.importedOptions = importedOptions;
|
||||
m_graph.m_textures.push_back(resource);
|
||||
|
||||
RenderGraphTextureHandle handle = {};
|
||||
handle.index = static_cast<Core::uint32>(m_graph.m_textures.size() - 1u);
|
||||
return handle;
|
||||
}
|
||||
|
||||
RenderGraphTextureHandle RenderGraphBuilder::CreateTransientTexture(
|
||||
const Containers::String& name,
|
||||
const RenderGraphTextureDesc& desc) {
|
||||
RenderGraph::TextureResource resource = {};
|
||||
resource.name = name;
|
||||
resource.desc = desc;
|
||||
resource.kind = RenderGraphTextureKind::Transient;
|
||||
resource.importedView = nullptr;
|
||||
m_graph.m_textures.push_back(resource);
|
||||
|
||||
RenderGraphTextureHandle handle = {};
|
||||
handle.index = static_cast<Core::uint32>(m_graph.m_textures.size() - 1u);
|
||||
return handle;
|
||||
}
|
||||
|
||||
RenderGraphPassHandle RenderGraphBuilder::AddRasterPass(
|
||||
const Containers::String& name,
|
||||
const std::function<void(RenderGraphPassBuilder&)>& setup) {
|
||||
return AddPass(name, RenderGraphPassType::Raster, setup);
|
||||
}
|
||||
|
||||
RenderGraphPassHandle RenderGraphBuilder::AddComputePass(
|
||||
const Containers::String& name,
|
||||
const std::function<void(RenderGraphPassBuilder&)>& setup) {
|
||||
return AddPass(name, RenderGraphPassType::Compute, setup);
|
||||
}
|
||||
|
||||
RenderGraphPassHandle RenderGraphBuilder::AddPass(
|
||||
const Containers::String& name,
|
||||
RenderGraphPassType type,
|
||||
const std::function<void(RenderGraphPassBuilder&)>& setup) {
|
||||
RenderGraph::PassNode pass = {};
|
||||
pass.name = name;
|
||||
pass.type = type;
|
||||
m_graph.m_passes.push_back(pass);
|
||||
|
||||
RenderGraphPassHandle handle = {};
|
||||
handle.index = static_cast<Core::uint32>(m_graph.m_passes.size() - 1u);
|
||||
|
||||
if (setup) {
|
||||
RenderGraphPassBuilder passBuilder(&m_graph, handle);
|
||||
setup(passBuilder);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
303
engine/src/Rendering/Graph/RenderGraphCompiler.cpp
Normal file
303
engine/src/Rendering/Graph/RenderGraphCompiler.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace {
|
||||
|
||||
RHI::ResourceStates ResolveRequiredState(
|
||||
RenderGraphPassType passType,
|
||||
RenderGraphAccessMode accessMode) {
|
||||
if (accessMode == RenderGraphAccessMode::Write) {
|
||||
return passType == RenderGraphPassType::Compute
|
||||
? RHI::ResourceStates::UnorderedAccess
|
||||
: RHI::ResourceStates::RenderTarget;
|
||||
}
|
||||
|
||||
return passType == RenderGraphPassType::Compute
|
||||
? RHI::ResourceStates::GenericRead
|
||||
: RHI::ResourceStates::PixelShaderResource;
|
||||
}
|
||||
|
||||
void WriteError(
|
||||
const Containers::String& message,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CompiledRenderGraph::Reset() {
|
||||
m_passes.clear();
|
||||
m_textures.clear();
|
||||
m_textureLifetimes.clear();
|
||||
m_textureTransitionPlans.clear();
|
||||
}
|
||||
|
||||
const Containers::String& CompiledRenderGraph::GetPassName(size_t index) const {
|
||||
static const Containers::String kEmptyString;
|
||||
return index < m_passes.size() ? m_passes[index].name : kEmptyString;
|
||||
}
|
||||
|
||||
RenderGraphPassType CompiledRenderGraph::GetPassType(size_t index) const {
|
||||
return index < m_passes.size()
|
||||
? m_passes[index].type
|
||||
: RenderGraphPassType::Raster;
|
||||
}
|
||||
|
||||
bool CompiledRenderGraph::TryGetTextureLifetime(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureLifetime& outLifetime) const {
|
||||
if (!handle.IsValid() || handle.index >= m_textureLifetimes.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outLifetime = m_textureLifetimes[handle.index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompiledRenderGraph::TryGetImportedTextureOptions(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphImportedTextureOptions& outOptions) const {
|
||||
if (!handle.IsValid() || handle.index >= m_textures.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_textures[handle.index];
|
||||
if (texture.kind != RenderGraphTextureKind::Imported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outOptions = texture.importedOptions;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompiledRenderGraph::TryGetTextureTransitionPlan(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureTransitionPlan& outPlan) const {
|
||||
if (!handle.IsValid() || handle.index >= m_textureTransitionPlans.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outPlan = m_textureTransitionPlans[handle.index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderGraphCompiler::Compile(
|
||||
const RenderGraph& graph,
|
||||
CompiledRenderGraph& outCompiledGraph,
|
||||
Containers::String* outErrorMessage) {
|
||||
outCompiledGraph.Reset();
|
||||
|
||||
for (const RenderGraph::TextureResource& texture : graph.m_textures) {
|
||||
if (!texture.desc.IsValid()) {
|
||||
WriteError(
|
||||
Containers::String("RenderGraph texture desc is invalid: ") + texture.name,
|
||||
outErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texture.kind == RenderGraphTextureKind::Imported &&
|
||||
texture.importedOptions.graphOwnsTransitions &&
|
||||
texture.importedView == nullptr) {
|
||||
WriteError(
|
||||
Containers::String("RenderGraph imported texture requires a valid view when graph owns transitions: ") +
|
||||
texture.name,
|
||||
outErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t passCount = graph.m_passes.size();
|
||||
const size_t textureCount = graph.m_textures.size();
|
||||
|
||||
std::vector<std::vector<Core::uint32>> outgoingEdges(passCount);
|
||||
std::vector<Core::uint32> incomingEdgeCount(passCount, 0u);
|
||||
std::unordered_set<Core::uint64> edgeKeys;
|
||||
std::vector<Core::uint32> lastWriter(textureCount, kInvalidRenderGraphHandle);
|
||||
std::vector<std::vector<Core::uint32>> lastReaders(textureCount);
|
||||
|
||||
auto addUniqueReader = [](
|
||||
std::vector<Core::uint32>& readers,
|
||||
Core::uint32 passIndex) {
|
||||
if (std::find(readers.begin(), readers.end(), passIndex) == readers.end()) {
|
||||
readers.push_back(passIndex);
|
||||
}
|
||||
};
|
||||
|
||||
auto addEdge = [&](
|
||||
Core::uint32 from,
|
||||
Core::uint32 to) {
|
||||
if (from == kInvalidRenderGraphHandle || to == kInvalidRenderGraphHandle || from == to) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Core::uint64 key =
|
||||
(static_cast<Core::uint64>(from) << 32u) | static_cast<Core::uint64>(to);
|
||||
if (!edgeKeys.insert(key).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingEdges[from].push_back(to);
|
||||
++incomingEdgeCount[to];
|
||||
};
|
||||
|
||||
for (Core::uint32 passIndex = 0u; passIndex < static_cast<Core::uint32>(passCount); ++passIndex) {
|
||||
const RenderGraph::PassNode& pass = graph.m_passes[passIndex];
|
||||
for (const RenderGraph::TextureAccess& access : pass.accesses) {
|
||||
if (!access.texture.IsValid() || access.texture.index >= textureCount) {
|
||||
WriteError(
|
||||
Containers::String("RenderGraph pass '") + pass.name +
|
||||
"' references an invalid texture handle",
|
||||
outErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderGraph::TextureResource& texture = graph.m_textures[access.texture.index];
|
||||
std::vector<Core::uint32>& readers = lastReaders[access.texture.index];
|
||||
Core::uint32& writer = lastWriter[access.texture.index];
|
||||
|
||||
if (access.mode == RenderGraphAccessMode::Read) {
|
||||
if (texture.kind == RenderGraphTextureKind::Transient &&
|
||||
writer == kInvalidRenderGraphHandle) {
|
||||
WriteError(
|
||||
Containers::String("RenderGraph transient texture '") + texture.name +
|
||||
"' is read before any pass writes it",
|
||||
outErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
addEdge(writer, passIndex);
|
||||
addUniqueReader(readers, passIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
addEdge(writer, passIndex);
|
||||
for (Core::uint32 readerPassIndex : readers) {
|
||||
addEdge(readerPassIndex, passIndex);
|
||||
}
|
||||
readers.clear();
|
||||
writer = passIndex;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Core::uint32> executionOrder;
|
||||
executionOrder.reserve(passCount);
|
||||
std::vector<bool> emitted(passCount, false);
|
||||
|
||||
while (executionOrder.size() < passCount) {
|
||||
bool progressed = false;
|
||||
for (Core::uint32 passIndex = 0u; passIndex < static_cast<Core::uint32>(passCount); ++passIndex) {
|
||||
if (emitted[passIndex] || incomingEdgeCount[passIndex] != 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
emitted[passIndex] = true;
|
||||
executionOrder.push_back(passIndex);
|
||||
for (Core::uint32 dependentPassIndex : outgoingEdges[passIndex]) {
|
||||
if (incomingEdgeCount[dependentPassIndex] > 0u) {
|
||||
--incomingEdgeCount[dependentPassIndex];
|
||||
}
|
||||
}
|
||||
|
||||
progressed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!progressed) {
|
||||
WriteError(
|
||||
"RenderGraph failed to compile because pass dependencies contain a cycle",
|
||||
outErrorMessage);
|
||||
outCompiledGraph.Reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outCompiledGraph.m_textureLifetimes.resize(textureCount);
|
||||
outCompiledGraph.m_textureTransitionPlans.resize(textureCount);
|
||||
for (size_t textureIndex = 0u; textureIndex < textureCount; ++textureIndex) {
|
||||
CompiledRenderGraph::CompiledTexture compiledTexture = {};
|
||||
compiledTexture.name = graph.m_textures[textureIndex].name;
|
||||
compiledTexture.desc = graph.m_textures[textureIndex].desc;
|
||||
compiledTexture.kind = graph.m_textures[textureIndex].kind;
|
||||
compiledTexture.importedView = graph.m_textures[textureIndex].importedView;
|
||||
compiledTexture.importedOptions = graph.m_textures[textureIndex].importedOptions;
|
||||
outCompiledGraph.m_textures.push_back(std::move(compiledTexture));
|
||||
|
||||
outCompiledGraph.m_textureLifetimes[textureIndex].kind = graph.m_textures[textureIndex].kind;
|
||||
RenderGraphTextureTransitionPlan& transitionPlan =
|
||||
outCompiledGraph.m_textureTransitionPlans[textureIndex];
|
||||
transitionPlan.graphOwnsTransitions =
|
||||
graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Transient ||
|
||||
graph.m_textures[textureIndex].importedOptions.graphOwnsTransitions;
|
||||
transitionPlan.initialState =
|
||||
graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Imported
|
||||
? graph.m_textures[textureIndex].importedOptions.initialState
|
||||
: RHI::ResourceStates::Common;
|
||||
transitionPlan.finalState =
|
||||
graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Imported
|
||||
? graph.m_textures[textureIndex].importedOptions.finalState
|
||||
: RHI::ResourceStates::Common;
|
||||
}
|
||||
|
||||
outCompiledGraph.m_passes.reserve(passCount);
|
||||
for (Core::uint32 compiledPassIndex = 0u;
|
||||
compiledPassIndex < static_cast<Core::uint32>(executionOrder.size());
|
||||
++compiledPassIndex) {
|
||||
const Core::uint32 originalPassIndex = executionOrder[compiledPassIndex];
|
||||
const RenderGraph::PassNode& sourcePass = graph.m_passes[originalPassIndex];
|
||||
|
||||
CompiledRenderGraph::CompiledPass compiledPass = {};
|
||||
compiledPass.name = sourcePass.name;
|
||||
compiledPass.type = sourcePass.type;
|
||||
compiledPass.originalPassIndex = originalPassIndex;
|
||||
compiledPass.accesses.reserve(sourcePass.accesses.size());
|
||||
for (const RenderGraph::TextureAccess& access : sourcePass.accesses) {
|
||||
CompiledRenderGraph::CompiledTextureAccess compiledAccess = {};
|
||||
compiledAccess.texture = access.texture;
|
||||
compiledAccess.mode = access.mode;
|
||||
compiledAccess.requiredState =
|
||||
ResolveRequiredState(sourcePass.type, access.mode);
|
||||
compiledPass.accesses.push_back(compiledAccess);
|
||||
}
|
||||
compiledPass.executeCallback = sourcePass.executeCallback;
|
||||
outCompiledGraph.m_passes.push_back(std::move(compiledPass));
|
||||
|
||||
for (const RenderGraph::TextureAccess& access : sourcePass.accesses) {
|
||||
RenderGraphTextureLifetime& lifetime =
|
||||
outCompiledGraph.m_textureLifetimes[access.texture.index];
|
||||
RenderGraphTextureTransitionPlan& transitionPlan =
|
||||
outCompiledGraph.m_textureTransitionPlans[access.texture.index];
|
||||
if (!lifetime.used) {
|
||||
lifetime.used = true;
|
||||
lifetime.firstPassIndex = compiledPassIndex;
|
||||
}
|
||||
lifetime.lastPassIndex = compiledPassIndex;
|
||||
|
||||
const RHI::ResourceStates accessState =
|
||||
ResolveRequiredState(sourcePass.type, access.mode);
|
||||
if (!transitionPlan.hasFirstAccessState) {
|
||||
transitionPlan.hasFirstAccessState = true;
|
||||
transitionPlan.firstAccessState = accessState;
|
||||
}
|
||||
transitionPlan.hasLastAccessState = true;
|
||||
transitionPlan.lastAccessState = accessState;
|
||||
if (graph.m_textures[access.texture.index].kind == RenderGraphTextureKind::Transient) {
|
||||
transitionPlan.finalState = accessState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outErrorMessage != nullptr) {
|
||||
outErrorMessage->Clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
357
engine/src/Rendering/Graph/RenderGraphExecutor.cpp
Normal file
357
engine/src/Rendering/Graph/RenderGraphExecutor.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
#include <XCEngine/Rendering/Graph/RenderGraphExecutor.h>
|
||||
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace {
|
||||
|
||||
bool IsGraphManagedTransientState(RHI::ResourceStates state) {
|
||||
return state == RHI::ResourceStates::Common ||
|
||||
state == RHI::ResourceStates::RenderTarget ||
|
||||
state == RHI::ResourceStates::PixelShaderResource ||
|
||||
state == RHI::ResourceStates::GenericRead ||
|
||||
state == RHI::ResourceStates::Present;
|
||||
}
|
||||
|
||||
RenderGraphTextureViewType ResolveBarrierViewType(RHI::ResourceStates state) {
|
||||
return state == RHI::ResourceStates::RenderTarget
|
||||
? RenderGraphTextureViewType::RenderTarget
|
||||
: RenderGraphTextureViewType::ShaderResource;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class RenderGraphRuntimeResources {
|
||||
public:
|
||||
explicit RenderGraphRuntimeResources(const CompiledRenderGraph& graph)
|
||||
: m_graph(graph)
|
||||
, m_textureAllocations(graph.m_textures.size())
|
||||
, m_textureStates(
|
||||
graph.m_textures.size(),
|
||||
RHI::ResourceStates::Common) {
|
||||
}
|
||||
|
||||
~RenderGraphRuntimeResources() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool Initialize(
|
||||
const RenderContext& renderContext,
|
||||
Containers::String* outErrorMessage) {
|
||||
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
|
||||
const RenderGraphTextureLifetime& lifetime = m_graph.m_textureLifetimes[textureIndex];
|
||||
if (texture.kind == RenderGraphTextureKind::Imported) {
|
||||
m_textureStates[textureIndex] = texture.importedOptions.initialState;
|
||||
}
|
||||
|
||||
if (texture.kind != RenderGraphTextureKind::Transient || !lifetime.used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderContext.device == nullptr) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("RenderGraph cannot allocate transient texture without a valid device: ") +
|
||||
texture.name;
|
||||
}
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateTransientTexture(renderContext, texture, m_textureAllocations[textureIndex])) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("RenderGraph failed to allocate transient texture: ") +
|
||||
texture.name;
|
||||
}
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* ResolveTextureView(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureViewType viewType) const {
|
||||
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[handle.index];
|
||||
if (texture.kind == RenderGraphTextureKind::Imported) {
|
||||
return texture.importedView;
|
||||
}
|
||||
|
||||
const TextureAllocation& allocation = m_textureAllocations[handle.index];
|
||||
switch (viewType) {
|
||||
case RenderGraphTextureViewType::RenderTarget:
|
||||
return allocation.renderTargetView;
|
||||
case RenderGraphTextureViewType::ShaderResource:
|
||||
return allocation.shaderResourceView;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryGetTextureDesc(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureDesc& outDesc) const {
|
||||
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outDesc = m_graph.m_textures[handle.index].desc;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsTransientTexture(RenderGraphTextureHandle handle) const {
|
||||
return handle.IsValid() &&
|
||||
handle.index < m_graph.m_textures.size() &&
|
||||
m_graph.m_textures[handle.index].kind == RenderGraphTextureKind::Transient;
|
||||
}
|
||||
|
||||
bool TransitionGraphOwnedImportsToFinalStates(
|
||||
const RenderContext& renderContext,
|
||||
Containers::String* outErrorMessage) {
|
||||
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
|
||||
if (texture.kind != RenderGraphTextureKind::Imported ||
|
||||
!texture.importedOptions.graphOwnsTransitions ||
|
||||
texture.importedView == nullptr ||
|
||||
textureIndex >= m_graph.m_textureLifetimes.size() ||
|
||||
!m_graph.m_textureLifetimes[textureIndex].used ||
|
||||
!IsGraphManagedTransientState(texture.importedOptions.finalState)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TransitionTexture(
|
||||
{ static_cast<Core::uint32>(textureIndex) },
|
||||
texture.importedOptions.finalState,
|
||||
renderContext,
|
||||
outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransitionPassResources(
|
||||
const CompiledRenderGraph::CompiledPass& pass,
|
||||
const RenderContext& renderContext,
|
||||
Containers::String* outErrorMessage) {
|
||||
for (const CompiledRenderGraph::CompiledTextureAccess& access : pass.accesses) {
|
||||
if (!access.texture.IsValid() ||
|
||||
access.texture.index >= m_graph.m_textures.size() ||
|
||||
!ShouldGraphManageTransitions(access.texture) ||
|
||||
!IsGraphManagedTransientState(access.requiredState)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TransitionTexture(
|
||||
access.texture,
|
||||
access.requiredState,
|
||||
renderContext,
|
||||
outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
struct TextureAllocation {
|
||||
RHI::RHITexture* texture = nullptr;
|
||||
RHI::RHIResourceView* renderTargetView = nullptr;
|
||||
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||
};
|
||||
|
||||
bool ShouldGraphManageTransitions(RenderGraphTextureHandle handle) const {
|
||||
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[handle.index];
|
||||
return texture.kind == RenderGraphTextureKind::Transient ||
|
||||
(texture.kind == RenderGraphTextureKind::Imported &&
|
||||
texture.importedOptions.graphOwnsTransitions);
|
||||
}
|
||||
|
||||
bool TransitionTexture(
|
||||
RenderGraphTextureHandle handle,
|
||||
RHI::ResourceStates targetState,
|
||||
const RenderContext& renderContext,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (renderContext.commandList == nullptr) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("RenderGraph cannot transition texture without a valid command list: ") +
|
||||
m_graph.m_textures[handle.index].name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::ResourceStates& currentState = m_textureStates[handle.index];
|
||||
if (currentState == targetState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* resourceView =
|
||||
ResolveTextureView(handle, ResolveBarrierViewType(targetState));
|
||||
if (resourceView == nullptr) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("RenderGraph cannot resolve texture view for state transition: ") +
|
||||
m_graph.m_textures[handle.index].name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
resourceView,
|
||||
currentState,
|
||||
targetState);
|
||||
currentState = targetState;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DestroyTextureAllocation(TextureAllocation& allocation) {
|
||||
if (allocation.renderTargetView != nullptr) {
|
||||
allocation.renderTargetView->Shutdown();
|
||||
delete allocation.renderTargetView;
|
||||
allocation.renderTargetView = nullptr;
|
||||
}
|
||||
|
||||
if (allocation.shaderResourceView != nullptr) {
|
||||
allocation.shaderResourceView->Shutdown();
|
||||
delete allocation.shaderResourceView;
|
||||
allocation.shaderResourceView = nullptr;
|
||||
}
|
||||
|
||||
if (allocation.texture != nullptr) {
|
||||
allocation.texture->Shutdown();
|
||||
delete allocation.texture;
|
||||
allocation.texture = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool CreateTransientTexture(
|
||||
const RenderContext& renderContext,
|
||||
const CompiledRenderGraph::CompiledTexture& texture,
|
||||
TextureAllocation& allocation) {
|
||||
RHI::TextureDesc textureDesc = {};
|
||||
textureDesc.width = texture.desc.width;
|
||||
textureDesc.height = texture.desc.height;
|
||||
textureDesc.depth = 1u;
|
||||
textureDesc.mipLevels = 1u;
|
||||
textureDesc.arraySize = 1u;
|
||||
textureDesc.format = texture.desc.format;
|
||||
textureDesc.textureType = texture.desc.textureType;
|
||||
textureDesc.sampleCount = texture.desc.sampleCount;
|
||||
textureDesc.sampleQuality = texture.desc.sampleQuality;
|
||||
textureDesc.flags = 0u;
|
||||
|
||||
allocation.texture = renderContext.device->CreateTexture(textureDesc);
|
||||
if (allocation.texture == nullptr) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = texture.desc.format;
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||
viewDesc.mipLevel = 0u;
|
||||
|
||||
allocation.renderTargetView =
|
||||
renderContext.device->CreateRenderTargetView(allocation.texture, viewDesc);
|
||||
if (allocation.renderTargetView == nullptr) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
allocation.shaderResourceView =
|
||||
renderContext.device->CreateShaderResourceView(allocation.texture, viewDesc);
|
||||
if (allocation.shaderResourceView == nullptr) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
for (TextureAllocation& allocation : m_textureAllocations) {
|
||||
DestroyTextureAllocation(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
const CompiledRenderGraph& m_graph;
|
||||
std::vector<TextureAllocation> m_textureAllocations;
|
||||
std::vector<RHI::ResourceStates> m_textureStates;
|
||||
};
|
||||
|
||||
RHI::RHIResourceView* RenderGraphExecutionContext::ResolveTextureView(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureViewType viewType) const {
|
||||
return runtimeResources != nullptr
|
||||
? runtimeResources->ResolveTextureView(handle, viewType)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool RenderGraphExecutionContext::TryGetTextureDesc(
|
||||
RenderGraphTextureHandle handle,
|
||||
RenderGraphTextureDesc& outDesc) const {
|
||||
return runtimeResources != nullptr &&
|
||||
runtimeResources->TryGetTextureDesc(handle, outDesc);
|
||||
}
|
||||
|
||||
bool RenderGraphExecutionContext::IsTransientTexture(RenderGraphTextureHandle handle) const {
|
||||
return runtimeResources != nullptr &&
|
||||
runtimeResources->IsTransientTexture(handle);
|
||||
}
|
||||
|
||||
bool RenderGraphExecutor::Execute(
|
||||
const CompiledRenderGraph& graph,
|
||||
const RenderContext& renderContext,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
outErrorMessage->Clear();
|
||||
}
|
||||
|
||||
RenderGraphRuntimeResources runtimeResources(graph);
|
||||
if (!runtimeResources.Initialize(renderContext, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderGraphExecutionContext executionContext = {
|
||||
renderContext,
|
||||
&runtimeResources
|
||||
};
|
||||
for (const CompiledRenderGraph::CompiledPass& pass : graph.m_passes) {
|
||||
if (!runtimeResources.TransitionPassResources(pass, renderContext, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pass.executeCallback) {
|
||||
pass.executeCallback(executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (!runtimeResources.TransitionGraphOwnedImportsToFinalStates(
|
||||
renderContext,
|
||||
outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -276,42 +276,97 @@ inline bool TryBuildRuntimeShaderBindings(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sortBindingIndices = [&outBindings](auto&& predicate) {
|
||||
std::vector<size_t> indices;
|
||||
indices.reserve(outBindings.Size());
|
||||
for (size_t index = 0; index < outBindings.Size(); ++index) {
|
||||
if (predicate(outBindings[index])) {
|
||||
indices.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(
|
||||
indices.begin(),
|
||||
indices.end(),
|
||||
[&outBindings](size_t leftIndex, size_t rightIndex) {
|
||||
const Resources::ShaderResourceBindingDesc& left = outBindings[leftIndex];
|
||||
const Resources::ShaderResourceBindingDesc& right = outBindings[rightIndex];
|
||||
if (left.set != right.set) {
|
||||
return left.set < right.set;
|
||||
}
|
||||
return left.binding < right.binding;
|
||||
});
|
||||
return indices;
|
||||
};
|
||||
|
||||
Core::uint32 nextConstantBufferRegister = 0;
|
||||
Core::uint32 nextTextureRegister = 0;
|
||||
Core::uint32 nextSamplerRegister = 0;
|
||||
Core::uint32 nextUnorderedAccessRegister = 0;
|
||||
Core::uint32 nextStorageBufferRegister = 0;
|
||||
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
|
||||
binding.set = 0;
|
||||
switch (binding.type) {
|
||||
case Resources::ShaderResourceType::ConstantBuffer:
|
||||
binding.binding = nextConstantBufferRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Texture2D:
|
||||
case Resources::ShaderResourceType::TextureCube:
|
||||
binding.binding = nextTextureRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::StructuredBuffer:
|
||||
case Resources::ShaderResourceType::RawBuffer:
|
||||
binding.binding =
|
||||
backend == Resources::ShaderBackend::OpenGL
|
||||
? nextStorageBufferRegister++
|
||||
: nextTextureRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Sampler:
|
||||
binding.binding = nextSamplerRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
||||
case Resources::ShaderResourceType::RWRawBuffer:
|
||||
binding.binding =
|
||||
backend == Resources::ShaderBackend::OpenGL
|
||||
? nextStorageBufferRegister++
|
||||
: nextUnorderedAccessRegister++;
|
||||
break;
|
||||
default:
|
||||
binding.binding = nextUnorderedAccessRegister++;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto constantBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||
return binding.type == Resources::ShaderResourceType::ConstantBuffer;
|
||||
});
|
||||
for (size_t index : constantBufferIndices) {
|
||||
outBindings[index].set = 0;
|
||||
outBindings[index].binding = nextConstantBufferRegister++;
|
||||
}
|
||||
|
||||
const auto textureIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||
return binding.type == Resources::ShaderResourceType::Texture2D ||
|
||||
binding.type == Resources::ShaderResourceType::TextureCube;
|
||||
});
|
||||
for (size_t index : textureIndices) {
|
||||
outBindings[index].set = 0;
|
||||
outBindings[index].binding = nextTextureRegister++;
|
||||
}
|
||||
|
||||
const auto srvBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||
return binding.type == Resources::ShaderResourceType::StructuredBuffer ||
|
||||
binding.type == Resources::ShaderResourceType::RawBuffer;
|
||||
});
|
||||
for (size_t index : srvBufferIndices) {
|
||||
outBindings[index].set = 0;
|
||||
outBindings[index].binding =
|
||||
backend == Resources::ShaderBackend::OpenGL
|
||||
? nextStorageBufferRegister++
|
||||
: nextTextureRegister++;
|
||||
}
|
||||
|
||||
const auto samplerIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||
return binding.type == Resources::ShaderResourceType::Sampler;
|
||||
});
|
||||
for (size_t index : samplerIndices) {
|
||||
outBindings[index].set = 0;
|
||||
outBindings[index].binding = nextSamplerRegister++;
|
||||
}
|
||||
|
||||
const auto uavIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||
return binding.type == Resources::ShaderResourceType::RWStructuredBuffer ||
|
||||
binding.type == Resources::ShaderResourceType::RWRawBuffer;
|
||||
});
|
||||
for (size_t index : uavIndices) {
|
||||
outBindings[index].set = 0;
|
||||
outBindings[index].binding =
|
||||
backend == Resources::ShaderBackend::OpenGL
|
||||
? nextStorageBufferRegister++
|
||||
: nextUnorderedAccessRegister++;
|
||||
}
|
||||
|
||||
const auto fallbackUavIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||
return binding.type != Resources::ShaderResourceType::ConstantBuffer &&
|
||||
binding.type != Resources::ShaderResourceType::Texture2D &&
|
||||
binding.type != Resources::ShaderResourceType::TextureCube &&
|
||||
binding.type != Resources::ShaderResourceType::StructuredBuffer &&
|
||||
binding.type != Resources::ShaderResourceType::RawBuffer &&
|
||||
binding.type != Resources::ShaderResourceType::Sampler &&
|
||||
binding.type != Resources::ShaderResourceType::RWStructuredBuffer &&
|
||||
binding.type != Resources::ShaderResourceType::RWRawBuffer;
|
||||
});
|
||||
for (size_t index : fallbackUavIndices) {
|
||||
outBindings[index].set = 0;
|
||||
outBindings[index].binding = nextUnorderedAccessRegister++;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||
#include "Rendering/FrameData/RendererListUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Resources/Mesh/Mesh.h"
|
||||
@@ -139,13 +140,16 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
||||
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
|
||||
for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) {
|
||||
if (!ShouldRenderVisibleItem(visibleItem)) {
|
||||
continue;
|
||||
}
|
||||
VisitRendererListVisibleItems(
|
||||
context.sceneData,
|
||||
GetRendererListType(),
|
||||
[&](const VisibleRenderItem& visibleItem) {
|
||||
if (!ShouldRenderVisibleItem(visibleItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DrawVisibleItem(context.renderContext, context.surface, context.sceneData, visibleItem);
|
||||
}
|
||||
DrawVisibleItem(context.renderContext, context.surface, context.sceneData, visibleItem);
|
||||
});
|
||||
|
||||
commandList->EndRenderPass();
|
||||
|
||||
@@ -165,6 +169,10 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RendererListType BuiltinDepthStylePassBase::GetRendererListType() const {
|
||||
return RendererListType::AllVisible;
|
||||
}
|
||||
|
||||
bool BuiltinDepthStylePassBase::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const {
|
||||
(void)visibleItem;
|
||||
return true;
|
||||
|
||||
@@ -222,6 +222,16 @@ bool BuiltinGaussianSplatPass::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context);
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::IsActive(const RenderSceneData& sceneData) const {
|
||||
return !sceneData.visibleGaussianSplats.empty();
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::Prepare(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
return PrepareGaussianSplatResources(context, sceneData);
|
||||
}
|
||||
|
||||
bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "Rendering/FrameData/RendererListUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
@@ -119,9 +120,12 @@ bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
commandList->SetPipelineState(m_pipelineState);
|
||||
|
||||
for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) {
|
||||
DrawVisibleItem(context.renderContext, context.sceneData, visibleItem);
|
||||
}
|
||||
VisitRendererListVisibleItems(
|
||||
context.sceneData,
|
||||
RendererListType::ObjectId,
|
||||
[&](const VisibleRenderItem& visibleItem) {
|
||||
DrawVisibleItem(context.renderContext, context.sceneData, visibleItem);
|
||||
});
|
||||
|
||||
commandList->EndRenderPass();
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ const char* BuiltinShadowCasterPass::GetName() const {
|
||||
return "BuiltinShadowCasterPass";
|
||||
}
|
||||
|
||||
RendererListType BuiltinShadowCasterPass::GetRendererListType() const {
|
||||
return RendererListType::ShadowCaster;
|
||||
}
|
||||
|
||||
bool BuiltinShadowCasterPass::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const {
|
||||
return visibleItem.meshRenderer == nullptr || visibleItem.meshRenderer->GetCastShadows();
|
||||
}
|
||||
|
||||
@@ -210,6 +210,16 @@ bool BuiltinVolumetricPass::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context);
|
||||
}
|
||||
|
||||
bool BuiltinVolumetricPass::IsActive(const RenderSceneData& sceneData) const {
|
||||
return !sceneData.visibleVolumes.empty();
|
||||
}
|
||||
|
||||
bool BuiltinVolumetricPass::Prepare(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
return PrepareVolumeResources(context, sceneData);
|
||||
}
|
||||
|
||||
bool BuiltinVolumetricPass::PrepareVolumeResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
|
||||
@@ -143,58 +143,31 @@ RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
|
||||
|
||||
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context) &&
|
||||
m_gaussianSplatPass != nullptr &&
|
||||
m_gaussianSplatPass->Initialize(context) &&
|
||||
m_volumetricPass != nullptr &&
|
||||
m_volumetricPass->Initialize(context);
|
||||
InitializeForwardSceneFeaturePasses(context);
|
||||
}
|
||||
|
||||
void BuiltinForwardPipeline::Shutdown() {
|
||||
if (m_gaussianSplatPass != nullptr) {
|
||||
m_gaussianSplatPass->Shutdown();
|
||||
}
|
||||
if (m_volumetricPass != nullptr) {
|
||||
m_volumetricPass->Shutdown();
|
||||
}
|
||||
ShutdownForwardSceneFeaturePasses();
|
||||
DestroyPipelineResources();
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::Render(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData) {
|
||||
if (!Initialize(context)) {
|
||||
const FrameExecutionContext& executionContext) {
|
||||
if (!Initialize(executionContext.renderContext)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: Initialize returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_volumetricPass != nullptr &&
|
||||
!sceneData.visibleVolumes.empty() &&
|
||||
!m_volumetricPass->PrepareVolumeResources(context, sceneData)) {
|
||||
if (!PrepareForwardSceneFeaturePasses(executionContext)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: PrepareVolumeResources returned false");
|
||||
return false;
|
||||
}
|
||||
if (m_gaussianSplatPass != nullptr &&
|
||||
!sceneData.visibleGaussianSplats.empty() &&
|
||||
!m_gaussianSplatPass->PrepareGaussianSplatResources(context, sceneData)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: PrepareGaussianSplatResources returned false");
|
||||
"BuiltinForwardPipeline::Render failed: PrepareForwardSceneFeaturePasses returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderPassContext passContext = {
|
||||
context,
|
||||
surface,
|
||||
sceneData,
|
||||
nullptr,
|
||||
nullptr,
|
||||
RHI::ResourceStates::Common
|
||||
};
|
||||
const RenderPassContext passContext = BuildRenderPassContext(executionContext);
|
||||
|
||||
if (!BeginForwardScenePass(passContext)) {
|
||||
Debug::Logger::Get().Error(
|
||||
@@ -203,53 +176,33 @@ bool BuiltinForwardPipeline::Render(
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool sampledDirectionalShadow = ShouldSampleMainDirectionalShadowMap(sceneData);
|
||||
const bool sampledDirectionalShadow =
|
||||
ShouldSampleMainDirectionalShadowMap(executionContext.sceneData);
|
||||
if (sampledDirectionalShadow) {
|
||||
TransitionMainDirectionalShadowForSampling(context, sceneData);
|
||||
TransitionMainDirectionalShadowForSampling(
|
||||
executionContext.renderContext,
|
||||
executionContext.sceneData);
|
||||
}
|
||||
|
||||
bool renderResult = ExecuteForwardOpaquePass(passContext);
|
||||
if (renderResult) {
|
||||
renderResult = ExecuteForwardSkyboxPass(passContext);
|
||||
if (!renderResult) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: ExecuteForwardSkyboxPass returned false");
|
||||
}
|
||||
}
|
||||
if (renderResult && m_gaussianSplatPass != nullptr) {
|
||||
renderResult = m_gaussianSplatPass->Execute(passContext);
|
||||
if (!renderResult) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: BuiltinGaussianSplatPass::Execute returned false");
|
||||
}
|
||||
}
|
||||
if (renderResult && m_volumetricPass != nullptr) {
|
||||
renderResult = m_volumetricPass->Execute(passContext);
|
||||
if (!renderResult) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: BuiltinVolumetricPass::Execute returned false");
|
||||
}
|
||||
}
|
||||
if (renderResult) {
|
||||
renderResult = ExecuteForwardTransparentPass(passContext);
|
||||
if (!renderResult) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinForwardPipeline::Render failed: ExecuteForwardTransparentPass returned false");
|
||||
}
|
||||
}
|
||||
const bool renderResult = ExecuteForwardScene(executionContext);
|
||||
|
||||
if (sampledDirectionalShadow) {
|
||||
RestoreMainDirectionalShadowAfterSampling(context, sceneData);
|
||||
RestoreMainDirectionalShadowAfterSampling(
|
||||
executionContext.renderContext,
|
||||
executionContext.sceneData);
|
||||
}
|
||||
EndForwardScenePass(passContext);
|
||||
|
||||
return renderResult;
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::Render(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData) {
|
||||
return Render(FrameExecutionContext(context, surface, sceneData));
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::BeginForwardScenePass(const RenderPassContext& passContext) {
|
||||
const RenderContext& context = passContext.renderContext;
|
||||
const RenderSurface& surface = passContext.surface;
|
||||
@@ -358,20 +311,18 @@ void BuiltinForwardPipeline::EndForwardScenePass(const RenderPassContext& passCo
|
||||
}
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& passContext) {
|
||||
const RenderContext& context = passContext.renderContext;
|
||||
const RenderSurface& surface = passContext.surface;
|
||||
const RenderSceneData& sceneData = passContext.sceneData;
|
||||
|
||||
return DrawVisibleItems(context, surface, sceneData, false);
|
||||
bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(
|
||||
const ScenePhaseExecutionContext& executionContext) {
|
||||
return DrawVisibleItems(
|
||||
executionContext.frameContext,
|
||||
BuildDrawSettings(executionContext.scenePhase));
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::ExecuteForwardTransparentPass(const RenderPassContext& passContext) {
|
||||
const RenderContext& context = passContext.renderContext;
|
||||
const RenderSurface& surface = passContext.surface;
|
||||
const RenderSceneData& sceneData = passContext.sceneData;
|
||||
|
||||
return DrawVisibleItems(context, surface, sceneData, true);
|
||||
bool BuiltinForwardPipeline::ExecuteForwardTransparentPass(
|
||||
const ScenePhaseExecutionContext& executionContext) {
|
||||
return DrawVisibleItems(
|
||||
executionContext.frameContext,
|
||||
BuildDrawSettings(executionContext.scenePhase));
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) {
|
||||
@@ -392,6 +343,146 @@ bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) {
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
BuiltinForwardPipeline::ForwardSceneFeaturePassArray
|
||||
BuiltinForwardPipeline::CollectForwardSceneFeaturePasses() const {
|
||||
return {
|
||||
m_gaussianSplatPass.get(),
|
||||
m_volumetricPass.get()
|
||||
};
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::InitializeForwardSceneFeaturePasses(const RenderContext& context) {
|
||||
for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) {
|
||||
if (featurePass == nullptr || !featurePass->Initialize(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuiltinForwardPipeline::ShutdownForwardSceneFeaturePasses() {
|
||||
for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) {
|
||||
if (featurePass != nullptr) {
|
||||
featurePass->Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::PrepareForwardSceneFeaturePasses(
|
||||
const FrameExecutionContext& executionContext) const {
|
||||
for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) {
|
||||
if (featurePass == nullptr || !featurePass->IsActive(executionContext.sceneData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!featurePass->Prepare(
|
||||
executionContext.renderContext,
|
||||
executionContext.sceneData)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String("BuiltinForwardPipeline feature prepare failed: ") +
|
||||
featurePass->GetName()).CStr());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::ExecuteForwardSceneFeaturePasses(
|
||||
const ScenePhaseExecutionContext& executionContext) const {
|
||||
const RenderPassContext passContext = BuildRenderPassContext(executionContext);
|
||||
|
||||
for (SceneRenderFeaturePass* featurePass : CollectForwardSceneFeaturePasses()) {
|
||||
if (featurePass == nullptr ||
|
||||
!featurePass->IsActive(executionContext.frameContext.sceneData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!featurePass->Execute(passContext)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String("BuiltinForwardPipeline feature execute failed: ") +
|
||||
featurePass->GetName()).CStr());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ScenePhaseExecutionContext BuiltinForwardPipeline::BuildScenePhaseExecutionContext(
|
||||
const FrameExecutionContext& executionContext,
|
||||
ScenePhase scenePhase) const {
|
||||
return ScenePhaseExecutionContext(
|
||||
executionContext,
|
||||
scenePhase,
|
||||
ShouldSampleMainDirectionalShadowMap(executionContext.sceneData));
|
||||
}
|
||||
|
||||
DrawSettings BuiltinForwardPipeline::BuildDrawSettings(ScenePhase scenePhase) const {
|
||||
DrawSettings drawSettings = {};
|
||||
drawSettings.scenePhase = scenePhase;
|
||||
switch (scenePhase) {
|
||||
case ScenePhase::Opaque:
|
||||
drawSettings.rendererListType = RendererListType::Opaque;
|
||||
break;
|
||||
case ScenePhase::Transparent:
|
||||
drawSettings.rendererListType = RendererListType::Transparent;
|
||||
break;
|
||||
default:
|
||||
drawSettings.rendererListType = RendererListType::AllVisible;
|
||||
break;
|
||||
}
|
||||
|
||||
return drawSettings;
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::ExecuteScenePhase(
|
||||
const ScenePhaseExecutionContext& executionContext) {
|
||||
switch (executionContext.scenePhase) {
|
||||
case ScenePhase::Opaque:
|
||||
return ExecuteForwardOpaquePass(executionContext);
|
||||
case ScenePhase::Skybox:
|
||||
return ExecuteForwardSkyboxPass(BuildRenderPassContext(executionContext));
|
||||
case ScenePhase::Feature:
|
||||
return ExecuteForwardSceneFeaturePasses(executionContext);
|
||||
case ScenePhase::Transparent:
|
||||
return ExecuteForwardTransparentPass(executionContext);
|
||||
default:
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String("BuiltinForwardPipeline::ExecuteScenePhase does not support scene phase: ") +
|
||||
ToString(executionContext.scenePhase)).CStr());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::ExecuteForwardScene(
|
||||
const FrameExecutionContext& executionContext) {
|
||||
static constexpr ScenePhase kForwardScenePhases[] = {
|
||||
ScenePhase::Opaque,
|
||||
ScenePhase::Skybox,
|
||||
ScenePhase::Feature,
|
||||
ScenePhase::Transparent
|
||||
};
|
||||
|
||||
for (ScenePhase scenePhase : kForwardScenePhases) {
|
||||
const ScenePhaseExecutionContext scenePhaseExecutionContext =
|
||||
BuildScenePhaseExecutionContext(executionContext, scenePhase);
|
||||
if (!ExecuteScenePhase(scenePhaseExecutionContext)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String("BuiltinForwardPipeline::ExecuteForwardScene failed during phase: ") +
|
||||
ToString(scenePhase)).CStr());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) {
|
||||
m_builtinForwardShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
||||
Resources::GetBuiltinForwardLitShaderPath());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "RHI/RHIDevice.h"
|
||||
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
||||
#include "Rendering/FrameData/RendererListUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/Materials/RenderMaterialResolve.h"
|
||||
@@ -687,10 +688,12 @@ BuiltinForwardPipeline::AdditionalLightConstants BuiltinForwardPipeline::BuildAd
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::DrawVisibleItem(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const FrameExecutionContext& executionContext,
|
||||
const VisibleRenderItem& visibleItem) {
|
||||
const RenderContext& context = executionContext.renderContext;
|
||||
const RenderSurface& surface = executionContext.surface;
|
||||
const RenderSceneData& sceneData = executionContext.sceneData;
|
||||
|
||||
(void)surface;
|
||||
const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh);
|
||||
if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) {
|
||||
@@ -861,36 +864,35 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
|
||||
}
|
||||
|
||||
bool BuiltinForwardPipeline::DrawVisibleItems(
|
||||
const RenderContext& context,
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
bool drawTransparentItems) {
|
||||
const FrameExecutionContext& executionContext,
|
||||
const DrawSettings& drawSettings) {
|
||||
const RenderContext& context = executionContext.renderContext;
|
||||
const RenderSurface& surface = executionContext.surface;
|
||||
const RenderSceneData& sceneData = executionContext.sceneData;
|
||||
|
||||
RHI::RHICommandList* commandList = context.commandList;
|
||||
RHI::RHIPipelineState* currentPipelineState = nullptr;
|
||||
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
|
||||
const bool isTransparentItem = IsTransparentRenderQueue(visibleItem.renderQueue);
|
||||
if (isTransparentItem != drawTransparentItems) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto drawVisibleItem = [&](const VisibleRenderItem& visibleItem) {
|
||||
const Resources::Material* material = ResolveMaterial(visibleItem);
|
||||
BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
|
||||
if (!TryResolveSurfacePassType(material, pass)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
||||
if (pipelineState == nullptr) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (pipelineState != currentPipelineState) {
|
||||
commandList->SetPipelineState(pipelineState);
|
||||
currentPipelineState = pipelineState;
|
||||
}
|
||||
|
||||
DrawVisibleItem(context, surface, sceneData, visibleItem);
|
||||
}
|
||||
DrawVisibleItem(executionContext, visibleItem);
|
||||
};
|
||||
|
||||
VisitRendererListVisibleItems(sceneData, drawSettings.rendererListType, drawVisibleItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
217
engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp
Normal file
217
engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "Rendering/Planning/CameraFramePlanBuilder.h"
|
||||
|
||||
#include "Components/CameraComponent.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||
#include "Rendering/Planning/CameraPostProcessPassFactory.h"
|
||||
#include "Rendering/Planning/FinalColorPassFactory.h"
|
||||
#include "Rendering/RenderPipelineAsset.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
namespace {
|
||||
|
||||
RenderSurface ConfigureFullscreenStageSurface(
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry& entry,
|
||||
const RenderSurface& templateSurface,
|
||||
bool copyDepthAttachment) {
|
||||
RenderSurface surface = entry.surface;
|
||||
if (copyDepthAttachment) {
|
||||
surface.SetDepthAttachment(templateSurface.GetDepthAttachment());
|
||||
surface.SetDepthStateBefore(templateSurface.GetDepthStateBefore());
|
||||
surface.SetDepthStateAfter(templateSurface.GetDepthStateAfter());
|
||||
if (templateSurface.HasClearColorOverride()) {
|
||||
surface.SetClearColorOverride(templateSurface.GetClearColorOverride());
|
||||
}
|
||||
}
|
||||
|
||||
if (templateSurface.HasCustomRenderArea()) {
|
||||
surface.SetRenderArea(templateSurface.GetRenderArea());
|
||||
} else {
|
||||
surface.ResetRenderArea();
|
||||
}
|
||||
|
||||
surface.SetColorStateBefore(entry.currentColorState);
|
||||
surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||
return surface;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<CameraFramePlan> CameraFramePlanBuilder::BuildPlans(
|
||||
const std::vector<CameraRenderRequest>& requests,
|
||||
const RenderPipelineAsset* pipelineAsset) {
|
||||
std::vector<CameraFramePlan> plans = CreatePlansFromRequests(requests);
|
||||
ResolveCameraFinalColorPolicies(plans, pipelineAsset);
|
||||
AttachFullscreenStageRequests(plans);
|
||||
return plans;
|
||||
}
|
||||
|
||||
void CameraFramePlanBuilder::UpdateTrackedSurfaceState(const RenderSurface* surface) {
|
||||
if (surface == nullptr || surface->GetColorAttachments().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* colorAttachment = surface->GetColorAttachments()[0];
|
||||
if (colorAttachment == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<FullscreenPassSurfaceCache>& cache : m_ownedFullscreenStageSurfaces) {
|
||||
if (cache == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t entryIndex = 0; entryIndex < cache->GetSurfaceCount(); ++entryIndex) {
|
||||
FullscreenPassSurfaceCache::SurfaceEntry* entry = cache->GetSurfaceEntry(entryIndex);
|
||||
if (entry == nullptr || entry->renderTargetView != colorAttachment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry->currentColorState = surface->GetColorStateAfter();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CameraFramePlan> CameraFramePlanBuilder::CreatePlansFromRequests(
|
||||
const std::vector<CameraRenderRequest>& requests) const {
|
||||
std::vector<CameraFramePlan> plans = {};
|
||||
plans.reserve(requests.size());
|
||||
for (const CameraRenderRequest& request : requests) {
|
||||
plans.push_back(CameraFramePlan::FromRequest(request));
|
||||
}
|
||||
|
||||
return plans;
|
||||
}
|
||||
|
||||
void CameraFramePlanBuilder::PrepareOwnedFullscreenStageState(size_t planCount) {
|
||||
m_ownedPostProcessSequences.clear();
|
||||
m_ownedPostProcessSequences.resize(planCount);
|
||||
m_ownedFinalOutputSequences.clear();
|
||||
m_ownedFinalOutputSequences.resize(planCount);
|
||||
|
||||
if (m_ownedFullscreenStageSurfaces.size() < planCount) {
|
||||
m_ownedFullscreenStageSurfaces.resize(planCount);
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < planCount; ++index) {
|
||||
if (m_ownedFullscreenStageSurfaces[index] == nullptr) {
|
||||
m_ownedFullscreenStageSurfaces[index] = std::make_unique<FullscreenPassSurfaceCache>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFramePlanBuilder::ResolveCameraFinalColorPolicies(
|
||||
std::vector<CameraFramePlan>& plans,
|
||||
const RenderPipelineAsset* pipelineAsset) const {
|
||||
const FinalColorSettings pipelineDefaults =
|
||||
pipelineAsset != nullptr ? pipelineAsset->GetDefaultFinalColorSettings() : FinalColorSettings();
|
||||
|
||||
for (CameraFramePlan& plan : plans) {
|
||||
if (plan.request.camera == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
plan.finalColorPolicy = ResolveFinalColorPolicy(
|
||||
pipelineDefaults,
|
||||
&plan.request.camera->GetFinalColorOverrides());
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFramePlanBuilder::AttachFullscreenStageRequests(
|
||||
std::vector<CameraFramePlan>& plans) {
|
||||
PrepareOwnedFullscreenStageState(plans.size());
|
||||
|
||||
for (size_t index = 0; index < plans.size(); ++index) {
|
||||
CameraFramePlan& plan = plans[index];
|
||||
if (plan.request.camera == nullptr ||
|
||||
plan.request.context.device == nullptr ||
|
||||
!HasValidColorTarget(plan.request.surface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPassSequence> postProcessSequence =
|
||||
BuildCameraPostProcessPassSequence(plan.request.camera->GetPostProcessPasses());
|
||||
std::unique_ptr<RenderPassSequence> finalOutputSequence =
|
||||
BuildFinalColorPassSequence(plan.finalColorPolicy);
|
||||
|
||||
const bool hasPostProcess =
|
||||
postProcessSequence != nullptr && postProcessSequence->GetPassCount() > 0u;
|
||||
const bool hasFinalOutput =
|
||||
finalOutputSequence != nullptr && finalOutputSequence->GetPassCount() > 0u;
|
||||
if (!hasPostProcess && !hasFinalOutput) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plan.request.surface.GetSampleCount() > 1u) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"SceneRenderer fullscreen post-process/final-output chain currently requires a single-sample main scene surface");
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments =
|
||||
plan.request.surface.GetColorAttachments();
|
||||
const RHI::Format colorFormat = colorAttachments[0]->GetFormat();
|
||||
if (colorFormat == RHI::Format::Unknown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t fullscreenSurfaceCount = hasPostProcess && hasFinalOutput ? 2u : 1u;
|
||||
FullscreenPassSurfaceCache* surfaceCache = m_ownedFullscreenStageSurfaces[index].get();
|
||||
if (surfaceCache == nullptr ||
|
||||
!surfaceCache->EnsureSurfaces(
|
||||
plan.request.context,
|
||||
plan.request.surface.GetWidth(),
|
||||
plan.request.surface.GetHeight(),
|
||||
colorFormat,
|
||||
fullscreenSurfaceCount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry* sceneColorEntry =
|
||||
surfaceCache->GetSurfaceEntry(0u);
|
||||
if (sceneColorEntry == nullptr || sceneColorEntry->shaderResourceView == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry* postProcessOutputEntry =
|
||||
hasPostProcess && hasFinalOutput ? surfaceCache->GetSurfaceEntry(1u) : nullptr;
|
||||
if (hasPostProcess && hasFinalOutput &&
|
||||
(postProcessOutputEntry == nullptr || postProcessOutputEntry->shaderResourceView == nullptr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasPostProcess) {
|
||||
plan.postProcess.sourceSurface =
|
||||
ConfigureFullscreenStageSurface(*sceneColorEntry, plan.request.surface, true);
|
||||
plan.postProcess.sourceColorView = sceneColorEntry->shaderResourceView;
|
||||
plan.postProcess.sourceColorState = plan.postProcess.sourceSurface.GetColorStateAfter();
|
||||
plan.postProcess.destinationSurface =
|
||||
hasFinalOutput
|
||||
? ConfigureFullscreenStageSurface(*postProcessOutputEntry, plan.request.surface, false)
|
||||
: plan.request.surface;
|
||||
m_ownedPostProcessSequences[index] = std::move(postProcessSequence);
|
||||
plan.postProcess.passes = m_ownedPostProcessSequences[index].get();
|
||||
}
|
||||
|
||||
if (hasFinalOutput) {
|
||||
const FullscreenPassSurfaceCache::SurfaceEntry* finalOutputSourceEntry =
|
||||
hasPostProcess ? postProcessOutputEntry : sceneColorEntry;
|
||||
plan.finalOutput.sourceSurface =
|
||||
hasPostProcess
|
||||
? plan.postProcess.destinationSurface
|
||||
: ConfigureFullscreenStageSurface(*sceneColorEntry, plan.request.surface, true);
|
||||
plan.finalOutput.sourceColorView = finalOutputSourceEntry->shaderResourceView;
|
||||
plan.finalOutput.sourceColorState = plan.finalOutput.sourceSurface.GetColorStateAfter();
|
||||
plan.finalOutput.destinationSurface = plan.request.surface;
|
||||
m_ownedFinalOutputSequences[index] = std::move(finalOutputSequence);
|
||||
plan.finalOutput.passes = m_ownedFinalOutputSequences[index].get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
41
engine/src/Rendering/Planning/CameraFramePlanBuilder.h
Normal file
41
engine/src/Rendering/Planning/CameraFramePlanBuilder.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
class FullscreenPassSurfaceCache;
|
||||
class RenderPipelineAsset;
|
||||
|
||||
class CameraFramePlanBuilder {
|
||||
public:
|
||||
CameraFramePlanBuilder() = default;
|
||||
CameraFramePlanBuilder(const CameraFramePlanBuilder&) = delete;
|
||||
CameraFramePlanBuilder& operator=(const CameraFramePlanBuilder&) = delete;
|
||||
~CameraFramePlanBuilder() = default;
|
||||
|
||||
std::vector<CameraFramePlan> BuildPlans(
|
||||
const std::vector<CameraRenderRequest>& requests,
|
||||
const RenderPipelineAsset* pipelineAsset);
|
||||
void UpdateTrackedSurfaceState(const RenderSurface* surface);
|
||||
|
||||
private:
|
||||
std::vector<CameraFramePlan> CreatePlansFromRequests(
|
||||
const std::vector<CameraRenderRequest>& requests) const;
|
||||
void PrepareOwnedFullscreenStageState(size_t planCount);
|
||||
void ResolveCameraFinalColorPolicies(
|
||||
std::vector<CameraFramePlan>& plans,
|
||||
const RenderPipelineAsset* pipelineAsset) const;
|
||||
void AttachFullscreenStageRequests(std::vector<CameraFramePlan>& plans);
|
||||
|
||||
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedPostProcessSequences;
|
||||
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedFinalOutputSequences;
|
||||
std::vector<std::unique_ptr<FullscreenPassSurfaceCache>> m_ownedFullscreenStageSurfaces;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -81,17 +81,20 @@ std::vector<CameraRenderRequest> SceneRenderRequestPlanner::BuildRequests(
|
||||
? static_cast<float>(surface.GetRenderAreaWidth()) /
|
||||
static_cast<float>(surface.GetRenderAreaHeight())
|
||||
: 1.0f;
|
||||
DirectionalShadowPlanningSettings effectiveShadowSettings =
|
||||
m_directionalShadowPlanningSettings;
|
||||
if (mainDirectionalLight->GetOverridesDirectionalShadowSettings()) {
|
||||
effectiveShadowSettings.sampling =
|
||||
mainDirectionalLight->GetDirectionalShadowSamplingSettings();
|
||||
effectiveShadowSettings.casterBias =
|
||||
mainDirectionalLight->GetDirectionalShadowCasterBiasSettings();
|
||||
}
|
||||
request.directionalShadow = Internal::BuildDirectionalShadowRenderPlan(
|
||||
scene,
|
||||
*camera,
|
||||
*mainDirectionalLight,
|
||||
m_directionalShadowPlanningSettings,
|
||||
effectiveShadowSettings,
|
||||
viewportAspect);
|
||||
if (request.directionalShadow.IsValid()) {
|
||||
request.shadowCaster.clearFlags = RenderClearFlags::Depth;
|
||||
request.shadowCaster.hasCameraDataOverride = true;
|
||||
request.shadowCaster.cameraDataOverride = request.directionalShadow.cameraData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
engine/src/Rendering/Shadow/DirectionalShadowData.cpp
Normal file
33
engine/src/Rendering/Shadow/DirectionalShadowData.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "Rendering/Shadow/DirectionalShadowData.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
RenderDirectionalShadowData BuildRenderDirectionalShadowData(
|
||||
const DirectionalShadowRenderPlan& plan,
|
||||
RHI::RHIResourceView* shadowMapView) {
|
||||
RenderDirectionalShadowData shadowData = {};
|
||||
if (!plan.IsValid() || shadowMapView == nullptr) {
|
||||
return shadowData;
|
||||
}
|
||||
|
||||
shadowData.enabled = true;
|
||||
shadowData.viewProjection = plan.cameraData.viewProjection;
|
||||
shadowData.shadowMap = shadowMapView;
|
||||
const float texelWorldSize = plan.texelWorldSize > Math::EPSILON
|
||||
? plan.texelWorldSize
|
||||
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
|
||||
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
|
||||
: 0.0f);
|
||||
shadowData.mapMetrics.inverseMapSize = Math::Vector2(
|
||||
1.0f / static_cast<float>(plan.mapWidth),
|
||||
1.0f / static_cast<float>(plan.mapHeight));
|
||||
shadowData.mapMetrics.worldTexelSize = texelWorldSize;
|
||||
shadowData.sampling.enabled = 1.0f;
|
||||
shadowData.sampling.settings = plan.sampling;
|
||||
shadowData.casterBias.settings = plan.casterBias;
|
||||
return shadowData;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
44
engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp
Normal file
44
engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "Rendering/Shadow/DirectionalShadowRuntime.h"
|
||||
|
||||
#include "Rendering/Execution/CameraFramePlan.h"
|
||||
#include "Rendering/Shadow/DirectionalShadowData.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
bool DirectionalShadowRuntime::ResolveExecutionState(
|
||||
const CameraFramePlan& plan,
|
||||
DirectionalShadowExecutionState& outShadowState) {
|
||||
outShadowState = {};
|
||||
outShadowState.shadowCasterRequest = plan.shadowCaster;
|
||||
|
||||
if (outShadowState.shadowCasterRequest.IsRequested()) {
|
||||
return outShadowState.shadowCasterRequest.IsValid();
|
||||
}
|
||||
|
||||
if (!plan.directionalShadow.IsValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const DirectionalShadowSurfaceAllocation* shadowAllocation =
|
||||
m_surfaceCache.Resolve(plan.request.context, plan.directionalShadow);
|
||||
if (shadowAllocation == nullptr || !shadowAllocation->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outShadowState.shadowCasterRequest.surface = shadowAllocation->surface;
|
||||
outShadowState.shadowCasterRequest.clearFlags = RenderClearFlags::Depth;
|
||||
if (!outShadowState.shadowCasterRequest.hasCameraDataOverride) {
|
||||
outShadowState.shadowCasterRequest.hasCameraDataOverride = true;
|
||||
outShadowState.shadowCasterRequest.cameraDataOverride = plan.directionalShadow.cameraData;
|
||||
}
|
||||
|
||||
outShadowState.shadowData =
|
||||
BuildRenderDirectionalShadowData(
|
||||
plan.directionalShadow,
|
||||
shadowAllocation->depthShaderView);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -124,9 +124,16 @@ target_link_libraries(XCUIEditorLib PUBLIC
|
||||
|
||||
add_library(XCUIEditorHost STATIC
|
||||
app/Host/AutoScreenshot.cpp
|
||||
app/Host/BorderlessWindowChrome.cpp
|
||||
app/Host/BorderlessWindowFrame.cpp
|
||||
app/Host/D3D12HostDevice.cpp
|
||||
app/Host/D3D12ShaderResourceDescriptorAllocator.cpp
|
||||
app/Host/D3D12WindowInteropContext.cpp
|
||||
app/Host/D3D12WindowRenderer.cpp
|
||||
app/Host/D3D12WindowSwapChainPresenter.cpp
|
||||
app/Host/D3D12WindowRenderLoop.cpp
|
||||
app/Host/NativeRenderer.cpp
|
||||
app/Host/WindowMessageDispatcher.cpp
|
||||
)
|
||||
|
||||
target_include_directories(XCUIEditorHost
|
||||
@@ -165,6 +172,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP)
|
||||
app/Project/ProductProjectBrowserModel.cpp
|
||||
app/Shell/ProductShellAsset.cpp
|
||||
app/Viewport/ProductViewportHostService.cpp
|
||||
app/Viewport/ProductViewportRenderTargetManager.cpp
|
||||
app/Viewport/ProductViewportRenderTargets.cpp
|
||||
app/Workspace/ProductEditorWorkspace.cpp
|
||||
app/Workspace/ProductEditorWorkspaceEventRouter.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,11 @@
|
||||
#endif
|
||||
|
||||
#include <Host/AutoScreenshot.h>
|
||||
#include <Host/BorderlessWindowChrome.h>
|
||||
#include <Host/BorderlessWindowFrame.h>
|
||||
#include <Host/D3D12WindowRenderer.h>
|
||||
#include <Host/D3D12WindowRenderLoop.h>
|
||||
#include <Host/HostRuntimeState.h>
|
||||
#include <Host/InputModifierTracker.h>
|
||||
#include <Host/NativeRenderer.h>
|
||||
|
||||
@@ -23,6 +27,10 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
class WindowMessageDispatcher;
|
||||
}
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
class Application {
|
||||
@@ -32,20 +40,24 @@ public:
|
||||
int Run(HINSTANCE hInstance, int nCmdShow);
|
||||
|
||||
private:
|
||||
friend class ::XCEngine::UI::Editor::Host::WindowMessageDispatcher;
|
||||
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool Initialize(HINSTANCE hInstance, int nCmdShow);
|
||||
void Shutdown();
|
||||
void RenderFrame();
|
||||
void OnResize();
|
||||
void OnPaintMessage();
|
||||
void OnResize(UINT width, UINT height);
|
||||
void OnEnterSizeMove();
|
||||
void OnExitSizeMove();
|
||||
void OnDpiChanged(UINT dpi, const RECT& suggestedRect);
|
||||
void QueueWindowResize(UINT width, UINT height);
|
||||
void QueueCurrentClientResize();
|
||||
bool ApplyPendingWindowResize();
|
||||
void RequestDeferredRenderFrame();
|
||||
bool ApplyWindowResize(UINT width, UINT height);
|
||||
bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const;
|
||||
bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const;
|
||||
::XCEngine::UI::UIRect ResolveWorkspaceBounds(
|
||||
float clientWidthDips,
|
||||
float clientHeightDips) const;
|
||||
bool IsPointerInsideClientArea() const;
|
||||
bool ApplyCurrentCursor() const;
|
||||
LPCWSTR ResolveCurrentCursorResource() const;
|
||||
@@ -69,6 +81,38 @@ private:
|
||||
void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam);
|
||||
void QueueCharacterEvent(WPARAM wParam, LPARAM lParam);
|
||||
void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type);
|
||||
bool IsBorderlessWindowEnabled() const;
|
||||
bool IsBorderlessWindowMaximized() const;
|
||||
bool HandleBorderlessWindowSystemCommand(WPARAM wParam);
|
||||
bool HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const;
|
||||
LRESULT HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const;
|
||||
Host::BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(LPARAM lParam) const;
|
||||
bool UpdateBorderlessWindowChromeHover(LPARAM lParam);
|
||||
bool HandleBorderlessWindowChromeButtonDown(LPARAM lParam);
|
||||
bool HandleBorderlessWindowChromeButtonUp(LPARAM lParam);
|
||||
bool HandleBorderlessWindowChromeDoubleClick(LPARAM lParam);
|
||||
bool HandleBorderlessWindowChromeDragRestorePointerMove();
|
||||
void ClearBorderlessWindowChromeDragRestoreState();
|
||||
void ClearBorderlessWindowChromeState();
|
||||
Host::BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(LPARAM lParam) const;
|
||||
bool UpdateBorderlessWindowResizeHover(LPARAM lParam);
|
||||
bool HandleBorderlessWindowResizeButtonDown(LPARAM lParam);
|
||||
bool HandleBorderlessWindowResizeButtonUp();
|
||||
bool HandleBorderlessWindowResizePointerMove();
|
||||
void ClearBorderlessWindowResizeState();
|
||||
void ForceClearBorderlessWindowResizeState();
|
||||
void ApplyBorderlessWindowResizeCursorHoverPriority();
|
||||
bool QueryCurrentWindowRect(RECT& outRect) const;
|
||||
bool QueryBorderlessWindowWorkAreaRect(RECT& outRect) const;
|
||||
bool ApplyPredictedWindowRectTransition(const RECT& targetRect);
|
||||
void ToggleBorderlessWindowMaximizeRestore();
|
||||
void AppendBorderlessWindowChrome(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
float clientWidthDips) const;
|
||||
void ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget target);
|
||||
Host::BorderlessWindowChromeLayout ResolveBorderlessWindowChromeLayout(
|
||||
float clientWidthDips) const;
|
||||
void InvalidateHostWindow() const;
|
||||
static std::filesystem::path ResolveRepoRootPath();
|
||||
static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo);
|
||||
static bool IsVerboseRuntimeTraceEnabled();
|
||||
@@ -78,19 +122,16 @@ private:
|
||||
ATOM m_windowClassAtom = 0;
|
||||
::XCEngine::UI::Editor::Host::NativeRenderer m_renderer = {};
|
||||
::XCEngine::UI::Editor::Host::D3D12WindowRenderer m_windowRenderer = {};
|
||||
::XCEngine::UI::Editor::Host::D3D12WindowRenderLoop m_windowRenderLoop = {};
|
||||
::XCEngine::UI::Editor::Host::AutoScreenshotController m_autoScreenshot = {};
|
||||
::XCEngine::UI::Editor::Host::InputModifierTracker m_inputModifierTracker = {};
|
||||
App::ProductEditorContext m_editorContext = {};
|
||||
App::ProductEditorWorkspace m_editorWorkspace = {};
|
||||
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
|
||||
bool m_trackingMouseLeave = false;
|
||||
UINT m_windowDpi = 96u;
|
||||
float m_dpiScale = 1.0f;
|
||||
bool m_inInteractiveResize = false;
|
||||
bool m_renderFrameQueued = false;
|
||||
bool m_hasPendingWindowResize = false;
|
||||
UINT m_pendingWindowResizeWidth = 0u;
|
||||
UINT m_pendingWindowResizeHeight = 0u;
|
||||
bool m_renderReady = false;
|
||||
::XCEngine::UI::Editor::Host::BorderlessWindowChromeState m_borderlessWindowChromeState = {};
|
||||
::XCEngine::UI::Editor::Host::HostRuntimeState m_hostRuntime = {};
|
||||
};
|
||||
|
||||
int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow);
|
||||
|
||||
403
new_editor/app/Host/BorderlessWindowChrome.cpp
Normal file
403
new_editor/app/Host/BorderlessWindowChrome.cpp
Normal file
@@ -0,0 +1,403 @@
|
||||
#include "BorderlessWindowChrome.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <dwmapi.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
|
||||
bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) {
|
||||
return rect.width > 0.0f &&
|
||||
rect.height > 0.0f &&
|
||||
point.x >= rect.x &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y >= rect.y &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
void AppendMinimizeGlyph(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIColor& color,
|
||||
float thickness) {
|
||||
const float centerX = rect.x + rect.width * 0.5f;
|
||||
const float centerY = rect.y + rect.height * 0.5f;
|
||||
const float halfWidth = (std::max)(4.0f, rect.height * 0.22f);
|
||||
const float y = centerY + rect.height * 0.12f;
|
||||
drawList.AddLine(
|
||||
UIPoint(centerX - halfWidth, y),
|
||||
UIPoint(centerX + halfWidth, y),
|
||||
color,
|
||||
thickness);
|
||||
}
|
||||
|
||||
void AppendMaximizeGlyph(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIColor& color,
|
||||
float thickness) {
|
||||
const float centerX = rect.x + rect.width * 0.5f;
|
||||
const float centerY = rect.y + rect.height * 0.5f;
|
||||
const float halfExtent = (std::max)(4.0f, rect.height * 0.20f);
|
||||
drawList.AddRectOutline(
|
||||
UIRect(
|
||||
centerX - halfExtent,
|
||||
centerY - halfExtent,
|
||||
halfExtent * 2.0f,
|
||||
halfExtent * 2.0f),
|
||||
color,
|
||||
thickness);
|
||||
}
|
||||
|
||||
void AppendRestoreGlyph(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIColor& color,
|
||||
float thickness) {
|
||||
const float centerX = rect.x + rect.width * 0.5f;
|
||||
const float centerY = rect.y + rect.height * 0.5f;
|
||||
const float halfExtent = (std::max)(4.0f, rect.height * 0.18f);
|
||||
const float offset = 2.0f;
|
||||
drawList.AddRectOutline(
|
||||
UIRect(
|
||||
centerX - halfExtent + offset,
|
||||
centerY - halfExtent - offset,
|
||||
halfExtent * 2.0f,
|
||||
halfExtent * 2.0f),
|
||||
color,
|
||||
thickness);
|
||||
drawList.AddRectOutline(
|
||||
UIRect(
|
||||
centerX - halfExtent - offset,
|
||||
centerY - halfExtent + offset,
|
||||
halfExtent * 2.0f,
|
||||
halfExtent * 2.0f),
|
||||
color,
|
||||
thickness);
|
||||
}
|
||||
|
||||
void AppendCloseGlyph(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIColor& color,
|
||||
float thickness) {
|
||||
const float centerX = rect.x + rect.width * 0.5f;
|
||||
const float centerY = rect.y + rect.height * 0.5f;
|
||||
const float halfWidth = (std::max)(4.0f, rect.height * 0.20f);
|
||||
const float halfHeight = halfWidth;
|
||||
drawList.AddLine(
|
||||
UIPoint(centerX - halfWidth, centerY - halfHeight),
|
||||
UIPoint(centerX + halfWidth, centerY + halfHeight),
|
||||
color,
|
||||
thickness);
|
||||
drawList.AddLine(
|
||||
UIPoint(centerX + halfWidth, centerY - halfHeight),
|
||||
UIPoint(centerX - halfWidth, centerY + halfHeight),
|
||||
color,
|
||||
thickness);
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveButtonFillColor(
|
||||
BorderlessWindowChromeHitTarget target,
|
||||
const BorderlessWindowChromeState& state,
|
||||
const BorderlessWindowChromePalette& palette) {
|
||||
const bool hovered = state.hoveredTarget == target;
|
||||
const bool pressed = state.pressedTarget == target;
|
||||
if (target == BorderlessWindowChromeHitTarget::CloseButton) {
|
||||
if (pressed) {
|
||||
return palette.closeButtonPressedColor;
|
||||
}
|
||||
if (hovered) {
|
||||
return palette.closeButtonHoverColor;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pressed) {
|
||||
return palette.buttonPressedColor;
|
||||
}
|
||||
if (hovered) {
|
||||
return palette.buttonHoverColor;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveIconColor(
|
||||
BorderlessWindowChromeHitTarget target,
|
||||
const BorderlessWindowChromeState& state,
|
||||
const BorderlessWindowChromePalette& palette) {
|
||||
if (target == BorderlessWindowChromeHitTarget::CloseButton &&
|
||||
(state.hoveredTarget == target || state.pressedTarget == target)) {
|
||||
return palette.closeIconHoverColor;
|
||||
}
|
||||
|
||||
return palette.iconColor;
|
||||
}
|
||||
|
||||
int QuerySystemMetricForDpi(int index, UINT dpi) {
|
||||
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||
if (user32 != nullptr) {
|
||||
using GetSystemMetricsForDpiFn = int(WINAPI*)(int, UINT);
|
||||
const auto getSystemMetricsForDpi =
|
||||
reinterpret_cast<GetSystemMetricsForDpiFn>(
|
||||
GetProcAddress(user32, "GetSystemMetricsForDpi"));
|
||||
if (getSystemMetricsForDpi != nullptr) {
|
||||
return getSystemMetricsForDpi(index, dpi);
|
||||
}
|
||||
}
|
||||
|
||||
return GetSystemMetrics(index);
|
||||
}
|
||||
|
||||
bool IsWindowAlignedToMonitorWorkArea(HWND hwnd) {
|
||||
if (hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT windowRect = {};
|
||||
if (!GetWindowRect(hwnd, &windowRect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if (monitor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MONITORINFO monitorInfo = {};
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RECT& workArea = monitorInfo.rcWork;
|
||||
return windowRect.left == workArea.left &&
|
||||
windowRect.top == workArea.top &&
|
||||
windowRect.right == workArea.right &&
|
||||
windowRect.bottom == workArea.bottom;
|
||||
}
|
||||
|
||||
void ApplyDwmBoolWindowAttribute(HWND hwnd, DWORD attribute, BOOL value) {
|
||||
if (hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD);
|
||||
static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn {
|
||||
HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll");
|
||||
if (dwmapi == nullptr) {
|
||||
dwmapi = LoadLibraryW(L"dwmapi.dll");
|
||||
}
|
||||
if (dwmapi == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<DwmSetWindowAttributeFn>(
|
||||
GetProcAddress(dwmapi, "DwmSetWindowAttribute"));
|
||||
}();
|
||||
|
||||
if (setWindowAttribute != nullptr) {
|
||||
setWindowAttribute(hwnd, attribute, &value, sizeof(value));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout(
|
||||
const UIRect& titleBarRect,
|
||||
float leadingOccupiedRight,
|
||||
const BorderlessWindowChromeMetrics& metrics) {
|
||||
BorderlessWindowChromeLayout layout = {};
|
||||
layout.titleBarRect = titleBarRect;
|
||||
if (titleBarRect.width <= 0.0f || titleBarRect.height <= 0.0f) {
|
||||
return layout;
|
||||
}
|
||||
|
||||
const float buttonWidth = (std::max)(metrics.buttonWidth, 0.0f);
|
||||
const float buttonX3 = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX - buttonWidth;
|
||||
const float buttonX2 = buttonX3 - buttonWidth;
|
||||
const float buttonX1 = buttonX2 - buttonWidth;
|
||||
|
||||
layout.minimizeButtonRect = UIRect(buttonX1, titleBarRect.y, buttonWidth, titleBarRect.height);
|
||||
layout.maximizeRestoreButtonRect = UIRect(buttonX2, titleBarRect.y, buttonWidth, titleBarRect.height);
|
||||
layout.closeButtonRect = UIRect(buttonX3, titleBarRect.y, buttonWidth, titleBarRect.height);
|
||||
|
||||
const float dragLeft =
|
||||
(std::max)(titleBarRect.x, leadingOccupiedRight + metrics.dragPaddingLeft);
|
||||
const float dragRight =
|
||||
(std::max)(dragLeft, layout.minimizeButtonRect.x - metrics.dragPaddingRight);
|
||||
layout.dragRect = UIRect(
|
||||
dragLeft,
|
||||
titleBarRect.y,
|
||||
(std::max)(0.0f, dragRight - dragLeft),
|
||||
titleBarRect.height);
|
||||
return layout;
|
||||
}
|
||||
|
||||
BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(
|
||||
const BorderlessWindowChromeLayout& layout,
|
||||
const UIPoint& point) {
|
||||
if (IsPointInsideRect(layout.closeButtonRect, point)) {
|
||||
return BorderlessWindowChromeHitTarget::CloseButton;
|
||||
}
|
||||
if (IsPointInsideRect(layout.maximizeRestoreButtonRect, point)) {
|
||||
return BorderlessWindowChromeHitTarget::MaximizeRestoreButton;
|
||||
}
|
||||
if (IsPointInsideRect(layout.minimizeButtonRect, point)) {
|
||||
return BorderlessWindowChromeHitTarget::MinimizeButton;
|
||||
}
|
||||
if (IsPointInsideRect(layout.dragRect, point)) {
|
||||
return BorderlessWindowChromeHitTarget::DragRegion;
|
||||
}
|
||||
return BorderlessWindowChromeHitTarget::None;
|
||||
}
|
||||
|
||||
void AppendBorderlessWindowChrome(
|
||||
UIDrawList& drawList,
|
||||
const BorderlessWindowChromeLayout& layout,
|
||||
const BorderlessWindowChromeState& state,
|
||||
bool maximized,
|
||||
const BorderlessWindowChromePalette& palette,
|
||||
const BorderlessWindowChromeMetrics& metrics) {
|
||||
const struct ButtonEntry {
|
||||
BorderlessWindowChromeHitTarget target;
|
||||
UIRect rect;
|
||||
} buttons[] = {
|
||||
{ BorderlessWindowChromeHitTarget::MinimizeButton, layout.minimizeButtonRect },
|
||||
{ BorderlessWindowChromeHitTarget::MaximizeRestoreButton, layout.maximizeRestoreButtonRect },
|
||||
{ BorderlessWindowChromeHitTarget::CloseButton, layout.closeButtonRect }
|
||||
};
|
||||
|
||||
for (const ButtonEntry& button : buttons) {
|
||||
const UIColor fill = ResolveButtonFillColor(button.target, state, palette);
|
||||
if (fill.a > 0.0f) {
|
||||
drawList.AddFilledRect(button.rect, fill);
|
||||
}
|
||||
|
||||
const UIColor iconColor = ResolveIconColor(button.target, state, palette);
|
||||
switch (button.target) {
|
||||
case BorderlessWindowChromeHitTarget::MinimizeButton:
|
||||
AppendMinimizeGlyph(
|
||||
drawList,
|
||||
button.rect,
|
||||
iconColor,
|
||||
metrics.iconThickness);
|
||||
break;
|
||||
case BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
|
||||
if (maximized) {
|
||||
AppendRestoreGlyph(
|
||||
drawList,
|
||||
button.rect,
|
||||
iconColor,
|
||||
metrics.iconThickness);
|
||||
} else {
|
||||
AppendMaximizeGlyph(
|
||||
drawList,
|
||||
button.rect,
|
||||
iconColor,
|
||||
metrics.iconThickness);
|
||||
}
|
||||
break;
|
||||
case BorderlessWindowChromeHitTarget::CloseButton:
|
||||
AppendCloseGlyph(
|
||||
drawList,
|
||||
button.rect,
|
||||
iconColor,
|
||||
metrics.iconThickness);
|
||||
break;
|
||||
case BorderlessWindowChromeHitTarget::DragRegion:
|
||||
case BorderlessWindowChromeHitTarget::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnableBorderlessWindowShadow(HWND hwnd) {
|
||||
if (hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
using DwmExtendFrameIntoClientAreaFn = HRESULT(WINAPI*)(HWND, const MARGINS*);
|
||||
static const auto extendFrameIntoClientArea = []() -> DwmExtendFrameIntoClientAreaFn {
|
||||
HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll");
|
||||
if (dwmapi == nullptr) {
|
||||
dwmapi = LoadLibraryW(L"dwmapi.dll");
|
||||
}
|
||||
if (dwmapi == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<DwmExtendFrameIntoClientAreaFn>(
|
||||
GetProcAddress(dwmapi, "DwmExtendFrameIntoClientArea"));
|
||||
}();
|
||||
if (extendFrameIntoClientArea != nullptr) {
|
||||
const bool maximized = IsZoomed(hwnd) || IsWindowAlignedToMonitorWorkArea(hwnd);
|
||||
const MARGINS margins = maximized
|
||||
? MARGINS{ 0, 0, 0, 0 }
|
||||
: MARGINS{ 1, 1, 1, 1 };
|
||||
extendFrameIntoClientArea(hwnd, &margins);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshBorderlessWindowDwmDecorations(HWND hwnd) {
|
||||
if (hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Borderless host cannot participate in compositor-driven minimize/maximize
|
||||
// transitions without Windows stretching the last presented client frame.
|
||||
// Disable those transitions for this window, then refresh the shadow state.
|
||||
ApplyDwmBoolWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, TRUE);
|
||||
EnableBorderlessWindowShadow(hwnd);
|
||||
}
|
||||
|
||||
bool HandleBorderlessWindowGetMinMaxInfo(HWND hwnd, LPARAM lParam) {
|
||||
if (hwnd == nullptr || lParam == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* minMaxInfo = reinterpret_cast<MINMAXINFO*>(lParam);
|
||||
if (minMaxInfo == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if (monitor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MONITORINFO monitorInfo = {};
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RECT& workArea = monitorInfo.rcWork;
|
||||
const RECT& monitorArea = monitorInfo.rcMonitor;
|
||||
minMaxInfo->ptMaxPosition.x = workArea.left - monitorArea.left;
|
||||
minMaxInfo->ptMaxPosition.y = workArea.top - monitorArea.top;
|
||||
minMaxInfo->ptMaxSize.x = workArea.right - workArea.left;
|
||||
minMaxInfo->ptMaxSize.y = workArea.bottom - workArea.top;
|
||||
minMaxInfo->ptMaxTrackSize = minMaxInfo->ptMaxSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
LRESULT HandleBorderlessWindowNcCalcSize(
|
||||
HWND hwnd,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
UINT dpi) {
|
||||
(void)hwnd;
|
||||
(void)wParam;
|
||||
(void)lParam;
|
||||
(void)dpi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
85
new_editor/app/Host/BorderlessWindowChrome.h
Normal file
85
new_editor/app/Host/BorderlessWindowChrome.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
enum class BorderlessWindowChromeHitTarget : std::uint8_t {
|
||||
None = 0,
|
||||
DragRegion,
|
||||
MinimizeButton,
|
||||
MaximizeRestoreButton,
|
||||
CloseButton
|
||||
};
|
||||
|
||||
struct BorderlessWindowChromeMetrics {
|
||||
float buttonWidth = 46.0f;
|
||||
float buttonInsetX = 0.0f;
|
||||
float dragPaddingLeft = 6.0f;
|
||||
float dragPaddingRight = 6.0f;
|
||||
float iconThickness = 1.0f;
|
||||
};
|
||||
|
||||
struct BorderlessWindowChromePalette {
|
||||
::XCEngine::UI::UIColor buttonHoverColor =
|
||||
::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f);
|
||||
::XCEngine::UI::UIColor buttonPressedColor =
|
||||
::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f);
|
||||
::XCEngine::UI::UIColor closeButtonHoverColor =
|
||||
::XCEngine::UI::UIColor(0.91f, 0.31f, 0.24f, 1.0f);
|
||||
::XCEngine::UI::UIColor closeButtonPressedColor =
|
||||
::XCEngine::UI::UIColor(0.78f, 0.22f, 0.18f, 1.0f);
|
||||
::XCEngine::UI::UIColor iconColor =
|
||||
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
|
||||
::XCEngine::UI::UIColor closeIconHoverColor =
|
||||
::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
};
|
||||
|
||||
struct BorderlessWindowChromeState {
|
||||
BorderlessWindowChromeHitTarget hoveredTarget = BorderlessWindowChromeHitTarget::None;
|
||||
BorderlessWindowChromeHitTarget pressedTarget = BorderlessWindowChromeHitTarget::None;
|
||||
};
|
||||
|
||||
struct BorderlessWindowChromeLayout {
|
||||
::XCEngine::UI::UIRect titleBarRect = {};
|
||||
::XCEngine::UI::UIRect dragRect = {};
|
||||
::XCEngine::UI::UIRect minimizeButtonRect = {};
|
||||
::XCEngine::UI::UIRect maximizeRestoreButtonRect = {};
|
||||
::XCEngine::UI::UIRect closeButtonRect = {};
|
||||
};
|
||||
|
||||
BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout(
|
||||
const ::XCEngine::UI::UIRect& titleBarRect,
|
||||
float leadingOccupiedRight,
|
||||
const BorderlessWindowChromeMetrics& metrics = {});
|
||||
|
||||
BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(
|
||||
const BorderlessWindowChromeLayout& layout,
|
||||
const ::XCEngine::UI::UIPoint& point);
|
||||
|
||||
void AppendBorderlessWindowChrome(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const BorderlessWindowChromeLayout& layout,
|
||||
const BorderlessWindowChromeState& state,
|
||||
bool maximized,
|
||||
const BorderlessWindowChromePalette& palette = {},
|
||||
const BorderlessWindowChromeMetrics& metrics = {});
|
||||
|
||||
void RefreshBorderlessWindowDwmDecorations(HWND hwnd);
|
||||
void EnableBorderlessWindowShadow(HWND hwnd);
|
||||
|
||||
bool HandleBorderlessWindowGetMinMaxInfo(HWND hwnd, LPARAM lParam);
|
||||
|
||||
LRESULT HandleBorderlessWindowNcCalcSize(
|
||||
HWND hwnd,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
UINT dpi);
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
176
new_editor/app/Host/BorderlessWindowFrame.cpp
Normal file
176
new_editor/app/Host/BorderlessWindowFrame.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "BorderlessWindowFrame.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
|
||||
bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) {
|
||||
return rect.width > 0.0f &&
|
||||
rect.height > 0.0f &&
|
||||
point.x >= rect.x &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y >= rect.y &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
int ClampMinimum(int value, int minimum) {
|
||||
return (std::max)(value, minimum);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(
|
||||
const UIRect& clientRect,
|
||||
const UIPoint& point,
|
||||
const BorderlessWindowFrameMetrics& metrics) {
|
||||
const float edge = (std::max)(metrics.resizeBorderThickness, 0.0f);
|
||||
if (edge <= 0.0f || !IsPointInsideRect(clientRect, point)) {
|
||||
return BorderlessWindowResizeEdge::None;
|
||||
}
|
||||
|
||||
const bool left = point.x <= clientRect.x + edge;
|
||||
const bool right = point.x >= clientRect.x + clientRect.width - edge;
|
||||
const bool top = point.y <= clientRect.y + edge;
|
||||
const bool bottom = point.y >= clientRect.y + clientRect.height - edge;
|
||||
|
||||
if (left && top) {
|
||||
return BorderlessWindowResizeEdge::TopLeft;
|
||||
}
|
||||
if (right && top) {
|
||||
return BorderlessWindowResizeEdge::TopRight;
|
||||
}
|
||||
if (left && bottom) {
|
||||
return BorderlessWindowResizeEdge::BottomLeft;
|
||||
}
|
||||
if (right && bottom) {
|
||||
return BorderlessWindowResizeEdge::BottomRight;
|
||||
}
|
||||
if (left) {
|
||||
return BorderlessWindowResizeEdge::Left;
|
||||
}
|
||||
if (right) {
|
||||
return BorderlessWindowResizeEdge::Right;
|
||||
}
|
||||
if (top) {
|
||||
return BorderlessWindowResizeEdge::Top;
|
||||
}
|
||||
if (bottom) {
|
||||
return BorderlessWindowResizeEdge::Bottom;
|
||||
}
|
||||
return BorderlessWindowResizeEdge::None;
|
||||
}
|
||||
|
||||
LPCWSTR ResolveBorderlessWindowResizeCursor(BorderlessWindowResizeEdge edge) {
|
||||
switch (edge) {
|
||||
case BorderlessWindowResizeEdge::Left:
|
||||
case BorderlessWindowResizeEdge::Right:
|
||||
return IDC_SIZEWE;
|
||||
case BorderlessWindowResizeEdge::Top:
|
||||
case BorderlessWindowResizeEdge::Bottom:
|
||||
return IDC_SIZENS;
|
||||
case BorderlessWindowResizeEdge::TopLeft:
|
||||
case BorderlessWindowResizeEdge::BottomRight:
|
||||
return IDC_SIZENWSE;
|
||||
case BorderlessWindowResizeEdge::TopRight:
|
||||
case BorderlessWindowResizeEdge::BottomLeft:
|
||||
return IDC_SIZENESW;
|
||||
case BorderlessWindowResizeEdge::None:
|
||||
default:
|
||||
return IDC_ARROW;
|
||||
}
|
||||
}
|
||||
|
||||
RECT ComputeBorderlessWindowResizeRect(
|
||||
const RECT& initialRect,
|
||||
const POINT& initialScreenPoint,
|
||||
const POINT& currentScreenPoint,
|
||||
BorderlessWindowResizeEdge edge,
|
||||
int minimumOuterWidth,
|
||||
int minimumOuterHeight) {
|
||||
RECT result = initialRect;
|
||||
const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x;
|
||||
const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y;
|
||||
const int minimumWidth = ClampMinimum(minimumOuterWidth, 1);
|
||||
const int minimumHeight = ClampMinimum(minimumOuterHeight, 1);
|
||||
|
||||
switch (edge) {
|
||||
case BorderlessWindowResizeEdge::Left:
|
||||
case BorderlessWindowResizeEdge::TopLeft:
|
||||
case BorderlessWindowResizeEdge::BottomLeft:
|
||||
result.left += deltaX;
|
||||
if (result.right - result.left < minimumWidth) {
|
||||
result.left = result.right - minimumWidth;
|
||||
}
|
||||
break;
|
||||
case BorderlessWindowResizeEdge::None:
|
||||
case BorderlessWindowResizeEdge::Top:
|
||||
case BorderlessWindowResizeEdge::Bottom:
|
||||
case BorderlessWindowResizeEdge::Right:
|
||||
case BorderlessWindowResizeEdge::TopRight:
|
||||
case BorderlessWindowResizeEdge::BottomRight:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (edge) {
|
||||
case BorderlessWindowResizeEdge::Right:
|
||||
case BorderlessWindowResizeEdge::TopRight:
|
||||
case BorderlessWindowResizeEdge::BottomRight:
|
||||
result.right += deltaX;
|
||||
if (result.right - result.left < minimumWidth) {
|
||||
result.right = result.left + minimumWidth;
|
||||
}
|
||||
break;
|
||||
case BorderlessWindowResizeEdge::None:
|
||||
case BorderlessWindowResizeEdge::Left:
|
||||
case BorderlessWindowResizeEdge::Top:
|
||||
case BorderlessWindowResizeEdge::Bottom:
|
||||
case BorderlessWindowResizeEdge::TopLeft:
|
||||
case BorderlessWindowResizeEdge::BottomLeft:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (edge) {
|
||||
case BorderlessWindowResizeEdge::Top:
|
||||
case BorderlessWindowResizeEdge::TopLeft:
|
||||
case BorderlessWindowResizeEdge::TopRight:
|
||||
result.top += deltaY;
|
||||
if (result.bottom - result.top < minimumHeight) {
|
||||
result.top = result.bottom - minimumHeight;
|
||||
}
|
||||
break;
|
||||
case BorderlessWindowResizeEdge::None:
|
||||
case BorderlessWindowResizeEdge::Left:
|
||||
case BorderlessWindowResizeEdge::Right:
|
||||
case BorderlessWindowResizeEdge::Bottom:
|
||||
case BorderlessWindowResizeEdge::BottomLeft:
|
||||
case BorderlessWindowResizeEdge::BottomRight:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (edge) {
|
||||
case BorderlessWindowResizeEdge::Bottom:
|
||||
case BorderlessWindowResizeEdge::BottomLeft:
|
||||
case BorderlessWindowResizeEdge::BottomRight:
|
||||
result.bottom += deltaY;
|
||||
if (result.bottom - result.top < minimumHeight) {
|
||||
result.bottom = result.top + minimumHeight;
|
||||
}
|
||||
break;
|
||||
case BorderlessWindowResizeEdge::None:
|
||||
case BorderlessWindowResizeEdge::Left:
|
||||
case BorderlessWindowResizeEdge::Top:
|
||||
case BorderlessWindowResizeEdge::Right:
|
||||
case BorderlessWindowResizeEdge::TopLeft:
|
||||
case BorderlessWindowResizeEdge::TopRight:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
46
new_editor/app/Host/BorderlessWindowFrame.h
Normal file
46
new_editor/app/Host/BorderlessWindowFrame.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
enum class BorderlessWindowResizeEdge : std::uint8_t {
|
||||
None = 0,
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
};
|
||||
|
||||
struct BorderlessWindowFrameMetrics {
|
||||
float resizeBorderThickness = 6.0f;
|
||||
int minimumOuterWidth = 640;
|
||||
int minimumOuterHeight = 360;
|
||||
};
|
||||
|
||||
BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(
|
||||
const ::XCEngine::UI::UIRect& clientRect,
|
||||
const ::XCEngine::UI::UIPoint& point,
|
||||
const BorderlessWindowFrameMetrics& metrics = {});
|
||||
|
||||
LPCWSTR ResolveBorderlessWindowResizeCursor(BorderlessWindowResizeEdge edge);
|
||||
|
||||
RECT ComputeBorderlessWindowResizeRect(
|
||||
const RECT& initialRect,
|
||||
const POINT& initialScreenPoint,
|
||||
const POINT& currentScreenPoint,
|
||||
BorderlessWindowResizeEdge edge,
|
||||
int minimumOuterWidth,
|
||||
int minimumOuterHeight);
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
296
new_editor/app/Host/D3D12HostDevice.cpp
Normal file
296
new_editor/app/Host/D3D12HostDevice.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "D3D12HostDevice.h"
|
||||
|
||||
#include <XCEngine/RHI/RHIFactory.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using ::XCEngine::RHI::CommandListDesc;
|
||||
using ::XCEngine::RHI::CommandQueueDesc;
|
||||
using ::XCEngine::RHI::D3D12CommandList;
|
||||
using ::XCEngine::RHI::D3D12CommandQueue;
|
||||
using ::XCEngine::RHI::D3D12Device;
|
||||
using ::XCEngine::RHI::RHICommandList;
|
||||
using ::XCEngine::RHI::RHICommandQueue;
|
||||
using ::XCEngine::RHI::RHIDevice;
|
||||
using ::XCEngine::RHI::RHIDeviceDesc;
|
||||
using ::XCEngine::RHI::RHIFactory;
|
||||
using ::XCEngine::RHI::RHIType;
|
||||
|
||||
bool D3D12HostDevice::Initialize() {
|
||||
Shutdown();
|
||||
|
||||
m_device = RHIFactory::CreateRHIDevice(RHIType::D3D12);
|
||||
if (m_device == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 RHI device.";
|
||||
return false;
|
||||
}
|
||||
|
||||
RHIDeviceDesc deviceDesc = {};
|
||||
#ifdef _DEBUG
|
||||
deviceDesc.enableDebugLayer = true;
|
||||
deviceDesc.enableGPUValidation = true;
|
||||
#endif
|
||||
if (!m_device->Initialize(deviceDesc)) {
|
||||
m_lastError = "Failed to initialize the D3D12 RHI device.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandQueueDesc queueDesc = {};
|
||||
queueDesc.queueType = static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
|
||||
m_commandQueue = m_device->CreateCommandQueue(queueDesc);
|
||||
if (m_commandQueue == nullptr || GetD3D12CommandQueue() == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 command queue.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandListDesc commandListDesc = {};
|
||||
commandListDesc.commandListType =
|
||||
static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
|
||||
for (std::uint32_t commandListIndex = 0;
|
||||
commandListIndex < kFrameContextCount;
|
||||
++commandListIndex) {
|
||||
m_commandLists[commandListIndex] = m_device->CreateCommandList(commandListDesc);
|
||||
if (m_commandLists[commandListIndex] == nullptr ||
|
||||
GetD3D12CommandList(commandListIndex) == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 command list.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_commandLists[commandListIndex]->Close();
|
||||
}
|
||||
|
||||
if (!InitializeFrameCompletionFence()) {
|
||||
m_lastError = "Failed to create the host frame completion fence.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_frameFenceValues.fill(0u);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12HostDevice::Shutdown() {
|
||||
WaitForGpuIdle();
|
||||
ReleaseFrameCompletionFence();
|
||||
|
||||
for (auto*& commandList : m_commandLists) {
|
||||
if (commandList != nullptr) {
|
||||
commandList->Shutdown();
|
||||
delete commandList;
|
||||
commandList = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_commandQueue != nullptr) {
|
||||
m_commandQueue->Shutdown();
|
||||
delete m_commandQueue;
|
||||
m_commandQueue = nullptr;
|
||||
}
|
||||
|
||||
if (m_device != nullptr) {
|
||||
m_device->Shutdown();
|
||||
delete m_device;
|
||||
m_device = nullptr;
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
}
|
||||
|
||||
bool D3D12HostDevice::BeginFrame(std::uint32_t frameIndex) {
|
||||
WaitForFrame(frameIndex);
|
||||
|
||||
RHICommandList* commandList = GetCommandList(frameIndex);
|
||||
D3D12CommandList* d3d12CommandList = GetD3D12CommandList(frameIndex);
|
||||
if (commandList == nullptr || d3d12CommandList == nullptr) {
|
||||
m_lastError = "BeginFrame could not resolve the active command list.";
|
||||
return false;
|
||||
}
|
||||
|
||||
commandList->Reset();
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12HostDevice::SubmitFrame(std::uint32_t frameIndex) {
|
||||
RHICommandList* commandList = GetCommandList(frameIndex);
|
||||
if (commandList == nullptr || m_commandQueue == nullptr) {
|
||||
m_lastError = "SubmitFrame requires an initialized command list and queue.";
|
||||
return false;
|
||||
}
|
||||
|
||||
commandList->Close();
|
||||
void* commandLists[] = { commandList };
|
||||
m_commandQueue->ExecuteCommandLists(1, commandLists);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12HostDevice::SignalFrameCompletion(std::uint32_t frameIndex) {
|
||||
ID3D12CommandQueue* commandQueue = GetCommandQueue();
|
||||
if (commandQueue == nullptr ||
|
||||
m_frameCompletionFence == nullptr ||
|
||||
frameIndex >= m_frameFenceValues.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++m_lastSubmittedFrameValue;
|
||||
const HRESULT hr = commandQueue->Signal(
|
||||
m_frameCompletionFence.Get(),
|
||||
m_lastSubmittedFrameValue);
|
||||
if (SUCCEEDED(hr)) {
|
||||
m_frameFenceValues[frameIndex] = m_lastSubmittedFrameValue;
|
||||
m_lastError.clear();
|
||||
}
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
void D3D12HostDevice::WaitForFrame(std::uint32_t frameIndex) {
|
||||
if (m_frameCompletionFence == nullptr ||
|
||||
m_frameCompletionEvent == nullptr ||
|
||||
frameIndex >= m_frameFenceValues.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::uint64_t fenceValue = m_frameFenceValues[frameIndex];
|
||||
if (fenceValue == 0u ||
|
||||
m_frameCompletionFence->GetCompletedValue() >= fenceValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HRESULT hr = m_frameCompletionFence->SetEventOnCompletion(
|
||||
fenceValue,
|
||||
m_frameCompletionEvent);
|
||||
if (SUCCEEDED(hr)) {
|
||||
WaitForSingleObject(m_frameCompletionEvent, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12HostDevice::WaitForGpuIdle() {
|
||||
ID3D12CommandQueue* commandQueue = GetCommandQueue();
|
||||
if (commandQueue == nullptr ||
|
||||
m_frameCompletionFence == nullptr ||
|
||||
m_frameCompletionEvent == nullptr) {
|
||||
if (m_commandQueue != nullptr) {
|
||||
m_commandQueue->WaitForIdle();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
++m_lastSubmittedFrameValue;
|
||||
const std::uint64_t fenceValue = m_lastSubmittedFrameValue;
|
||||
const HRESULT signalHr = commandQueue->Signal(
|
||||
m_frameCompletionFence.Get(),
|
||||
fenceValue);
|
||||
if (FAILED(signalHr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_frameCompletionFence->GetCompletedValue() >= fenceValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HRESULT waitHr = m_frameCompletionFence->SetEventOnCompletion(
|
||||
fenceValue,
|
||||
m_frameCompletionEvent);
|
||||
if (SUCCEEDED(waitHr)) {
|
||||
WaitForSingleObject(m_frameCompletionEvent, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12HostDevice::ResetFrameTracking() {
|
||||
m_frameFenceValues.fill(0u);
|
||||
}
|
||||
|
||||
ID3D12Device* D3D12HostDevice::GetDevice() const {
|
||||
const D3D12Device* device = GetD3D12Device();
|
||||
return device != nullptr ? device->GetDevice() : nullptr;
|
||||
}
|
||||
|
||||
ID3D12CommandQueue* D3D12HostDevice::GetCommandQueue() const {
|
||||
const D3D12CommandQueue* queue = GetD3D12CommandQueue();
|
||||
return queue != nullptr ? queue->GetCommandQueue() : nullptr;
|
||||
}
|
||||
|
||||
const std::string& D3D12HostDevice::GetLastError() const {
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
RHIDevice* D3D12HostDevice::GetRHIDevice() const {
|
||||
return m_device;
|
||||
}
|
||||
|
||||
RHICommandQueue* D3D12HostDevice::GetRHICommandQueue() const {
|
||||
return m_commandQueue;
|
||||
}
|
||||
|
||||
RHICommandList* D3D12HostDevice::GetCommandList(std::uint32_t frameIndex) const {
|
||||
return frameIndex < m_commandLists.size()
|
||||
? m_commandLists[frameIndex]
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderContext D3D12HostDevice::GetRenderContext(
|
||||
std::uint32_t frameIndex) const {
|
||||
::XCEngine::Rendering::RenderContext context = {};
|
||||
context.device = m_device;
|
||||
context.commandList = GetCommandList(frameIndex);
|
||||
context.commandQueue = m_commandQueue;
|
||||
context.backendType = RHIType::D3D12;
|
||||
return context;
|
||||
}
|
||||
|
||||
D3D12Device* D3D12HostDevice::GetD3D12Device() const {
|
||||
return m_device != nullptr ? static_cast<D3D12Device*>(m_device) : nullptr;
|
||||
}
|
||||
|
||||
D3D12CommandQueue* D3D12HostDevice::GetD3D12CommandQueue() const {
|
||||
return m_commandQueue != nullptr ? static_cast<D3D12CommandQueue*>(m_commandQueue) : nullptr;
|
||||
}
|
||||
|
||||
D3D12CommandList* D3D12HostDevice::GetD3D12CommandList(std::uint32_t frameIndex) const {
|
||||
RHICommandList* commandList = GetCommandList(frameIndex);
|
||||
return commandList != nullptr ? static_cast<D3D12CommandList*>(commandList) : nullptr;
|
||||
}
|
||||
|
||||
bool D3D12HostDevice::InitializeFrameCompletionFence() {
|
||||
ReleaseFrameCompletionFence();
|
||||
|
||||
ID3D12Device* device = GetDevice();
|
||||
if (device == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HRESULT hr = device->CreateFence(
|
||||
0u,
|
||||
D3D12_FENCE_FLAG_NONE,
|
||||
IID_PPV_ARGS(m_frameCompletionFence.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_frameCompletionEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (m_frameCompletionEvent == nullptr) {
|
||||
m_frameCompletionFence.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lastSubmittedFrameValue = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12HostDevice::ReleaseFrameCompletionFence() {
|
||||
if (m_frameCompletionEvent != nullptr) {
|
||||
CloseHandle(m_frameCompletionEvent);
|
||||
m_frameCompletionEvent = nullptr;
|
||||
}
|
||||
|
||||
m_frameCompletionFence.Reset();
|
||||
m_frameFenceValues.fill(0u);
|
||||
m_lastSubmittedFrameValue = 0u;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
64
new_editor/app/Host/D3D12HostDevice.h
Normal file
64
new_editor/app/Host/D3D12HostDevice.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12CommandList.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12CommandQueue.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHICommandQueue.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
|
||||
#include <d3d12.h>
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class D3D12HostDevice {
|
||||
public:
|
||||
static constexpr std::uint32_t kFrameContextCount = 3u;
|
||||
|
||||
bool Initialize();
|
||||
void Shutdown();
|
||||
|
||||
bool BeginFrame(std::uint32_t frameIndex);
|
||||
bool SubmitFrame(std::uint32_t frameIndex);
|
||||
bool SignalFrameCompletion(std::uint32_t frameIndex);
|
||||
void WaitForFrame(std::uint32_t frameIndex);
|
||||
void WaitForGpuIdle();
|
||||
void ResetFrameTracking();
|
||||
|
||||
ID3D12Device* GetDevice() const;
|
||||
ID3D12CommandQueue* GetCommandQueue() const;
|
||||
const std::string& GetLastError() const;
|
||||
::XCEngine::RHI::RHIDevice* GetRHIDevice() const;
|
||||
::XCEngine::RHI::RHICommandQueue* GetRHICommandQueue() const;
|
||||
::XCEngine::RHI::RHICommandList* GetCommandList(std::uint32_t frameIndex) const;
|
||||
::XCEngine::Rendering::RenderContext GetRenderContext(std::uint32_t frameIndex) const;
|
||||
|
||||
private:
|
||||
::XCEngine::RHI::D3D12Device* GetD3D12Device() const;
|
||||
::XCEngine::RHI::D3D12CommandQueue* GetD3D12CommandQueue() const;
|
||||
::XCEngine::RHI::D3D12CommandList* GetD3D12CommandList(std::uint32_t frameIndex) const;
|
||||
bool InitializeFrameCompletionFence();
|
||||
void ReleaseFrameCompletionFence();
|
||||
|
||||
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||
::XCEngine::RHI::RHICommandQueue* m_commandQueue = nullptr;
|
||||
std::array<::XCEngine::RHI::RHICommandList*, kFrameContextCount> m_commandLists = {};
|
||||
Microsoft::WRL::ComPtr<ID3D12Fence> m_frameCompletionFence = {};
|
||||
HANDLE m_frameCompletionEvent = nullptr;
|
||||
std::array<std::uint64_t, kFrameContextCount> m_frameFenceValues = {};
|
||||
std::uint64_t m_lastSubmittedFrameValue = 0u;
|
||||
std::string m_lastError = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
169
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.cpp
Normal file
169
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "D3D12ShaderResourceDescriptorAllocator.h"
|
||||
|
||||
#include <XCEngine/RHI/RHITypes.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::RHI::D3D12DescriptorHeap;
|
||||
using ::XCEngine::RHI::D3D12Device;
|
||||
using ::XCEngine::RHI::D3D12Texture;
|
||||
using ::XCEngine::RHI::DescriptorHeapType;
|
||||
using ::XCEngine::RHI::DescriptorPoolDesc;
|
||||
using ::XCEngine::RHI::RHIDevice;
|
||||
using ::XCEngine::RHI::RHITexture;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool D3D12ShaderResourceDescriptorAllocator::Initialize(RHIDevice& device, UINT descriptorCount) {
|
||||
Shutdown();
|
||||
|
||||
DescriptorPoolDesc descriptorPoolDesc = {};
|
||||
descriptorPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||
descriptorPoolDesc.descriptorCount = descriptorCount;
|
||||
descriptorPoolDesc.shaderVisible = true;
|
||||
m_descriptorPool = device.CreateDescriptorPool(descriptorPoolDesc);
|
||||
m_descriptorHeap = dynamic_cast<D3D12DescriptorHeap*>(m_descriptorPool);
|
||||
if (m_descriptorPool == nullptr || m_descriptorHeap == nullptr) {
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device = &device;
|
||||
m_descriptorSize = m_descriptorHeap->GetDescriptorSize();
|
||||
m_descriptorCount = descriptorCount;
|
||||
m_descriptorUsage.assign(descriptorCount, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12ShaderResourceDescriptorAllocator::Shutdown() {
|
||||
if (m_descriptorPool != nullptr) {
|
||||
m_descriptorPool->Shutdown();
|
||||
delete m_descriptorPool;
|
||||
m_descriptorPool = nullptr;
|
||||
}
|
||||
|
||||
m_device = nullptr;
|
||||
m_descriptorHeap = nullptr;
|
||||
m_descriptorUsage.clear();
|
||||
m_descriptorSize = 0u;
|
||||
m_descriptorCount = 0u;
|
||||
}
|
||||
|
||||
bool D3D12ShaderResourceDescriptorAllocator::IsInitialized() const {
|
||||
return m_device != nullptr &&
|
||||
m_descriptorHeap != nullptr &&
|
||||
m_descriptorSize > 0u &&
|
||||
!m_descriptorUsage.empty();
|
||||
}
|
||||
|
||||
ID3D12DescriptorHeap* D3D12ShaderResourceDescriptorAllocator::GetDescriptorHeap() const {
|
||||
return m_descriptorHeap != nullptr ? m_descriptorHeap->GetDescriptorHeap() : nullptr;
|
||||
}
|
||||
|
||||
UINT D3D12ShaderResourceDescriptorAllocator::GetDescriptorSize() const {
|
||||
return m_descriptorSize;
|
||||
}
|
||||
|
||||
UINT D3D12ShaderResourceDescriptorAllocator::GetDescriptorCount() const {
|
||||
return m_descriptorCount;
|
||||
}
|
||||
|
||||
void D3D12ShaderResourceDescriptorAllocator::Allocate(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||
AllocateInternal(outCpuHandle, outGpuHandle);
|
||||
}
|
||||
|
||||
bool D3D12ShaderResourceDescriptorAllocator::CreateTextureDescriptor(
|
||||
RHITexture* texture,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||
if (m_device == nullptr ||
|
||||
texture == nullptr ||
|
||||
outCpuHandle == nullptr ||
|
||||
outGpuHandle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*outCpuHandle = {};
|
||||
*outGpuHandle = {};
|
||||
|
||||
auto* nativeDevice = dynamic_cast<D3D12Device*>(m_device);
|
||||
auto* nativeTexture = dynamic_cast<D3D12Texture*>(texture);
|
||||
if (nativeDevice == nullptr ||
|
||||
nativeTexture == nullptr ||
|
||||
nativeTexture->GetResource() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AllocateInternal(outCpuHandle, outGpuHandle);
|
||||
if (outCpuHandle->ptr == 0 || outGpuHandle->ptr == 0) {
|
||||
*outCpuHandle = {};
|
||||
*outGpuHandle = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D.MipLevels = 1;
|
||||
nativeDevice->GetDevice()->CreateShaderResourceView(
|
||||
nativeTexture->GetResource(),
|
||||
&srvDesc,
|
||||
*outCpuHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12ShaderResourceDescriptorAllocator::Free(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE) {
|
||||
if (m_descriptorHeap == nullptr ||
|
||||
m_descriptorSize == 0u ||
|
||||
cpuHandle.ptr < m_descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SIZE_T offset =
|
||||
cpuHandle.ptr - m_descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr;
|
||||
const std::size_t index =
|
||||
static_cast<std::size_t>(offset / m_descriptorSize);
|
||||
if (index < m_descriptorUsage.size()) {
|
||||
m_descriptorUsage[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12ShaderResourceDescriptorAllocator::AllocateInternal(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||
if (outCpuHandle == nullptr || outGpuHandle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
*outCpuHandle = {};
|
||||
*outGpuHandle = {};
|
||||
if (m_descriptorHeap == nullptr ||
|
||||
m_descriptorSize == 0u ||
|
||||
m_descriptorUsage.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < m_descriptorUsage.size(); ++index) {
|
||||
if (m_descriptorUsage[index]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_descriptorUsage[index] = true;
|
||||
outCpuHandle->ptr =
|
||||
m_descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr +
|
||||
static_cast<SIZE_T>(index) * m_descriptorSize;
|
||||
outGpuHandle->ptr =
|
||||
m_descriptorHeap->GetGPUDescriptorHandleForHeapStart().ptr +
|
||||
static_cast<UINT64>(index) * m_descriptorSize;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
55
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.h
Normal file
55
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/RHI/D3D12/D3D12DescriptorHeap.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include <d3d12.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class D3D12ShaderResourceDescriptorAllocator {
|
||||
public:
|
||||
bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u);
|
||||
void Shutdown();
|
||||
|
||||
bool IsInitialized() const;
|
||||
ID3D12DescriptorHeap* GetDescriptorHeap() const;
|
||||
UINT GetDescriptorSize() const;
|
||||
UINT GetDescriptorCount() const;
|
||||
|
||||
void Allocate(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||
bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||
void Free(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle);
|
||||
|
||||
private:
|
||||
void AllocateInternal(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||
|
||||
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorPool* m_descriptorPool = nullptr;
|
||||
::XCEngine::RHI::D3D12DescriptorHeap* m_descriptorHeap = nullptr;
|
||||
std::vector<bool> m_descriptorUsage = {};
|
||||
UINT m_descriptorSize = 0u;
|
||||
UINT m_descriptorCount = 0u;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
444
new_editor/app/Host/D3D12WindowInteropContext.cpp
Normal file
444
new_editor/app/Host/D3D12WindowInteropContext.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
#include "D3D12WindowInteropContext.h"
|
||||
|
||||
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string HrToInteropString(const char* operation, HRESULT hr) {
|
||||
char buffer[128] = {};
|
||||
sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast<unsigned int>(hr));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties(
|
||||
DXGI_FORMAT format,
|
||||
D2D1_BITMAP_OPTIONS options) {
|
||||
return D2D1::BitmapProperties1(
|
||||
options,
|
||||
D2D1::PixelFormat(format, D2D1_ALPHA_MODE_IGNORE),
|
||||
96.0f,
|
||||
96.0f);
|
||||
}
|
||||
|
||||
bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) {
|
||||
return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView &&
|
||||
texture.resourceHandle != 0u;
|
||||
}
|
||||
|
||||
void CollectInteropTextureHandles(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
std::vector<::XCEngine::UI::UITextureHandle>& outTextures) {
|
||||
outTextures.clear();
|
||||
std::unordered_set<std::uintptr_t> seenKeys = {};
|
||||
for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
|
||||
if (!IsInteropTextureHandle(command.texture) ||
|
||||
!seenKeys.insert(command.texture.resourceHandle).second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outTextures.push_back(command.texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool D3D12WindowInteropContext::Attach(
|
||||
D3D12WindowRenderer& windowRenderer,
|
||||
ID2D1Factory1& d2dFactory) {
|
||||
if (m_windowRenderer != &windowRenderer) {
|
||||
Detach();
|
||||
m_windowRenderer = &windowRenderer;
|
||||
}
|
||||
|
||||
m_d2dFactory = &d2dFactory;
|
||||
if (!EnsureInterop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasBackBufferTargets()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return RebuildBackBufferTargets();
|
||||
}
|
||||
|
||||
void D3D12WindowInteropContext::Detach() {
|
||||
ReleaseInteropState();
|
||||
m_windowRenderer = nullptr;
|
||||
m_d2dFactory = nullptr;
|
||||
m_lastError.clear();
|
||||
}
|
||||
|
||||
void D3D12WindowInteropContext::ReleaseBackBufferTargets() {
|
||||
ClearSourceTextures();
|
||||
if (m_d2dDeviceContext != nullptr) {
|
||||
m_d2dDeviceContext->SetTarget(nullptr);
|
||||
}
|
||||
if (m_d3d11DeviceContext != nullptr) {
|
||||
m_d3d11DeviceContext->ClearState();
|
||||
}
|
||||
m_backBufferTargets.clear();
|
||||
if (m_d2dDeviceContext != nullptr) {
|
||||
D2D1_TAG firstTag = 0u;
|
||||
D2D1_TAG secondTag = 0u;
|
||||
m_d2dDeviceContext->Flush(&firstTag, &secondTag);
|
||||
}
|
||||
if (m_d3d11DeviceContext != nullptr) {
|
||||
m_d3d11DeviceContext->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
bool D3D12WindowInteropContext::RebuildBackBufferTargets() {
|
||||
m_backBufferTargets.clear();
|
||||
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount();
|
||||
m_backBufferTargets.resize(backBufferCount);
|
||||
for (std::uint32_t index = 0u; index < backBufferCount; ++index) {
|
||||
const ::XCEngine::RHI::D3D12Texture* backBufferTexture =
|
||||
m_windowRenderer->GetBackBufferTexture(index);
|
||||
if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) {
|
||||
m_lastError = "Failed to resolve a D3D12 swap chain back buffer.";
|
||||
m_backBufferTargets.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D11_RESOURCE_FLAGS resourceFlags = {};
|
||||
resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
|
||||
backBufferTexture->GetResource(),
|
||||
&resourceFlags,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
D3D12_RESOURCE_STATE_PRESENT,
|
||||
IID_PPV_ARGS(m_backBufferTargets[index].wrappedResource.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr) || m_backBufferTargets[index].wrappedResource == nullptr) {
|
||||
m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr);
|
||||
m_backBufferTargets.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface = {};
|
||||
hr = m_backBufferTargets[index].wrappedResource.As(&dxgiSurface);
|
||||
if (FAILED(hr) || dxgiSurface == nullptr) {
|
||||
m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
|
||||
m_backBufferTargets.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
|
||||
BuildD2DBitmapProperties(
|
||||
backBufferTexture->GetDesc().Format,
|
||||
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW);
|
||||
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
|
||||
dxgiSurface.Get(),
|
||||
&bitmapProperties,
|
||||
m_backBufferTargets[index].targetBitmap.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_backBufferTargets[index].targetBitmap == nullptr) {
|
||||
m_lastError = HrToInteropString(
|
||||
"ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)",
|
||||
hr);
|
||||
m_backBufferTargets.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12WindowInteropContext::HasAttachedWindowRenderer() const {
|
||||
return m_windowRenderer != nullptr &&
|
||||
m_d3d11On12Device != nullptr &&
|
||||
m_d2dDeviceContext != nullptr &&
|
||||
!m_backBufferTargets.empty();
|
||||
}
|
||||
|
||||
bool D3D12WindowInteropContext::HasBackBufferTargets() const {
|
||||
return !m_backBufferTargets.empty();
|
||||
}
|
||||
|
||||
bool D3D12WindowInteropContext::PrepareSourceTextures(
|
||||
const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
ClearSourceTextures();
|
||||
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<::XCEngine::UI::UITextureHandle> textureHandles = {};
|
||||
CollectInteropTextureHandles(drawData, textureHandles);
|
||||
m_activeSourceTextures.reserve(textureHandles.size());
|
||||
|
||||
for (const ::XCEngine::UI::UITextureHandle& textureHandle : textureHandles) {
|
||||
auto* texture =
|
||||
reinterpret_cast<::XCEngine::RHI::RHITexture*>(textureHandle.resourceHandle);
|
||||
auto* nativeTexture = dynamic_cast<::XCEngine::RHI::D3D12Texture*>(texture);
|
||||
if (nativeTexture == nullptr || nativeTexture->GetResource() == nullptr) {
|
||||
m_lastError = "Failed to resolve a D3D12 source texture for UI composition.";
|
||||
ClearSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D11_RESOURCE_FLAGS resourceFlags = {};
|
||||
resourceFlags.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
SourceTextureResource resource = {};
|
||||
resource.key = textureHandle.resourceHandle;
|
||||
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
|
||||
nativeTexture->GetResource(),
|
||||
&resourceFlags,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
IID_PPV_ARGS(resource.wrappedResource.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr) || resource.wrappedResource == nullptr) {
|
||||
m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(source)", hr);
|
||||
ClearSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface = {};
|
||||
hr = resource.wrappedResource.As(&dxgiSurface);
|
||||
if (FAILED(hr) || dxgiSurface == nullptr) {
|
||||
m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
|
||||
ClearSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
|
||||
BuildD2DBitmapProperties(
|
||||
nativeTexture->GetDesc().Format,
|
||||
D2D1_BITMAP_OPTIONS_NONE);
|
||||
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
|
||||
dxgiSurface.Get(),
|
||||
&bitmapProperties,
|
||||
resource.bitmap.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || resource.bitmap == nullptr) {
|
||||
m_lastError = HrToInteropString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(source)", hr);
|
||||
ClearSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_activeBitmaps.emplace(resource.key, resource.bitmap);
|
||||
m_activeSourceTextures.push_back(std::move(resource));
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12WindowInteropContext::ClearSourceTextures() {
|
||||
m_activeBitmaps.clear();
|
||||
m_activeSourceTextures.clear();
|
||||
}
|
||||
|
||||
bool D3D12WindowInteropContext::ResolveInteropBitmap(
|
||||
const ::XCEngine::UI::UITextureHandle& texture,
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const {
|
||||
outBitmap.Reset();
|
||||
if (!IsInteropTextureHandle(texture)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto found = m_activeBitmaps.find(texture.resourceHandle);
|
||||
if (found == m_activeBitmaps.end() || found->second == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outBitmap = found->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
D3D12WindowRenderer* D3D12WindowInteropContext::GetWindowRenderer() const {
|
||||
return m_windowRenderer;
|
||||
}
|
||||
|
||||
ID3D11On12Device* D3D12WindowInteropContext::GetD3D11On12Device() const {
|
||||
return m_d3d11On12Device.Get();
|
||||
}
|
||||
|
||||
ID3D11DeviceContext* D3D12WindowInteropContext::GetD3D11DeviceContext() const {
|
||||
return m_d3d11DeviceContext.Get();
|
||||
}
|
||||
|
||||
ID2D1DeviceContext* D3D12WindowInteropContext::GetD2DDeviceContext() const {
|
||||
return m_d2dDeviceContext.Get();
|
||||
}
|
||||
|
||||
ID2D1SolidColorBrush* D3D12WindowInteropContext::GetInteropBrush() const {
|
||||
return m_interopBrush.Get();
|
||||
}
|
||||
|
||||
void D3D12WindowInteropContext::BuildAcquiredResources(
|
||||
std::uint32_t backBufferIndex,
|
||||
std::vector<ID3D11Resource*>& outResources) const {
|
||||
outResources.clear();
|
||||
|
||||
ID3D11Resource* backBufferResource = GetWrappedBackBufferResource(backBufferIndex);
|
||||
if (backBufferResource != nullptr) {
|
||||
outResources.push_back(backBufferResource);
|
||||
}
|
||||
|
||||
for (const SourceTextureResource& resource : m_activeSourceTextures) {
|
||||
if (resource.wrappedResource != nullptr) {
|
||||
outResources.push_back(resource.wrappedResource.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ID3D11Resource* D3D12WindowInteropContext::GetWrappedBackBufferResource(std::uint32_t index) const {
|
||||
return index < m_backBufferTargets.size() ? m_backBufferTargets[index].wrappedResource.Get() : nullptr;
|
||||
}
|
||||
|
||||
ID2D1Bitmap1* D3D12WindowInteropContext::GetBackBufferTargetBitmap(std::uint32_t index) const {
|
||||
return index < m_backBufferTargets.size() ? m_backBufferTargets[index].targetBitmap.Get() : nullptr;
|
||||
}
|
||||
|
||||
std::uint32_t D3D12WindowInteropContext::GetCurrentBackBufferIndex() const {
|
||||
return m_windowRenderer != nullptr && m_windowRenderer->GetSwapChain() != nullptr
|
||||
? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex()
|
||||
: 0u;
|
||||
}
|
||||
|
||||
const std::string& D3D12WindowInteropContext::GetLastError() const {
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
bool D3D12WindowInteropContext::EnsureInterop() {
|
||||
if (m_windowRenderer == nullptr) {
|
||||
m_lastError = "EnsureInterop requires an attached D3D12 window renderer.";
|
||||
return false;
|
||||
}
|
||||
if (m_d2dFactory == nullptr) {
|
||||
m_lastError = "EnsureInterop requires an initialized D2D factory.";
|
||||
return false;
|
||||
}
|
||||
if (m_d3d11On12Device != nullptr &&
|
||||
m_d2dDeviceContext != nullptr &&
|
||||
m_interopBrush != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ReleaseInteropState();
|
||||
|
||||
ID3D12Device* d3d12Device = m_windowRenderer->GetDevice();
|
||||
ID3D12CommandQueue* d3d12CommandQueue = m_windowRenderer->GetCommandQueue();
|
||||
if (d3d12Device == nullptr || d3d12CommandQueue == nullptr) {
|
||||
m_lastError = "The attached D3D12 window renderer does not expose a native device/queue.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<D3D_FEATURE_LEVEL, 4> featureLevels = {
|
||||
D3D_FEATURE_LEVEL_12_1,
|
||||
D3D_FEATURE_LEVEL_12_0,
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0
|
||||
};
|
||||
const std::array<IUnknown*, 1> commandQueues = {
|
||||
reinterpret_cast<IUnknown*>(d3d12CommandQueue)
|
||||
};
|
||||
|
||||
UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
#ifdef _DEBUG
|
||||
createFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
|
||||
D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0;
|
||||
HRESULT hr = D3D11On12CreateDevice(
|
||||
d3d12Device,
|
||||
createFlags,
|
||||
featureLevels.data(),
|
||||
static_cast<UINT>(featureLevels.size()),
|
||||
commandQueues.data(),
|
||||
static_cast<UINT>(commandQueues.size()),
|
||||
0u,
|
||||
m_d3d11Device.ReleaseAndGetAddressOf(),
|
||||
m_d3d11DeviceContext.ReleaseAndGetAddressOf(),
|
||||
&actualFeatureLevel);
|
||||
#ifdef _DEBUG
|
||||
if (FAILED(hr)) {
|
||||
createFlags &= ~D3D11_CREATE_DEVICE_DEBUG;
|
||||
hr = D3D11On12CreateDevice(
|
||||
d3d12Device,
|
||||
createFlags,
|
||||
featureLevels.data(),
|
||||
static_cast<UINT>(featureLevels.size()),
|
||||
commandQueues.data(),
|
||||
static_cast<UINT>(commandQueues.size()),
|
||||
0u,
|
||||
m_d3d11Device.ReleaseAndGetAddressOf(),
|
||||
m_d3d11DeviceContext.ReleaseAndGetAddressOf(),
|
||||
&actualFeatureLevel);
|
||||
}
|
||||
#endif
|
||||
if (FAILED(hr) || m_d3d11Device == nullptr || m_d3d11DeviceContext == nullptr) {
|
||||
m_lastError = HrToInteropString("D3D11On12CreateDevice", hr);
|
||||
ReleaseInteropState();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d3d11Device.As(&m_d3d11On12Device);
|
||||
if (FAILED(hr) || m_d3d11On12Device == nullptr) {
|
||||
m_lastError = HrToInteropString("ID3D11Device::QueryInterface(ID3D11On12Device)", hr);
|
||||
ReleaseInteropState();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice = {};
|
||||
hr = m_d3d11Device.As(&dxgiDevice);
|
||||
if (FAILED(hr) || dxgiDevice == nullptr) {
|
||||
m_lastError = HrToInteropString("ID3D11Device::QueryInterface(IDXGIDevice)", hr);
|
||||
ReleaseInteropState();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), m_d2dDevice.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_d2dDevice == nullptr) {
|
||||
m_lastError = HrToInteropString("ID2D1Factory1::CreateDevice", hr);
|
||||
ReleaseInteropState();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d2dDevice->CreateDeviceContext(
|
||||
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
|
||||
m_d2dDeviceContext.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_d2dDeviceContext == nullptr) {
|
||||
m_lastError = HrToInteropString("ID2D1Device::CreateDeviceContext", hr);
|
||||
ReleaseInteropState();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d2dDeviceContext->CreateSolidColorBrush(
|
||||
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
m_interopBrush.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_interopBrush == nullptr) {
|
||||
m_lastError = HrToInteropString("ID2D1DeviceContext::CreateSolidColorBrush", hr);
|
||||
ReleaseInteropState();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_d2dDeviceContext->SetDpi(96.0f, 96.0f);
|
||||
m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12WindowInteropContext::ReleaseInteropState() {
|
||||
ReleaseBackBufferTargets();
|
||||
m_interopBrush.Reset();
|
||||
m_d2dDeviceContext.Reset();
|
||||
m_d2dDevice.Reset();
|
||||
m_d3d11On12Device.Reset();
|
||||
m_d3d11DeviceContext.Reset();
|
||||
m_d3d11Device.Reset();
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
79
new_editor/app/Host/D3D12WindowInteropContext.h
Normal file
79
new_editor/app/Host/D3D12WindowInteropContext.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "D3D12WindowRenderer.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <d2d1_1.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <d3d11on12.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class D3D12WindowInteropContext {
|
||||
public:
|
||||
bool Attach(D3D12WindowRenderer& windowRenderer, ID2D1Factory1& d2dFactory);
|
||||
void Detach();
|
||||
void ReleaseBackBufferTargets();
|
||||
bool RebuildBackBufferTargets();
|
||||
bool HasAttachedWindowRenderer() const;
|
||||
bool HasBackBufferTargets() const;
|
||||
bool PrepareSourceTextures(const ::XCEngine::UI::UIDrawData& drawData);
|
||||
void ClearSourceTextures();
|
||||
bool ResolveInteropBitmap(
|
||||
const ::XCEngine::UI::UITextureHandle& texture,
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const;
|
||||
|
||||
D3D12WindowRenderer* GetWindowRenderer() const;
|
||||
ID3D11On12Device* GetD3D11On12Device() const;
|
||||
ID3D11DeviceContext* GetD3D11DeviceContext() const;
|
||||
ID2D1DeviceContext* GetD2DDeviceContext() const;
|
||||
ID2D1SolidColorBrush* GetInteropBrush() const;
|
||||
void BuildAcquiredResources(
|
||||
std::uint32_t backBufferIndex,
|
||||
std::vector<ID3D11Resource*>& outResources) const;
|
||||
ID3D11Resource* GetWrappedBackBufferResource(std::uint32_t index) const;
|
||||
ID2D1Bitmap1* GetBackBufferTargetBitmap(std::uint32_t index) const;
|
||||
std::uint32_t GetCurrentBackBufferIndex() const;
|
||||
const std::string& GetLastError() const;
|
||||
|
||||
private:
|
||||
struct BackBufferTarget {
|
||||
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap1> targetBitmap = {};
|
||||
};
|
||||
|
||||
struct SourceTextureResource {
|
||||
std::uintptr_t key = 0u;
|
||||
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap1> bitmap = {};
|
||||
};
|
||||
|
||||
bool EnsureInterop();
|
||||
void ReleaseInteropState();
|
||||
|
||||
D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||
ID2D1Factory1* m_d2dFactory = nullptr;
|
||||
Microsoft::WRL::ComPtr<ID3D11Device> m_d3d11Device = {};
|
||||
Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_d3d11DeviceContext = {};
|
||||
Microsoft::WRL::ComPtr<ID3D11On12Device> m_d3d11On12Device = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dDeviceContext = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_interopBrush = {};
|
||||
std::vector<BackBufferTarget> m_backBufferTargets = {};
|
||||
std::vector<SourceTextureResource> m_activeSourceTextures = {};
|
||||
std::unordered_map<std::uintptr_t, Microsoft::WRL::ComPtr<ID2D1Bitmap1>> m_activeBitmaps = {};
|
||||
std::string m_lastError = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
@@ -2,57 +2,140 @@
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
bool RenderD3D12WindowFrame(
|
||||
D3D12WindowRenderer& windowRenderer,
|
||||
const float clearColor[4],
|
||||
const D3D12WindowRenderCallback& beforePresent,
|
||||
const D3D12WindowRenderCallback& afterPresent) {
|
||||
const ::XCEngine::Rendering::RenderSurface* renderSurface =
|
||||
windowRenderer.GetCurrentRenderSurface();
|
||||
::XCEngine::Rendering::RenderContext renderContext =
|
||||
windowRenderer.GetRenderContext();
|
||||
if (!renderContext.IsValid() ||
|
||||
renderContext.commandList == nullptr ||
|
||||
renderContext.commandQueue == nullptr ||
|
||||
windowRenderer.GetSwapChain() == nullptr ||
|
||||
renderSurface == nullptr) {
|
||||
return false;
|
||||
D3D12WindowRenderLoopAttachResult D3D12WindowRenderLoop::Attach(
|
||||
NativeRenderer& uiRenderer,
|
||||
D3D12WindowRenderer& windowRenderer) {
|
||||
m_uiRenderer = &uiRenderer;
|
||||
m_windowRenderer = &windowRenderer;
|
||||
|
||||
D3D12WindowRenderLoopAttachResult result = {};
|
||||
result.hasViewportSurfacePresentation = m_uiRenderer->AttachWindowRenderer(*m_windowRenderer);
|
||||
if (!result.hasViewportSurfacePresentation) {
|
||||
const std::string& interopError = m_uiRenderer->GetLastRenderError();
|
||||
result.interopWarning = interopError.empty()
|
||||
? "native renderer d3d12 interop unavailable; falling back to hwnd renderer."
|
||||
: "native renderer d3d12 interop unavailable; falling back to hwnd renderer: " +
|
||||
interopError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderLoop::Detach() {
|
||||
if (m_uiRenderer != nullptr) {
|
||||
m_uiRenderer->DetachWindowRenderer();
|
||||
}
|
||||
|
||||
auto* d3d12CommandList =
|
||||
static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList);
|
||||
if (d3d12CommandList == nullptr) {
|
||||
return false;
|
||||
m_uiRenderer = nullptr;
|
||||
m_windowRenderer = nullptr;
|
||||
}
|
||||
|
||||
D3D12WindowRenderLoopFrameContext D3D12WindowRenderLoop::BeginFrame() const {
|
||||
D3D12WindowRenderLoopFrameContext context = {};
|
||||
if (!HasViewportSurfacePresentation()) {
|
||||
return context;
|
||||
}
|
||||
|
||||
const auto& colorAttachments = renderSurface->GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
if (!m_windowRenderer->BeginFrame()) {
|
||||
const std::string& frameError = m_windowRenderer->GetLastError();
|
||||
context.warning = frameError.empty()
|
||||
? "d3d12 frame begin failed"
|
||||
: "d3d12 frame begin failed: " + frameError;
|
||||
return context;
|
||||
}
|
||||
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView = colorAttachments[0];
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
renderTargetView,
|
||||
::XCEngine::RHI::ResourceStates::Present,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget);
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
renderContext.commandList->ClearRenderTarget(renderTargetView, clearColor);
|
||||
context.canRenderViewports = true;
|
||||
context.renderContext = m_windowRenderer->GetRenderContext();
|
||||
return context;
|
||||
}
|
||||
|
||||
if (beforePresent) {
|
||||
beforePresent(renderContext, *renderSurface);
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
D3D12WindowRenderLoopResizeResult D3D12WindowRenderLoop::ApplyResize(UINT width, UINT height) {
|
||||
D3D12WindowRenderLoopResizeResult result = {};
|
||||
if (m_uiRenderer == nullptr || m_windowRenderer == nullptr) {
|
||||
result.interopWarning = "window render loop is detached.";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (afterPresent) {
|
||||
afterPresent(renderContext, *renderSurface);
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
m_uiRenderer->Resize(width, height);
|
||||
const bool hadViewportSurfacePresentation = m_uiRenderer->HasAttachedWindowRenderer();
|
||||
if (hadViewportSurfacePresentation) {
|
||||
m_uiRenderer->ReleaseWindowRendererBackBufferTargets();
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
renderTargetView,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget,
|
||||
::XCEngine::RHI::ResourceStates::Present);
|
||||
return windowRenderer.SubmitFrame(true);
|
||||
const bool resizedWindowRenderer =
|
||||
m_windowRenderer->Resize(static_cast<int>(width), static_cast<int>(height));
|
||||
if (!resizedWindowRenderer || !m_windowRenderer->GetLastError().empty()) {
|
||||
const std::string& resizeError = m_windowRenderer->GetLastError();
|
||||
result.windowRendererWarning = resizeError.empty()
|
||||
? "window renderer resize warning."
|
||||
: "window renderer resize warning: " + resizeError;
|
||||
}
|
||||
|
||||
if (!resizedWindowRenderer) {
|
||||
if (hadViewportSurfacePresentation) {
|
||||
result.hasViewportSurfacePresentation =
|
||||
m_uiRenderer->RebuildWindowRendererBackBufferTargets();
|
||||
if (!result.hasViewportSurfacePresentation) {
|
||||
const D3D12WindowRenderLoopAttachResult attachResult =
|
||||
Attach(*m_uiRenderer, *m_windowRenderer);
|
||||
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
|
||||
result.interopWarning = attachResult.interopWarning;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (hadViewportSurfacePresentation) {
|
||||
result.hasViewportSurfacePresentation =
|
||||
m_uiRenderer->RebuildWindowRendererBackBufferTargets();
|
||||
if (!result.hasViewportSurfacePresentation) {
|
||||
const D3D12WindowRenderLoopAttachResult attachResult =
|
||||
Attach(*m_uiRenderer, *m_windowRenderer);
|
||||
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
|
||||
result.interopWarning = attachResult.interopWarning;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const D3D12WindowRenderLoopAttachResult attachResult =
|
||||
Attach(*m_uiRenderer, *m_windowRenderer);
|
||||
result.hasViewportSurfacePresentation = attachResult.hasViewportSurfacePresentation;
|
||||
result.interopWarning = attachResult.interopWarning;
|
||||
return result;
|
||||
}
|
||||
|
||||
D3D12WindowRenderLoopPresentResult D3D12WindowRenderLoop::Present(
|
||||
const ::XCEngine::UI::UIDrawData& drawData) const {
|
||||
D3D12WindowRenderLoopPresentResult result = {};
|
||||
if (m_uiRenderer == nullptr) {
|
||||
result.warning = "window render loop has no ui renderer.";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (HasViewportSurfacePresentation()) {
|
||||
result.framePresented = m_uiRenderer->RenderToWindowRenderer(drawData);
|
||||
if (!result.framePresented) {
|
||||
const std::string& composeError = m_uiRenderer->GetLastRenderError();
|
||||
result.warning = composeError.empty()
|
||||
? "d3d12 window composition failed, falling back to hwnd renderer."
|
||||
: "d3d12 window composition failed, falling back to hwnd renderer: " +
|
||||
composeError;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.framePresented) {
|
||||
result.framePresented = m_uiRenderer->Render(drawData);
|
||||
if (!result.framePresented && result.warning.empty()) {
|
||||
result.warning = m_uiRenderer->GetLastRenderError();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderLoop::HasViewportSurfacePresentation() const {
|
||||
return m_uiRenderer != nullptr &&
|
||||
m_windowRenderer != nullptr &&
|
||||
m_uiRenderer->HasAttachedWindowRenderer();
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
|
||||
@@ -1,20 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "D3D12WindowRenderer.h"
|
||||
#include "NativeRenderer.h"
|
||||
|
||||
#include <functional>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using D3D12WindowRenderCallback =
|
||||
std::function<void(
|
||||
const ::XCEngine::Rendering::RenderContext&,
|
||||
const ::XCEngine::Rendering::RenderSurface&)>;
|
||||
struct D3D12WindowRenderLoopAttachResult {
|
||||
bool hasViewportSurfacePresentation = false;
|
||||
std::string interopWarning = {};
|
||||
};
|
||||
|
||||
bool RenderD3D12WindowFrame(
|
||||
D3D12WindowRenderer& windowRenderer,
|
||||
const float clearColor[4],
|
||||
const D3D12WindowRenderCallback& beforePresent = {},
|
||||
const D3D12WindowRenderCallback& afterPresent = {});
|
||||
struct D3D12WindowRenderLoopFrameContext {
|
||||
bool canRenderViewports = false;
|
||||
::XCEngine::Rendering::RenderContext renderContext = {};
|
||||
std::string warning = {};
|
||||
};
|
||||
|
||||
struct D3D12WindowRenderLoopResizeResult {
|
||||
bool hasViewportSurfacePresentation = false;
|
||||
std::string windowRendererWarning = {};
|
||||
std::string interopWarning = {};
|
||||
};
|
||||
|
||||
struct D3D12WindowRenderLoopPresentResult {
|
||||
bool framePresented = false;
|
||||
std::string warning = {};
|
||||
};
|
||||
|
||||
class D3D12WindowRenderLoop {
|
||||
public:
|
||||
D3D12WindowRenderLoopAttachResult Attach(
|
||||
NativeRenderer& uiRenderer,
|
||||
D3D12WindowRenderer& windowRenderer);
|
||||
void Detach();
|
||||
|
||||
D3D12WindowRenderLoopFrameContext BeginFrame() const;
|
||||
D3D12WindowRenderLoopResizeResult ApplyResize(UINT width, UINT height);
|
||||
D3D12WindowRenderLoopPresentResult Present(
|
||||
const ::XCEngine::UI::UIDrawData& drawData) const;
|
||||
|
||||
bool HasViewportSurfacePresentation() const;
|
||||
|
||||
private:
|
||||
NativeRenderer* m_uiRenderer = nullptr;
|
||||
D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
|
||||
@@ -1,33 +1,10 @@
|
||||
#include "D3D12WindowRenderer.h"
|
||||
|
||||
#include <XCEngine/RHI/RHIFactory.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using ::XCEngine::RHI::CommandListDesc;
|
||||
using ::XCEngine::RHI::CommandQueueDesc;
|
||||
using ::XCEngine::RHI::DescriptorHeapType;
|
||||
using ::XCEngine::RHI::DescriptorPoolDesc;
|
||||
using ::XCEngine::RHI::D3D12CommandList;
|
||||
using ::XCEngine::RHI::D3D12CommandQueue;
|
||||
using ::XCEngine::RHI::D3D12DescriptorHeap;
|
||||
using ::XCEngine::RHI::D3D12Device;
|
||||
using ::XCEngine::RHI::D3D12SwapChain;
|
||||
using ::XCEngine::RHI::D3D12Texture;
|
||||
using ::XCEngine::RHI::Format;
|
||||
using ::XCEngine::RHI::ResourceStates;
|
||||
using ::XCEngine::RHI::ResourceViewDesc;
|
||||
using ::XCEngine::RHI::ResourceViewDimension;
|
||||
using ::XCEngine::RHI::RHICommandList;
|
||||
using ::XCEngine::RHI::RHICommandQueue;
|
||||
using ::XCEngine::RHI::RHIDevice;
|
||||
using ::XCEngine::RHI::RHIDeviceDesc;
|
||||
using ::XCEngine::RHI::RHIFactory;
|
||||
using ::XCEngine::RHI::RHISwapChain;
|
||||
using ::XCEngine::RHI::RHIType;
|
||||
using ::XCEngine::RHI::SwapChainDesc;
|
||||
using ::XCEngine::RHI::D3D12Texture;
|
||||
|
||||
bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) {
|
||||
Shutdown();
|
||||
@@ -37,234 +14,55 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwnd = hwnd;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
if (!m_hostDevice.Initialize()) {
|
||||
m_lastError = m_hostDevice.GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device = RHIFactory::CreateRHIDevice(RHIType::D3D12);
|
||||
if (m_device == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 RHI device.";
|
||||
if (!m_presenter.Initialize(m_hostDevice, hwnd, width, height)) {
|
||||
m_lastError = m_presenter.GetLastError();
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
RHIDeviceDesc deviceDesc = {};
|
||||
#ifdef _DEBUG
|
||||
deviceDesc.enableDebugLayer = true;
|
||||
deviceDesc.enableGPUValidation = true;
|
||||
#endif
|
||||
if (!m_device->Initialize(deviceDesc)) {
|
||||
m_lastError = "Failed to initialize the D3D12 RHI device.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandQueueDesc queueDesc = {};
|
||||
queueDesc.queueType = static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
|
||||
m_commandQueue = m_device->CreateCommandQueue(queueDesc);
|
||||
if (m_commandQueue == nullptr || GetD3D12CommandQueue() == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 command queue.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandListDesc commandListDesc = {};
|
||||
commandListDesc.commandListType = static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
|
||||
for (std::uint32_t commandListIndex = 0; commandListIndex < kSwapChainBufferCount; ++commandListIndex) {
|
||||
m_commandLists[commandListIndex] = m_device->CreateCommandList(commandListDesc);
|
||||
if (m_commandLists[commandListIndex] == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 command list.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_activeBackBufferIndex = commandListIndex;
|
||||
if (GetD3D12CommandList() == nullptr) {
|
||||
m_lastError = "Failed to resolve the D3D12 command list.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_commandLists[commandListIndex]->Close();
|
||||
}
|
||||
m_activeBackBufferIndex = 0u;
|
||||
|
||||
SwapChainDesc swapChainDesc = {};
|
||||
swapChainDesc.windowHandle = hwnd;
|
||||
swapChainDesc.width = static_cast<std::uint32_t>(width);
|
||||
swapChainDesc.height = static_cast<std::uint32_t>(height);
|
||||
swapChainDesc.bufferCount = kSwapChainBufferCount;
|
||||
m_swapChain = m_device->CreateSwapChain(swapChainDesc, m_commandQueue);
|
||||
if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 swap chain.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
DescriptorPoolDesc srvPoolDesc = {};
|
||||
srvPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||
srvPoolDesc.descriptorCount = kSrvDescriptorCount;
|
||||
srvPoolDesc.shaderVisible = true;
|
||||
m_srvPool = m_device->CreateDescriptorPool(srvPoolDesc);
|
||||
m_srvHeap = dynamic_cast<D3D12DescriptorHeap*>(m_srvPool);
|
||||
if (m_srvPool == nullptr || m_srvHeap == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 SRV descriptor heap.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_srvDescriptorSize = m_srvHeap->GetDescriptorSize();
|
||||
m_srvUsage.assign(kSrvDescriptorCount, false);
|
||||
if (!RecreateBackBufferViews()) {
|
||||
m_lastError = "Failed to create swap chain back buffer views.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitializeFrameCompletionFence()) {
|
||||
m_lastError = "Failed to create the host frame completion fence.";
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_backBufferFenceValues.fill(0u);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::Shutdown() {
|
||||
WaitForGpuIdle();
|
||||
ReleaseFrameCompletionFence();
|
||||
ReleaseBackBufferViews();
|
||||
|
||||
if (m_srvPool != nullptr) {
|
||||
m_srvPool->Shutdown();
|
||||
delete m_srvPool;
|
||||
m_srvPool = nullptr;
|
||||
}
|
||||
m_srvHeap = nullptr;
|
||||
m_srvUsage.clear();
|
||||
|
||||
if (m_swapChain != nullptr) {
|
||||
m_swapChain->Shutdown();
|
||||
delete m_swapChain;
|
||||
m_swapChain = nullptr;
|
||||
}
|
||||
|
||||
for (auto*& commandList : m_commandLists) {
|
||||
if (commandList != nullptr) {
|
||||
commandList->Shutdown();
|
||||
delete commandList;
|
||||
commandList = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_commandQueue != nullptr) {
|
||||
m_commandQueue->Shutdown();
|
||||
delete m_commandQueue;
|
||||
m_commandQueue = nullptr;
|
||||
}
|
||||
|
||||
if (m_device != nullptr) {
|
||||
m_device->Shutdown();
|
||||
delete m_device;
|
||||
m_device = nullptr;
|
||||
}
|
||||
|
||||
m_hwnd = nullptr;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_presenter.Shutdown();
|
||||
m_hostDevice.Shutdown();
|
||||
m_activeBackBufferIndex = 0u;
|
||||
m_srvDescriptorSize = 0;
|
||||
m_lastError.clear();
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::Resize(int width, int height) {
|
||||
if (width <= 0 || height <= 0 || m_swapChain == nullptr) {
|
||||
if (!m_presenter.Resize(width, height)) {
|
||||
m_lastError = m_presenter.GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_width == width && m_height == height) {
|
||||
m_lastError.clear();
|
||||
m_activeBackBufferIndex = m_presenter.GetCurrentBackBufferIndex();
|
||||
m_lastError = m_presenter.GetLastError();
|
||||
if (!m_lastError.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WaitForGpuIdle();
|
||||
ReleaseBackBufferCommandReferences();
|
||||
ReleaseBackBufferViews();
|
||||
D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (d3d12SwapChain == nullptr) {
|
||||
m_lastError = "Resize could not resolve the native D3D12 swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
d3d12SwapChain->Resize(
|
||||
static_cast<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(height));
|
||||
const HRESULT resizeHr = d3d12SwapChain->GetLastResizeResult();
|
||||
if (FAILED(resizeHr)) {
|
||||
std::ostringstream error = {};
|
||||
error << "ResizeBuffers failed. requested="
|
||||
<< width
|
||||
<< 'x'
|
||||
<< height
|
||||
<< " hr=0x"
|
||||
<< std::uppercase
|
||||
<< std::hex
|
||||
<< static_cast<unsigned long>(resizeHr);
|
||||
m_lastError = error.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
const D3D12Texture* backBufferTexture = GetBackBufferTexture(0u);
|
||||
if (backBufferTexture == nullptr) {
|
||||
m_lastError = "Resize failed to restore swap chain back buffers after ResizeBuffers.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = static_cast<int>(backBufferTexture->GetWidth());
|
||||
m_height = static_cast<int>(backBufferTexture->GetHeight());
|
||||
m_activeBackBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
m_backBufferFenceValues.fill(0u);
|
||||
if (!RecreateBackBufferViews()) {
|
||||
m_lastError = "Resize failed to recreate swap chain back buffer views.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_width != width || m_height != height) {
|
||||
std::ostringstream warning = {};
|
||||
warning << "Resize applied the current swap chain size. requested="
|
||||
<< width
|
||||
<< 'x'
|
||||
<< height
|
||||
<< " actual="
|
||||
<< m_width
|
||||
<< 'x'
|
||||
<< m_height;
|
||||
m_lastError = warning.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::BeginFrame() {
|
||||
if (m_swapChain == nullptr || m_srvHeap == nullptr) {
|
||||
m_lastError = "BeginFrame requires a swap chain, command list, and SRV heap.";
|
||||
if (m_presenter.GetSwapChain() == nullptr) {
|
||||
m_lastError = "BeginFrame requires an initialized swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_activeBackBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
WaitForBackBufferFrame(m_activeBackBufferIndex);
|
||||
::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList();
|
||||
D3D12CommandList* d3d12CommandList = GetD3D12CommandList();
|
||||
if (commandList == nullptr || d3d12CommandList == nullptr) {
|
||||
m_lastError = "BeginFrame could not resolve the active command list.";
|
||||
m_activeBackBufferIndex = m_presenter.GetCurrentBackBufferIndex();
|
||||
if (!m_hostDevice.BeginFrame(m_activeBackBufferIndex)) {
|
||||
m_lastError = m_hostDevice.GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
commandList->Reset();
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -276,439 +74,86 @@ bool D3D12WindowRenderer::PreparePresentSurface() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_swapChain == nullptr) {
|
||||
m_lastError = "PreparePresentSurface requires an initialized swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
if (backBufferIndex >= m_backBufferViews.size() ||
|
||||
m_backBufferViews[backBufferIndex] == nullptr) {
|
||||
std::ostringstream error = {};
|
||||
error << "PreparePresentSurface could not find the current swap chain RTV. index="
|
||||
<< backBufferIndex
|
||||
<< " rtvCount="
|
||||
<< m_backBufferViews.size();
|
||||
m_lastError = error.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
m_backBufferViews[backBufferIndex],
|
||||
::XCEngine::RHI::ResourceStates::Present,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
const bool prepared = m_presenter.PreparePresentSurface(renderContext);
|
||||
m_lastError = prepared ? std::string() : m_presenter.GetLastError();
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::SubmitFrame(bool presentSwapChain) {
|
||||
::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList();
|
||||
if (commandList == nullptr || m_commandQueue == nullptr) {
|
||||
m_lastError = "SubmitFrame requires an initialized command list and queue.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (presentSwapChain && m_swapChain == nullptr) {
|
||||
if (presentSwapChain && m_presenter.GetSwapChain() == nullptr) {
|
||||
m_lastError = "SubmitFrame requested present without a swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
commandList->Close();
|
||||
void* commandLists[] = { commandList };
|
||||
m_commandQueue->ExecuteCommandLists(1, commandLists);
|
||||
if (!m_hostDevice.SubmitFrame(m_activeBackBufferIndex)) {
|
||||
m_lastError = m_hostDevice.GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (presentSwapChain) {
|
||||
m_swapChain->Present(1, 0);
|
||||
if (!m_presenter.PresentFrame()) {
|
||||
m_lastError = m_presenter.GetLastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::SignalFrameCompletion() {
|
||||
ID3D12CommandQueue* commandQueue = GetCommandQueue();
|
||||
if (commandQueue == nullptr || m_frameCompletionFence == nullptr) {
|
||||
if (!m_hostDevice.SignalFrameCompletion(m_activeBackBufferIndex)) {
|
||||
m_lastError = m_hostDevice.GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
++m_lastSubmittedFrameValue;
|
||||
const HRESULT hr = commandQueue->Signal(
|
||||
m_frameCompletionFence.Get(),
|
||||
m_lastSubmittedFrameValue);
|
||||
if (SUCCEEDED(hr) && m_activeBackBufferIndex < m_backBufferFenceValues.size()) {
|
||||
m_backBufferFenceValues[m_activeBackBufferIndex] = m_lastSubmittedFrameValue;
|
||||
}
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::PresentFrame() {
|
||||
if (m_swapChain == nullptr) {
|
||||
m_lastError = "PresentFrame requires an initialized swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_swapChain->Present(1, 0);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
ID3D12Device* D3D12WindowRenderer::GetDevice() const {
|
||||
const D3D12Device* device = GetD3D12Device();
|
||||
return device != nullptr ? device->GetDevice() : nullptr;
|
||||
bool D3D12WindowRenderer::PresentFrame() {
|
||||
const bool presented = m_presenter.PresentFrame();
|
||||
m_lastError = presented ? std::string() : m_presenter.GetLastError();
|
||||
return presented;
|
||||
}
|
||||
|
||||
ID3D12DescriptorHeap* D3D12WindowRenderer::GetSrvHeap() const {
|
||||
return m_srvHeap != nullptr ? m_srvHeap->GetDescriptorHeap() : nullptr;
|
||||
ID3D12Device* D3D12WindowRenderer::GetDevice() const {
|
||||
return m_hostDevice.GetDevice();
|
||||
}
|
||||
|
||||
ID3D12CommandQueue* D3D12WindowRenderer::GetCommandQueue() const {
|
||||
const D3D12CommandQueue* queue = GetD3D12CommandQueue();
|
||||
return queue != nullptr ? queue->GetCommandQueue() : nullptr;
|
||||
return m_hostDevice.GetCommandQueue();
|
||||
}
|
||||
|
||||
const std::string& D3D12WindowRenderer::GetLastError() const {
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::AllocateShaderResourceDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||
AllocateShaderResourceDescriptorInternal(outCpuHandle, outGpuHandle);
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::CreateShaderResourceTextureDescriptor(
|
||||
RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||
if (device == nullptr ||
|
||||
texture == nullptr ||
|
||||
outCpuHandle == nullptr ||
|
||||
outGpuHandle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*outCpuHandle = {};
|
||||
*outGpuHandle = {};
|
||||
|
||||
auto* nativeDevice = dynamic_cast<D3D12Device*>(device);
|
||||
auto* nativeTexture = dynamic_cast<D3D12Texture*>(texture);
|
||||
if (nativeDevice == nullptr ||
|
||||
nativeTexture == nullptr ||
|
||||
nativeTexture->GetResource() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AllocateShaderResourceDescriptorInternal(outCpuHandle, outGpuHandle);
|
||||
if (outCpuHandle->ptr == 0 || outGpuHandle->ptr == 0) {
|
||||
*outCpuHandle = {};
|
||||
*outGpuHandle = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D.MipLevels = 1;
|
||||
nativeDevice->GetDevice()->CreateShaderResourceView(
|
||||
nativeTexture->GetResource(),
|
||||
&srvDesc,
|
||||
*outCpuHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::FreeShaderResourceDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle) {
|
||||
FreeShaderResourceDescriptorInternal(cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
UINT D3D12WindowRenderer::GetSrvDescriptorSize() const {
|
||||
return m_srvDescriptorSize;
|
||||
}
|
||||
|
||||
UINT D3D12WindowRenderer::GetSrvDescriptorCount() const {
|
||||
return kSrvDescriptorCount;
|
||||
}
|
||||
|
||||
RHIDevice* D3D12WindowRenderer::GetRHIDevice() const {
|
||||
return m_device;
|
||||
return m_hostDevice.GetRHIDevice();
|
||||
}
|
||||
|
||||
RHISwapChain* D3D12WindowRenderer::GetSwapChain() const {
|
||||
return m_swapChain;
|
||||
return m_presenter.GetSwapChain();
|
||||
}
|
||||
|
||||
const ::XCEngine::Rendering::RenderSurface* D3D12WindowRenderer::GetCurrentRenderSurface() const {
|
||||
if (m_swapChain == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
if (backBufferIndex >= m_backBufferSurfaces.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &m_backBufferSurfaces[backBufferIndex];
|
||||
return m_presenter.GetCurrentRenderSurface();
|
||||
}
|
||||
|
||||
const D3D12Texture* D3D12WindowRenderer::GetCurrentBackBufferTexture() const {
|
||||
if (m_swapChain == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return GetBackBufferTexture(m_swapChain->GetCurrentBackBufferIndex());
|
||||
return m_presenter.GetCurrentBackBufferTexture();
|
||||
}
|
||||
|
||||
const D3D12Texture* D3D12WindowRenderer::GetBackBufferTexture(std::uint32_t index) const {
|
||||
const D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (d3d12SwapChain == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return d3d12SwapChain->TryGetBackBuffer(index);
|
||||
return m_presenter.GetBackBufferTexture(index);
|
||||
}
|
||||
|
||||
std::uint32_t D3D12WindowRenderer::GetBackBufferCount() const {
|
||||
return kSwapChainBufferCount;
|
||||
return m_presenter.GetBackBufferCount();
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderContext D3D12WindowRenderer::GetRenderContext() const {
|
||||
::XCEngine::Rendering::RenderContext context = {};
|
||||
context.device = m_device;
|
||||
context.commandList = GetCurrentCommandList();
|
||||
context.commandQueue = m_commandQueue;
|
||||
context.backendType = RHIType::D3D12;
|
||||
return context;
|
||||
}
|
||||
|
||||
D3D12Device* D3D12WindowRenderer::GetD3D12Device() const {
|
||||
return m_device != nullptr ? static_cast<D3D12Device*>(m_device) : nullptr;
|
||||
}
|
||||
|
||||
D3D12CommandQueue* D3D12WindowRenderer::GetD3D12CommandQueue() const {
|
||||
return m_commandQueue != nullptr ? static_cast<D3D12CommandQueue*>(m_commandQueue) : nullptr;
|
||||
}
|
||||
|
||||
D3D12CommandList* D3D12WindowRenderer::GetD3D12CommandList() const {
|
||||
::XCEngine::RHI::RHICommandList* commandList = GetCurrentCommandList();
|
||||
return commandList != nullptr ? static_cast<D3D12CommandList*>(commandList) : nullptr;
|
||||
}
|
||||
|
||||
D3D12SwapChain* D3D12WindowRenderer::GetD3D12SwapChain() const {
|
||||
return m_swapChain != nullptr ? static_cast<D3D12SwapChain*>(m_swapChain) : nullptr;
|
||||
}
|
||||
|
||||
::XCEngine::RHI::RHICommandList* D3D12WindowRenderer::GetCurrentCommandList() const {
|
||||
return m_activeBackBufferIndex < m_commandLists.size()
|
||||
? m_commandLists[m_activeBackBufferIndex]
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::AllocateShaderResourceDescriptorInternal(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||
if (outCpuHandle == nullptr || outGpuHandle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
*outCpuHandle = {};
|
||||
*outGpuHandle = {};
|
||||
if (m_srvHeap == nullptr ||
|
||||
m_srvDescriptorSize == 0 ||
|
||||
m_srvUsage.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < m_srvUsage.size(); ++index) {
|
||||
if (m_srvUsage[index]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_srvUsage[index] = true;
|
||||
outCpuHandle->ptr =
|
||||
m_srvHeap->GetCPUDescriptorHandleForHeapStart().ptr +
|
||||
static_cast<SIZE_T>(index) * m_srvDescriptorSize;
|
||||
outGpuHandle->ptr =
|
||||
m_srvHeap->GetGPUDescriptorHandleForHeapStart().ptr +
|
||||
static_cast<UINT64>(index) * m_srvDescriptorSize;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::FreeShaderResourceDescriptorInternal(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE) {
|
||||
if (m_srvHeap == nullptr ||
|
||||
m_srvDescriptorSize == 0 ||
|
||||
cpuHandle.ptr < m_srvHeap->GetCPUDescriptorHandleForHeapStart().ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SIZE_T offset =
|
||||
cpuHandle.ptr - m_srvHeap->GetCPUDescriptorHandleForHeapStart().ptr;
|
||||
const std::size_t index =
|
||||
static_cast<std::size_t>(offset / m_srvDescriptorSize);
|
||||
if (index < m_srvUsage.size()) {
|
||||
m_srvUsage[index] = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::InitializeFrameCompletionFence() {
|
||||
ReleaseFrameCompletionFence();
|
||||
|
||||
ID3D12Device* device = GetDevice();
|
||||
if (device == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HRESULT hr = device->CreateFence(
|
||||
0u,
|
||||
D3D12_FENCE_FLAG_NONE,
|
||||
IID_PPV_ARGS(m_frameCompletionFence.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_frameCompletionEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (m_frameCompletionEvent == nullptr) {
|
||||
m_frameCompletionFence.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lastSubmittedFrameValue = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::ReleaseFrameCompletionFence() {
|
||||
if (m_frameCompletionEvent != nullptr) {
|
||||
CloseHandle(m_frameCompletionEvent);
|
||||
m_frameCompletionEvent = nullptr;
|
||||
}
|
||||
|
||||
m_frameCompletionFence.Reset();
|
||||
m_backBufferFenceValues.fill(0u);
|
||||
m_activeBackBufferIndex = 0u;
|
||||
m_lastSubmittedFrameValue = 0u;
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::WaitForBackBufferFrame(std::uint32_t backBufferIndex) {
|
||||
if (m_frameCompletionFence == nullptr ||
|
||||
m_frameCompletionEvent == nullptr ||
|
||||
backBufferIndex >= m_backBufferFenceValues.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::uint64_t fenceValue = m_backBufferFenceValues[backBufferIndex];
|
||||
if (fenceValue == 0u ||
|
||||
m_frameCompletionFence->GetCompletedValue() >= fenceValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HRESULT hr = m_frameCompletionFence->SetEventOnCompletion(
|
||||
fenceValue,
|
||||
m_frameCompletionEvent);
|
||||
if (SUCCEEDED(hr)) {
|
||||
WaitForSingleObject(m_frameCompletionEvent, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::WaitForGpuIdle() {
|
||||
ID3D12CommandQueue* commandQueue = GetCommandQueue();
|
||||
if (commandQueue == nullptr ||
|
||||
m_frameCompletionFence == nullptr ||
|
||||
m_frameCompletionEvent == nullptr) {
|
||||
if (m_commandQueue != nullptr) {
|
||||
m_commandQueue->WaitForIdle();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
++m_lastSubmittedFrameValue;
|
||||
const std::uint64_t fenceValue = m_lastSubmittedFrameValue;
|
||||
const HRESULT signalHr = commandQueue->Signal(
|
||||
m_frameCompletionFence.Get(),
|
||||
fenceValue);
|
||||
if (FAILED(signalHr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_frameCompletionFence->GetCompletedValue() >= fenceValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HRESULT waitHr = m_frameCompletionFence->SetEventOnCompletion(
|
||||
fenceValue,
|
||||
m_frameCompletionEvent);
|
||||
if (SUCCEEDED(waitHr)) {
|
||||
WaitForSingleObject(m_frameCompletionEvent, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::ReleaseBackBufferCommandReferences() {
|
||||
for (auto* commandList : m_commandLists) {
|
||||
if (commandList == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
commandList->Reset();
|
||||
commandList->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12WindowRenderer::ReleaseBackBufferViews() {
|
||||
for (auto* view : m_backBufferViews) {
|
||||
if (view != nullptr) {
|
||||
view->Shutdown();
|
||||
delete view;
|
||||
}
|
||||
}
|
||||
m_backBufferViews.clear();
|
||||
m_backBufferSurfaces.clear();
|
||||
}
|
||||
|
||||
bool D3D12WindowRenderer::RecreateBackBufferViews() {
|
||||
D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (m_device == nullptr || d3d12SwapChain == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_backBufferViews.resize(kSwapChainBufferCount, nullptr);
|
||||
m_backBufferSurfaces.resize(kSwapChainBufferCount);
|
||||
|
||||
ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = static_cast<std::uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
viewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||
|
||||
for (std::uint32_t backBufferIndex = 0; backBufferIndex < kSwapChainBufferCount; ++backBufferIndex) {
|
||||
D3D12Texture* backBufferTexture = d3d12SwapChain->TryGetBackBuffer(backBufferIndex);
|
||||
if (backBufferTexture == nullptr) {
|
||||
ReleaseBackBufferViews();
|
||||
m_lastError = "RecreateBackBufferViews could not resolve swap chain back buffer " +
|
||||
std::to_string(backBufferIndex) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_backBufferViews[backBufferIndex] = m_device->CreateRenderTargetView(
|
||||
backBufferTexture,
|
||||
viewDesc);
|
||||
if (m_backBufferViews[backBufferIndex] == nullptr) {
|
||||
ReleaseBackBufferViews();
|
||||
m_lastError = "RecreateBackBufferViews failed to create RTV for swap chain back buffer " +
|
||||
std::to_string(backBufferIndex) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex];
|
||||
surface = ::XCEngine::Rendering::RenderSurface(
|
||||
static_cast<std::uint32_t>(m_width),
|
||||
static_cast<std::uint32_t>(m_height));
|
||||
surface.SetColorAttachment(m_backBufferViews[backBufferIndex]);
|
||||
surface.SetAutoTransitionEnabled(false);
|
||||
surface.SetColorStateBefore(ResourceStates::RenderTarget);
|
||||
surface.SetColorStateAfter(ResourceStates::RenderTarget);
|
||||
}
|
||||
|
||||
return true;
|
||||
return m_hostDevice.GetRenderContext(m_activeBackBufferIndex);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
|
||||
@@ -4,36 +4,24 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "D3D12HostDevice.h"
|
||||
#include "D3D12WindowSwapChainPresenter.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12CommandList.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12CommandQueue.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12DescriptorHeap.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12SwapChain.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHICommandQueue.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/RHI/RHISwapChain.h>
|
||||
|
||||
#include <d3d12.h>
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class D3D12WindowRenderer {
|
||||
public:
|
||||
static constexpr UINT kSrvDescriptorCount = 64;
|
||||
static constexpr std::uint32_t kSwapChainBufferCount = 3;
|
||||
static constexpr std::uint32_t kSwapChainBufferCount =
|
||||
D3D12WindowSwapChainPresenter::kSwapChainBufferCount;
|
||||
|
||||
bool Initialize(HWND hwnd, int width, int height);
|
||||
void Shutdown();
|
||||
@@ -45,22 +33,8 @@ public:
|
||||
bool PresentFrame();
|
||||
|
||||
ID3D12Device* GetDevice() const;
|
||||
ID3D12DescriptorHeap* GetSrvHeap() const;
|
||||
ID3D12CommandQueue* GetCommandQueue() const;
|
||||
const std::string& GetLastError() const;
|
||||
void AllocateShaderResourceDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||
bool CreateShaderResourceTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||
void FreeShaderResourceDescriptor(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle);
|
||||
UINT GetSrvDescriptorSize() const;
|
||||
UINT GetSrvDescriptorCount() const;
|
||||
::XCEngine::RHI::RHIDevice* GetRHIDevice() const;
|
||||
::XCEngine::RHI::RHISwapChain* GetSwapChain() const;
|
||||
const ::XCEngine::Rendering::RenderSurface* GetCurrentRenderSurface() const;
|
||||
@@ -70,45 +44,10 @@ public:
|
||||
::XCEngine::Rendering::RenderContext GetRenderContext() const;
|
||||
|
||||
private:
|
||||
::XCEngine::RHI::D3D12Device* GetD3D12Device() const;
|
||||
::XCEngine::RHI::D3D12CommandQueue* GetD3D12CommandQueue() const;
|
||||
::XCEngine::RHI::D3D12CommandList* GetD3D12CommandList() const;
|
||||
::XCEngine::RHI::D3D12SwapChain* GetD3D12SwapChain() const;
|
||||
::XCEngine::RHI::RHICommandList* GetCurrentCommandList() const;
|
||||
void AllocateShaderResourceDescriptorInternal(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||
void FreeShaderResourceDescriptorInternal(
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle);
|
||||
bool InitializeFrameCompletionFence();
|
||||
void ReleaseFrameCompletionFence();
|
||||
void WaitForBackBufferFrame(std::uint32_t backBufferIndex);
|
||||
void WaitForGpuIdle();
|
||||
void ReleaseBackBufferCommandReferences();
|
||||
void ReleaseBackBufferViews();
|
||||
bool RecreateBackBufferViews();
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
|
||||
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||
::XCEngine::RHI::RHICommandQueue* m_commandQueue = nullptr;
|
||||
std::array<::XCEngine::RHI::RHICommandList*, kSwapChainBufferCount> m_commandLists = {};
|
||||
::XCEngine::RHI::RHISwapChain* m_swapChain = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorPool* m_srvPool = nullptr;
|
||||
::XCEngine::RHI::D3D12DescriptorHeap* m_srvHeap = nullptr;
|
||||
std::vector<bool> m_srvUsage = {};
|
||||
std::vector<::XCEngine::RHI::RHIResourceView*> m_backBufferViews = {};
|
||||
std::vector<::XCEngine::Rendering::RenderSurface> m_backBufferSurfaces = {};
|
||||
Microsoft::WRL::ComPtr<ID3D12Fence> m_frameCompletionFence = {};
|
||||
HANDLE m_frameCompletionEvent = nullptr;
|
||||
std::array<std::uint64_t, kSwapChainBufferCount> m_backBufferFenceValues = {};
|
||||
D3D12HostDevice m_hostDevice = {};
|
||||
D3D12WindowSwapChainPresenter m_presenter = {};
|
||||
std::uint32_t m_activeBackBufferIndex = 0u;
|
||||
std::uint64_t m_lastSubmittedFrameValue = 0;
|
||||
std::string m_lastError = {};
|
||||
UINT m_srvDescriptorSize = 0;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
|
||||
388
new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp
Normal file
388
new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "D3D12WindowSwapChainPresenter.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
using ::XCEngine::RHI::D3D12SwapChain;
|
||||
using ::XCEngine::RHI::D3D12Texture;
|
||||
using ::XCEngine::RHI::Format;
|
||||
using ::XCEngine::RHI::ResourceStates;
|
||||
using ::XCEngine::RHI::ResourceViewDesc;
|
||||
using ::XCEngine::RHI::ResourceViewDimension;
|
||||
using ::XCEngine::RHI::RHISwapChain;
|
||||
using ::XCEngine::RHI::SwapChainDesc;
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::Initialize(
|
||||
D3D12HostDevice& hostDevice,
|
||||
HWND hwnd,
|
||||
int width,
|
||||
int height) {
|
||||
Shutdown();
|
||||
|
||||
if (hwnd == nullptr || width <= 0 || height <= 0) {
|
||||
m_lastError = "Initialize rejected an invalid hwnd or size.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hostDevice.GetRHIDevice() == nullptr || hostDevice.GetRHICommandQueue() == nullptr) {
|
||||
m_lastError = "Initialize requires an initialized host D3D12 device.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwnd = hwnd;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_hostDevice = &hostDevice;
|
||||
|
||||
if (!CreateSwapChain(width, height)) {
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::CreateSwapChain(int width, int height) {
|
||||
if (m_hostDevice == nullptr || m_hostDevice->GetRHIDevice() == nullptr || m_hostDevice->GetRHICommandQueue() == nullptr) {
|
||||
m_lastError = "CreateSwapChain requires an initialized host D3D12 device.";
|
||||
return false;
|
||||
}
|
||||
|
||||
SwapChainDesc swapChainDesc = {};
|
||||
swapChainDesc.windowHandle = m_hwnd;
|
||||
swapChainDesc.width = static_cast<std::uint32_t>(width);
|
||||
swapChainDesc.height = static_cast<std::uint32_t>(height);
|
||||
swapChainDesc.bufferCount = kSwapChainBufferCount;
|
||||
m_swapChain = m_hostDevice->GetRHIDevice()->CreateSwapChain(
|
||||
swapChainDesc,
|
||||
m_hostDevice->GetRHICommandQueue());
|
||||
if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) {
|
||||
m_lastError = "Failed to create the D3D12 swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfigureFrameLatency();
|
||||
|
||||
if (!RecreateBackBufferViews()) {
|
||||
m_lastError = "Failed to create swap chain back buffer views.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12WindowSwapChainPresenter::ConfigureFrameLatency() {
|
||||
D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (d3d12SwapChain == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* nativeSwapChain =
|
||||
static_cast<IDXGISwapChain3*>(d3d12SwapChain->GetNativeHandle());
|
||||
if (nativeSwapChain == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
nativeSwapChain->SetMaximumFrameLatency(1u);
|
||||
}
|
||||
|
||||
void D3D12WindowSwapChainPresenter::DestroySwapChain() {
|
||||
ReleaseBackBufferViews();
|
||||
|
||||
if (m_swapChain != nullptr) {
|
||||
m_swapChain->Shutdown();
|
||||
delete m_swapChain;
|
||||
m_swapChain = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::RecreateSwapChain(int width, int height) {
|
||||
DestroySwapChain();
|
||||
m_hostDevice->ResetFrameTracking();
|
||||
return CreateSwapChain(width, height);
|
||||
}
|
||||
|
||||
void D3D12WindowSwapChainPresenter::Shutdown() {
|
||||
if (m_hostDevice != nullptr) {
|
||||
m_hostDevice->WaitForGpuIdle();
|
||||
}
|
||||
|
||||
DestroySwapChain();
|
||||
|
||||
m_hwnd = nullptr;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_hostDevice = nullptr;
|
||||
m_lastError.clear();
|
||||
}
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::Resize(int width, int height) {
|
||||
if (width <= 0 || height <= 0 || m_swapChain == nullptr || m_hostDevice == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_width == width && m_height == height) {
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_hostDevice->WaitForGpuIdle();
|
||||
ReleaseBackBufferCommandReferences();
|
||||
ReleaseBackBufferViews();
|
||||
|
||||
D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (d3d12SwapChain == nullptr) {
|
||||
m_lastError = "Resize could not resolve the native D3D12 swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
d3d12SwapChain->Resize(
|
||||
static_cast<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(height));
|
||||
const HRESULT resizeHr = d3d12SwapChain->GetLastResizeResult();
|
||||
if (FAILED(resizeHr)) {
|
||||
if (RecreateSwapChain(width, height)) {
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostringstream error = {};
|
||||
error << "ResizeBuffers failed. requested="
|
||||
<< width
|
||||
<< 'x'
|
||||
<< height
|
||||
<< " hr=0x"
|
||||
<< std::uppercase
|
||||
<< std::hex
|
||||
<< static_cast<unsigned long>(resizeHr);
|
||||
m_lastError = error.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
const D3D12Texture* backBufferTexture = GetBackBufferTexture(0u);
|
||||
if (backBufferTexture == nullptr) {
|
||||
if (RecreateSwapChain(width, height)) {
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_lastError = "Resize failed to restore swap chain back buffers after ResizeBuffers.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = static_cast<int>(backBufferTexture->GetWidth());
|
||||
m_height = static_cast<int>(backBufferTexture->GetHeight());
|
||||
m_hostDevice->ResetFrameTracking();
|
||||
if (!RecreateBackBufferViews()) {
|
||||
if (RecreateSwapChain(width, height)) {
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_lastError = "Resize failed to recreate swap chain back buffer views.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_width != width || m_height != height) {
|
||||
if (RecreateSwapChain(width, height)) {
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostringstream error = {};
|
||||
error << "Resize ended with an unexpected swap chain size. requested="
|
||||
<< width
|
||||
<< 'x'
|
||||
<< height
|
||||
<< " actual="
|
||||
<< m_width
|
||||
<< 'x'
|
||||
<< m_height;
|
||||
m_lastError = error.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::PreparePresentSurface(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext) {
|
||||
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
|
||||
m_lastError = "PreparePresentSurface requires a valid render context.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_swapChain == nullptr) {
|
||||
m_lastError = "PreparePresentSurface requires an initialized swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
if (backBufferIndex >= m_backBufferViews.size() ||
|
||||
m_backBufferViews[backBufferIndex] == nullptr) {
|
||||
std::ostringstream error = {};
|
||||
error << "PreparePresentSurface could not find the current swap chain RTV. index="
|
||||
<< backBufferIndex
|
||||
<< " rtvCount="
|
||||
<< m_backBufferViews.size();
|
||||
m_lastError = error.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
m_backBufferViews[backBufferIndex],
|
||||
::XCEngine::RHI::ResourceStates::Present,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::PresentFrame() {
|
||||
if (m_swapChain == nullptr) {
|
||||
m_lastError = "PresentFrame requires an initialized swap chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Editor shell presentation prioritizes interaction latency over display sync.
|
||||
// Blocking on every vblank makes host-window resize feel sticky even when the
|
||||
// UI tree itself is cheap to rebuild.
|
||||
m_swapChain->Present(0, 0);
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string& D3D12WindowSwapChainPresenter::GetLastError() const {
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
RHISwapChain* D3D12WindowSwapChainPresenter::GetSwapChain() const {
|
||||
return m_swapChain;
|
||||
}
|
||||
|
||||
const ::XCEngine::Rendering::RenderSurface*
|
||||
D3D12WindowSwapChainPresenter::GetCurrentRenderSurface() const {
|
||||
if (m_swapChain == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
|
||||
if (backBufferIndex >= m_backBufferSurfaces.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &m_backBufferSurfaces[backBufferIndex];
|
||||
}
|
||||
|
||||
const D3D12Texture* D3D12WindowSwapChainPresenter::GetCurrentBackBufferTexture() const {
|
||||
if (m_swapChain == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return GetBackBufferTexture(m_swapChain->GetCurrentBackBufferIndex());
|
||||
}
|
||||
|
||||
const D3D12Texture* D3D12WindowSwapChainPresenter::GetBackBufferTexture(std::uint32_t index) const {
|
||||
const D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (d3d12SwapChain == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return d3d12SwapChain->TryGetBackBuffer(index);
|
||||
}
|
||||
|
||||
std::uint32_t D3D12WindowSwapChainPresenter::GetBackBufferCount() const {
|
||||
return kSwapChainBufferCount;
|
||||
}
|
||||
|
||||
std::uint32_t D3D12WindowSwapChainPresenter::GetCurrentBackBufferIndex() const {
|
||||
return m_swapChain != nullptr ? m_swapChain->GetCurrentBackBufferIndex() : 0u;
|
||||
}
|
||||
|
||||
D3D12SwapChain* D3D12WindowSwapChainPresenter::GetD3D12SwapChain() const {
|
||||
return m_swapChain != nullptr ? static_cast<D3D12SwapChain*>(m_swapChain) : nullptr;
|
||||
}
|
||||
|
||||
void D3D12WindowSwapChainPresenter::ReleaseBackBufferCommandReferences() {
|
||||
if (m_hostDevice == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::uint32_t frameIndex = 0u;
|
||||
frameIndex < D3D12HostDevice::kFrameContextCount;
|
||||
++frameIndex) {
|
||||
auto* commandList = m_hostDevice->GetCommandList(frameIndex);
|
||||
if (commandList == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
commandList->Reset();
|
||||
commandList->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12WindowSwapChainPresenter::ReleaseBackBufferViews() {
|
||||
for (auto* view : m_backBufferViews) {
|
||||
if (view != nullptr) {
|
||||
view->Shutdown();
|
||||
delete view;
|
||||
}
|
||||
}
|
||||
|
||||
m_backBufferViews.clear();
|
||||
m_backBufferSurfaces.clear();
|
||||
}
|
||||
|
||||
bool D3D12WindowSwapChainPresenter::RecreateBackBufferViews() {
|
||||
D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain();
|
||||
if (m_hostDevice == nullptr || m_hostDevice->GetRHIDevice() == nullptr || d3d12SwapChain == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_backBufferViews.resize(kSwapChainBufferCount, nullptr);
|
||||
m_backBufferSurfaces.resize(kSwapChainBufferCount);
|
||||
|
||||
ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = static_cast<std::uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
viewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||
|
||||
for (std::uint32_t backBufferIndex = 0u;
|
||||
backBufferIndex < kSwapChainBufferCount;
|
||||
++backBufferIndex) {
|
||||
D3D12Texture* backBufferTexture = d3d12SwapChain->TryGetBackBuffer(backBufferIndex);
|
||||
if (backBufferTexture == nullptr) {
|
||||
ReleaseBackBufferViews();
|
||||
m_lastError = "RecreateBackBufferViews could not resolve swap chain back buffer " +
|
||||
std::to_string(backBufferIndex) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_backBufferViews[backBufferIndex] = m_hostDevice->GetRHIDevice()->CreateRenderTargetView(
|
||||
backBufferTexture,
|
||||
viewDesc);
|
||||
if (m_backBufferViews[backBufferIndex] == nullptr) {
|
||||
ReleaseBackBufferViews();
|
||||
m_lastError = "RecreateBackBufferViews failed to create RTV for swap chain back buffer " +
|
||||
std::to_string(backBufferIndex) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex];
|
||||
surface = ::XCEngine::Rendering::RenderSurface(
|
||||
static_cast<std::uint32_t>(m_width),
|
||||
static_cast<std::uint32_t>(m_height));
|
||||
surface.SetColorAttachment(m_backBufferViews[backBufferIndex]);
|
||||
surface.SetAutoTransitionEnabled(false);
|
||||
surface.SetColorStateBefore(ResourceStates::RenderTarget);
|
||||
surface.SetColorStateAfter(ResourceStates::RenderTarget);
|
||||
}
|
||||
|
||||
m_lastError.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
62
new_editor/app/Host/D3D12WindowSwapChainPresenter.h
Normal file
62
new_editor/app/Host/D3D12WindowSwapChainPresenter.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "D3D12HostDevice.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12SwapChain.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHISwapChain.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class D3D12WindowSwapChainPresenter {
|
||||
public:
|
||||
static constexpr std::uint32_t kSwapChainBufferCount = 2u;
|
||||
|
||||
bool Initialize(D3D12HostDevice& hostDevice, HWND hwnd, int width, int height);
|
||||
void Shutdown();
|
||||
bool Resize(int width, int height);
|
||||
bool PreparePresentSurface(const ::XCEngine::Rendering::RenderContext& renderContext);
|
||||
bool PresentFrame();
|
||||
|
||||
const std::string& GetLastError() const;
|
||||
::XCEngine::RHI::RHISwapChain* GetSwapChain() const;
|
||||
const ::XCEngine::Rendering::RenderSurface* GetCurrentRenderSurface() const;
|
||||
const ::XCEngine::RHI::D3D12Texture* GetCurrentBackBufferTexture() const;
|
||||
const ::XCEngine::RHI::D3D12Texture* GetBackBufferTexture(std::uint32_t index) const;
|
||||
std::uint32_t GetBackBufferCount() const;
|
||||
std::uint32_t GetCurrentBackBufferIndex() const;
|
||||
|
||||
private:
|
||||
bool CreateSwapChain(int width, int height);
|
||||
void ConfigureFrameLatency();
|
||||
void DestroySwapChain();
|
||||
bool RecreateSwapChain(int width, int height);
|
||||
::XCEngine::RHI::D3D12SwapChain* GetD3D12SwapChain() const;
|
||||
void ReleaseBackBufferCommandReferences();
|
||||
void ReleaseBackBufferViews();
|
||||
bool RecreateBackBufferViews();
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
D3D12HostDevice* m_hostDevice = nullptr;
|
||||
::XCEngine::RHI::RHISwapChain* m_swapChain = nullptr;
|
||||
std::vector<::XCEngine::RHI::RHIResourceView*> m_backBufferViews = {};
|
||||
std::vector<::XCEngine::Rendering::RenderSurface> m_backBufferSurfaces = {};
|
||||
std::string m_lastError = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
192
new_editor/app/Host/HostRuntimeState.h
Normal file
192
new_editor/app/Host/HostRuntimeState.h
Normal file
@@ -0,0 +1,192 @@
|
||||
#pragma once
|
||||
|
||||
#include "BorderlessWindowFrame.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
struct BorderlessWindowResizeState {
|
||||
bool active = false;
|
||||
BorderlessWindowResizeEdge edge = BorderlessWindowResizeEdge::None;
|
||||
POINT initialScreenPoint = {};
|
||||
RECT initialWindowRect = {};
|
||||
BorderlessWindowResizeEdge hoveredEdge = BorderlessWindowResizeEdge::None;
|
||||
};
|
||||
|
||||
struct PredictedClientPixelSize {
|
||||
bool active = false;
|
||||
UINT width = 0u;
|
||||
UINT height = 0u;
|
||||
};
|
||||
|
||||
struct BorderlessWindowPlacementState {
|
||||
bool maximized = false;
|
||||
bool hasRestoreRect = false;
|
||||
RECT restoreRect = {};
|
||||
};
|
||||
|
||||
struct BorderlessWindowDragRestoreState {
|
||||
bool armed = false;
|
||||
POINT initialScreenPoint = {};
|
||||
};
|
||||
|
||||
class HostRuntimeState {
|
||||
public:
|
||||
void Reset() {
|
||||
m_windowDpi = 96u;
|
||||
m_inInteractiveResize = false;
|
||||
m_borderlessResizeState = {};
|
||||
m_predictedClientPixelSize = {};
|
||||
m_borderlessWindowPlacementState = {};
|
||||
m_borderlessWindowDragRestoreState = {};
|
||||
}
|
||||
|
||||
void SetWindowDpi(UINT dpi) {
|
||||
m_windowDpi = dpi == 0u ? 96u : dpi;
|
||||
}
|
||||
|
||||
UINT GetWindowDpi() const {
|
||||
return m_windowDpi;
|
||||
}
|
||||
|
||||
float GetDpiScale(float baseDpiScale) const {
|
||||
return baseDpiScale > 0.0f
|
||||
? static_cast<float>(m_windowDpi) / baseDpiScale
|
||||
: 1.0f;
|
||||
}
|
||||
|
||||
void BeginInteractiveResize() {
|
||||
m_inInteractiveResize = true;
|
||||
}
|
||||
|
||||
void EndInteractiveResize() {
|
||||
m_inInteractiveResize = false;
|
||||
}
|
||||
|
||||
bool IsInteractiveResize() const {
|
||||
return m_inInteractiveResize;
|
||||
}
|
||||
|
||||
void BeginBorderlessResize(
|
||||
BorderlessWindowResizeEdge edge,
|
||||
const POINT& initialScreenPoint,
|
||||
const RECT& initialWindowRect) {
|
||||
m_borderlessResizeState.active = edge != BorderlessWindowResizeEdge::None;
|
||||
m_borderlessResizeState.edge = edge;
|
||||
m_borderlessResizeState.initialScreenPoint = initialScreenPoint;
|
||||
m_borderlessResizeState.initialWindowRect = initialWindowRect;
|
||||
m_borderlessResizeState.hoveredEdge = edge;
|
||||
m_inInteractiveResize = m_borderlessResizeState.active;
|
||||
}
|
||||
|
||||
void EndBorderlessResize() {
|
||||
m_borderlessResizeState.active = false;
|
||||
m_borderlessResizeState.edge = BorderlessWindowResizeEdge::None;
|
||||
m_inInteractiveResize = false;
|
||||
m_predictedClientPixelSize = {};
|
||||
}
|
||||
|
||||
bool IsBorderlessResizeActive() const {
|
||||
return m_borderlessResizeState.active;
|
||||
}
|
||||
|
||||
BorderlessWindowResizeEdge GetBorderlessResizeEdge() const {
|
||||
return m_borderlessResizeState.edge;
|
||||
}
|
||||
|
||||
const POINT& GetBorderlessResizeInitialScreenPoint() const {
|
||||
return m_borderlessResizeState.initialScreenPoint;
|
||||
}
|
||||
|
||||
const RECT& GetBorderlessResizeInitialWindowRect() const {
|
||||
return m_borderlessResizeState.initialWindowRect;
|
||||
}
|
||||
|
||||
void SetHoveredBorderlessResizeEdge(BorderlessWindowResizeEdge edge) {
|
||||
m_borderlessResizeState.hoveredEdge = edge;
|
||||
}
|
||||
|
||||
BorderlessWindowResizeEdge GetHoveredBorderlessResizeEdge() const {
|
||||
return m_borderlessResizeState.hoveredEdge;
|
||||
}
|
||||
|
||||
void SetPredictedClientPixelSize(UINT width, UINT height) {
|
||||
if (width == 0u || height == 0u) {
|
||||
m_predictedClientPixelSize = {};
|
||||
return;
|
||||
}
|
||||
|
||||
m_predictedClientPixelSize.active = true;
|
||||
m_predictedClientPixelSize.width = width;
|
||||
m_predictedClientPixelSize.height = height;
|
||||
}
|
||||
|
||||
void ClearPredictedClientPixelSize() {
|
||||
m_predictedClientPixelSize = {};
|
||||
}
|
||||
|
||||
bool TryGetPredictedClientPixelSize(UINT& outWidth, UINT& outHeight) const {
|
||||
outWidth = 0u;
|
||||
outHeight = 0u;
|
||||
if (!m_predictedClientPixelSize.active ||
|
||||
m_predictedClientPixelSize.width == 0u ||
|
||||
m_predictedClientPixelSize.height == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outWidth = m_predictedClientPixelSize.width;
|
||||
outHeight = m_predictedClientPixelSize.height;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetBorderlessWindowMaximized(bool maximized) {
|
||||
m_borderlessWindowPlacementState.maximized = maximized;
|
||||
}
|
||||
|
||||
bool IsBorderlessWindowMaximized() const {
|
||||
return m_borderlessWindowPlacementState.maximized;
|
||||
}
|
||||
|
||||
void SetBorderlessWindowRestoreRect(const RECT& rect) {
|
||||
m_borderlessWindowPlacementState.restoreRect = rect;
|
||||
m_borderlessWindowPlacementState.hasRestoreRect = true;
|
||||
}
|
||||
|
||||
bool TryGetBorderlessWindowRestoreRect(RECT& outRect) const {
|
||||
outRect = {};
|
||||
if (!m_borderlessWindowPlacementState.hasRestoreRect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRect = m_borderlessWindowPlacementState.restoreRect;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BeginBorderlessWindowDragRestore(const POINT& initialScreenPoint) {
|
||||
m_borderlessWindowDragRestoreState.armed = true;
|
||||
m_borderlessWindowDragRestoreState.initialScreenPoint = initialScreenPoint;
|
||||
}
|
||||
|
||||
void EndBorderlessWindowDragRestore() {
|
||||
m_borderlessWindowDragRestoreState = {};
|
||||
}
|
||||
|
||||
bool IsBorderlessWindowDragRestoreArmed() const {
|
||||
return m_borderlessWindowDragRestoreState.armed;
|
||||
}
|
||||
|
||||
const POINT& GetBorderlessWindowDragRestoreInitialScreenPoint() const {
|
||||
return m_borderlessWindowDragRestoreState.initialScreenPoint;
|
||||
}
|
||||
|
||||
private:
|
||||
UINT m_windowDpi = 96u;
|
||||
bool m_inInteractiveResize = false;
|
||||
BorderlessWindowResizeState m_borderlessResizeState = {};
|
||||
PredictedClientPixelSize m_predictedClientPixelSize = {};
|
||||
BorderlessWindowPlacementState m_borderlessWindowPlacementState = {};
|
||||
BorderlessWindowDragRestoreState m_borderlessWindowDragRestoreState = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
@@ -185,11 +185,11 @@ bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) {
|
||||
// fallback-only and should not stay alive while D3D11On12 interop is healthy.
|
||||
DiscardRenderTarget();
|
||||
|
||||
if (!m_backBufferInteropTargets.empty()) {
|
||||
if (m_windowInterop.HasBackBufferTargets()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return RebuildBackBufferInteropTargets();
|
||||
return m_windowInterop.RebuildBackBufferTargets();
|
||||
}
|
||||
|
||||
void NativeRenderer::DetachWindowRenderer() {
|
||||
@@ -198,18 +198,7 @@ void NativeRenderer::DetachWindowRenderer() {
|
||||
}
|
||||
|
||||
void NativeRenderer::ReleaseWindowRendererBackBufferTargets() {
|
||||
ClearActiveInteropSourceTextures();
|
||||
if (m_d2dDeviceContext != nullptr) {
|
||||
m_d2dDeviceContext->SetTarget(nullptr);
|
||||
D2D1_TAG firstTag = 0u;
|
||||
D2D1_TAG secondTag = 0u;
|
||||
m_d2dDeviceContext->Flush(&firstTag, &secondTag);
|
||||
}
|
||||
if (m_d3d11DeviceContext != nullptr) {
|
||||
m_d3d11DeviceContext->ClearState();
|
||||
m_d3d11DeviceContext->Flush();
|
||||
}
|
||||
m_backBufferInteropTargets.clear();
|
||||
m_windowInterop.ReleaseBackBufferTargets();
|
||||
}
|
||||
|
||||
bool NativeRenderer::RebuildWindowRendererBackBufferTargets() {
|
||||
@@ -219,14 +208,11 @@ bool NativeRenderer::RebuildWindowRendererBackBufferTargets() {
|
||||
|
||||
DiscardRenderTarget();
|
||||
ReleaseWindowRendererBackBufferTargets();
|
||||
return RebuildBackBufferInteropTargets();
|
||||
return m_windowInterop.RebuildBackBufferTargets();
|
||||
}
|
||||
|
||||
bool NativeRenderer::HasAttachedWindowRenderer() const {
|
||||
return m_windowRenderer != nullptr &&
|
||||
m_d3d11On12Device != nullptr &&
|
||||
m_d2dDeviceContext != nullptr &&
|
||||
!m_backBufferInteropTargets.empty();
|
||||
return m_windowInterop.HasAttachedWindowRenderer();
|
||||
}
|
||||
|
||||
bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
@@ -262,19 +248,29 @@ bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& dr
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_backBufferInteropTargets.empty() &&
|
||||
!RebuildBackBufferInteropTargets()) {
|
||||
if (!m_windowInterop.HasBackBufferTargets() &&
|
||||
!m_windowInterop.RebuildBackBufferTargets()) {
|
||||
if (m_lastRenderError.empty()) {
|
||||
m_lastRenderError = "Window renderer back buffer interop targets are unavailable.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex =
|
||||
m_windowRenderer->GetSwapChain() != nullptr
|
||||
? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex()
|
||||
: 0u;
|
||||
if (backBufferIndex >= m_backBufferInteropTargets.size()) {
|
||||
ID3D11On12Device* d3d11On12Device = m_windowInterop.GetD3D11On12Device();
|
||||
ID3D11DeviceContext* d3d11DeviceContext = m_windowInterop.GetD3D11DeviceContext();
|
||||
ID2D1DeviceContext* d2dDeviceContext = m_windowInterop.GetD2DDeviceContext();
|
||||
ID2D1SolidColorBrush* interopBrush = m_windowInterop.GetInteropBrush();
|
||||
if (d3d11On12Device == nullptr ||
|
||||
d3d11DeviceContext == nullptr ||
|
||||
d2dDeviceContext == nullptr ||
|
||||
interopBrush == nullptr) {
|
||||
m_lastRenderError = "Window renderer interop resources are incomplete.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferIndex = m_windowInterop.GetCurrentBackBufferIndex();
|
||||
if (m_windowInterop.GetWrappedBackBufferResource(backBufferIndex) == nullptr ||
|
||||
m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex) == nullptr) {
|
||||
m_lastRenderError = "Back buffer interop target index is out of range.";
|
||||
return false;
|
||||
}
|
||||
@@ -290,15 +286,15 @@ bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& dr
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PrepareActiveInteropSourceTextures(drawData)) {
|
||||
if (!m_windowInterop.PrepareSourceTextures(drawData)) {
|
||||
ID3D11Resource* backBufferResource =
|
||||
m_backBufferInteropTargets[backBufferIndex].wrappedResource.Get();
|
||||
m_windowInterop.GetWrappedBackBufferResource(backBufferIndex);
|
||||
if (backBufferResource != nullptr) {
|
||||
m_d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u);
|
||||
m_d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u);
|
||||
d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u);
|
||||
d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u);
|
||||
}
|
||||
m_d3d11DeviceContext->Flush();
|
||||
ClearActiveInteropSourceTextures();
|
||||
d3d11DeviceContext->Flush();
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
const bool signaled = m_windowRenderer->SignalFrameCompletion();
|
||||
ReleaseWindowRendererInterop();
|
||||
if (!signaled) {
|
||||
@@ -309,26 +305,26 @@ bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& dr
|
||||
}
|
||||
|
||||
std::vector<ID3D11Resource*> acquiredResources = {};
|
||||
acquiredResources.reserve(1u + m_activeInteropSourceTextures.size());
|
||||
acquiredResources.push_back(m_backBufferInteropTargets[backBufferIndex].wrappedResource.Get());
|
||||
for (const D3D12SourceTextureInteropResource& texture : m_activeInteropSourceTextures) {
|
||||
acquiredResources.push_back(texture.wrappedResource.Get());
|
||||
m_windowInterop.BuildAcquiredResources(backBufferIndex, acquiredResources);
|
||||
if (acquiredResources.empty()) {
|
||||
m_lastRenderError = "No wrapped interop resources were prepared for UI composition.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_d3d11On12Device->AcquireWrappedResources(
|
||||
d3d11On12Device->AcquireWrappedResources(
|
||||
acquiredResources.data(),
|
||||
static_cast<UINT>(acquiredResources.size()));
|
||||
|
||||
m_d2dDeviceContext->SetTarget(m_backBufferInteropTargets[backBufferIndex].targetBitmap.Get());
|
||||
const bool rendered = RenderToTarget(*m_d2dDeviceContext.Get(), *m_interopBrush.Get(), drawData);
|
||||
const HRESULT hr = m_d2dDeviceContext->EndDraw();
|
||||
d2dDeviceContext->SetTarget(m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex));
|
||||
const bool rendered = RenderToTarget(*d2dDeviceContext, *interopBrush, drawData);
|
||||
const HRESULT hr = d2dDeviceContext->EndDraw();
|
||||
|
||||
m_d3d11On12Device->ReleaseWrappedResources(
|
||||
d3d11On12Device->ReleaseWrappedResources(
|
||||
acquiredResources.data(),
|
||||
static_cast<UINT>(acquiredResources.size()));
|
||||
m_d3d11DeviceContext->Flush();
|
||||
m_d2dDeviceContext->SetTarget(nullptr);
|
||||
ClearActiveInteropSourceTextures();
|
||||
d3d11DeviceContext->Flush();
|
||||
d2dDeviceContext->SetTarget(nullptr);
|
||||
m_windowInterop.ClearSourceTextures();
|
||||
|
||||
if (!rendered || FAILED(hr)) {
|
||||
m_lastRenderError = FAILED(hr)
|
||||
@@ -604,252 +600,18 @@ bool NativeRenderer::EnsureWindowRendererInterop() {
|
||||
m_lastRenderError = "EnsureWindowRendererInterop requires initialized D2D and DWrite factories.";
|
||||
return false;
|
||||
}
|
||||
if (m_d3d11On12Device != nullptr &&
|
||||
m_d2dDeviceContext != nullptr &&
|
||||
m_interopBrush != nullptr) {
|
||||
return true;
|
||||
|
||||
const bool attached = m_windowInterop.Attach(*m_windowRenderer, *m_d2dFactory.Get());
|
||||
if (!attached) {
|
||||
m_lastRenderError = m_windowInterop.GetLastError();
|
||||
} else {
|
||||
m_lastRenderError.clear();
|
||||
}
|
||||
|
||||
ReleaseWindowRendererInterop();
|
||||
|
||||
ID3D12Device* d3d12Device = m_windowRenderer->GetDevice();
|
||||
ID3D12CommandQueue* d3d12CommandQueue = m_windowRenderer->GetCommandQueue();
|
||||
if (d3d12Device == nullptr || d3d12CommandQueue == nullptr) {
|
||||
m_lastRenderError = "The attached D3D12 window renderer does not expose a native device/queue.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<D3D_FEATURE_LEVEL, 4> featureLevels = {
|
||||
D3D_FEATURE_LEVEL_12_1,
|
||||
D3D_FEATURE_LEVEL_12_0,
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0
|
||||
};
|
||||
const std::array<IUnknown*, 1> commandQueues = {
|
||||
reinterpret_cast<IUnknown*>(d3d12CommandQueue)
|
||||
};
|
||||
|
||||
UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
#ifdef _DEBUG
|
||||
createFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
|
||||
D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0;
|
||||
HRESULT hr = D3D11On12CreateDevice(
|
||||
d3d12Device,
|
||||
createFlags,
|
||||
featureLevels.data(),
|
||||
static_cast<UINT>(featureLevels.size()),
|
||||
commandQueues.data(),
|
||||
static_cast<UINT>(commandQueues.size()),
|
||||
0u,
|
||||
m_d3d11Device.ReleaseAndGetAddressOf(),
|
||||
m_d3d11DeviceContext.ReleaseAndGetAddressOf(),
|
||||
&actualFeatureLevel);
|
||||
#ifdef _DEBUG
|
||||
if (FAILED(hr)) {
|
||||
createFlags &= ~D3D11_CREATE_DEVICE_DEBUG;
|
||||
hr = D3D11On12CreateDevice(
|
||||
d3d12Device,
|
||||
createFlags,
|
||||
featureLevels.data(),
|
||||
static_cast<UINT>(featureLevels.size()),
|
||||
commandQueues.data(),
|
||||
static_cast<UINT>(commandQueues.size()),
|
||||
0u,
|
||||
m_d3d11Device.ReleaseAndGetAddressOf(),
|
||||
m_d3d11DeviceContext.ReleaseAndGetAddressOf(),
|
||||
&actualFeatureLevel);
|
||||
}
|
||||
#endif
|
||||
if (FAILED(hr) || m_d3d11Device == nullptr || m_d3d11DeviceContext == nullptr) {
|
||||
m_lastRenderError = HrToString("D3D11On12CreateDevice", hr);
|
||||
ReleaseWindowRendererInterop();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d3d11Device.As(&m_d3d11On12Device);
|
||||
if (FAILED(hr) || m_d3d11On12Device == nullptr) {
|
||||
m_lastRenderError = HrToString("ID3D11Device::QueryInterface(ID3D11On12Device)", hr);
|
||||
ReleaseWindowRendererInterop();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
|
||||
hr = m_d3d11Device.As(&dxgiDevice);
|
||||
if (FAILED(hr) || dxgiDevice == nullptr) {
|
||||
m_lastRenderError = HrToString("ID3D11Device::QueryInterface(IDXGIDevice)", hr);
|
||||
ReleaseWindowRendererInterop();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), m_d2dDevice.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_d2dDevice == nullptr) {
|
||||
m_lastRenderError = HrToString("ID2D1Factory1::CreateDevice", hr);
|
||||
ReleaseWindowRendererInterop();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d2dDevice->CreateDeviceContext(
|
||||
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
|
||||
m_d2dDeviceContext.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_d2dDeviceContext == nullptr) {
|
||||
m_lastRenderError = HrToString("ID2D1Device::CreateDeviceContext", hr);
|
||||
ReleaseWindowRendererInterop();
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_d2dDeviceContext->CreateSolidColorBrush(
|
||||
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
m_interopBrush.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_interopBrush == nullptr) {
|
||||
m_lastRenderError = HrToString("ID2D1DeviceContext::CreateSolidColorBrush", hr);
|
||||
ReleaseWindowRendererInterop();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_d2dDeviceContext->SetDpi(kBaseDpi, kBaseDpi);
|
||||
m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
return RebuildBackBufferInteropTargets();
|
||||
return attached;
|
||||
}
|
||||
|
||||
void NativeRenderer::ReleaseWindowRendererInterop() {
|
||||
ReleaseWindowRendererBackBufferTargets();
|
||||
m_interopBrush.Reset();
|
||||
m_d2dDeviceContext.Reset();
|
||||
m_d2dDevice.Reset();
|
||||
m_d3d11On12Device.Reset();
|
||||
m_d3d11DeviceContext.Reset();
|
||||
m_d3d11Device.Reset();
|
||||
}
|
||||
|
||||
bool NativeRenderer::RebuildBackBufferInteropTargets() {
|
||||
m_backBufferInteropTargets.clear();
|
||||
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount();
|
||||
m_backBufferInteropTargets.resize(backBufferCount);
|
||||
for (std::uint32_t index = 0; index < backBufferCount; ++index) {
|
||||
const ::XCEngine::RHI::D3D12Texture* backBufferTexture =
|
||||
m_windowRenderer->GetBackBufferTexture(index);
|
||||
if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) {
|
||||
m_lastRenderError = "Failed to resolve a D3D12 swap chain back buffer.";
|
||||
m_backBufferInteropTargets.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D11_RESOURCE_FLAGS resourceFlags = {};
|
||||
resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
|
||||
backBufferTexture->GetResource(),
|
||||
&resourceFlags,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
D3D12_RESOURCE_STATE_PRESENT,
|
||||
IID_PPV_ARGS(m_backBufferInteropTargets[index].wrappedResource.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr) || m_backBufferInteropTargets[index].wrappedResource == nullptr) {
|
||||
m_lastRenderError = HrToString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr);
|
||||
m_backBufferInteropTargets.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface;
|
||||
hr = m_backBufferInteropTargets[index].wrappedResource.As(&dxgiSurface);
|
||||
if (FAILED(hr) || dxgiSurface == nullptr) {
|
||||
m_lastRenderError = HrToString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
|
||||
m_backBufferInteropTargets.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
|
||||
BuildD2DBitmapProperties(
|
||||
backBufferTexture->GetDesc().Format,
|
||||
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW);
|
||||
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
|
||||
dxgiSurface.Get(),
|
||||
&bitmapProperties,
|
||||
m_backBufferInteropTargets[index].targetBitmap.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || m_backBufferInteropTargets[index].targetBitmap == nullptr) {
|
||||
m_lastRenderError = HrToString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)", hr);
|
||||
m_backBufferInteropTargets.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeRenderer::ClearActiveInteropSourceTextures() {
|
||||
m_activeInteropBitmaps.clear();
|
||||
m_activeInteropSourceTextures.clear();
|
||||
}
|
||||
|
||||
bool NativeRenderer::PrepareActiveInteropSourceTextures(
|
||||
const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
ClearActiveInteropSourceTextures();
|
||||
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<::XCEngine::UI::UITextureHandle> textureHandles = {};
|
||||
CollectInteropTextureHandles(drawData, textureHandles);
|
||||
m_activeInteropSourceTextures.reserve(textureHandles.size());
|
||||
|
||||
for (const ::XCEngine::UI::UITextureHandle& textureHandle : textureHandles) {
|
||||
auto* texture =
|
||||
reinterpret_cast<::XCEngine::RHI::RHITexture*>(textureHandle.resourceHandle);
|
||||
auto* nativeTexture = dynamic_cast<::XCEngine::RHI::D3D12Texture*>(texture);
|
||||
if (nativeTexture == nullptr || nativeTexture->GetResource() == nullptr) {
|
||||
m_lastRenderError = "Failed to resolve a D3D12 source texture for UI composition.";
|
||||
ClearActiveInteropSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D11_RESOURCE_FLAGS resourceFlags = {};
|
||||
resourceFlags.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
D3D12SourceTextureInteropResource resource = {};
|
||||
resource.key = textureHandle.resourceHandle;
|
||||
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
|
||||
nativeTexture->GetResource(),
|
||||
&resourceFlags,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||
IID_PPV_ARGS(resource.wrappedResource.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(hr) || resource.wrappedResource == nullptr) {
|
||||
m_lastRenderError = HrToString("ID3D11On12Device::CreateWrappedResource(source)", hr);
|
||||
ClearActiveInteropSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface;
|
||||
hr = resource.wrappedResource.As(&dxgiSurface);
|
||||
if (FAILED(hr) || dxgiSurface == nullptr) {
|
||||
m_lastRenderError = HrToString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
|
||||
ClearActiveInteropSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
|
||||
BuildD2DBitmapProperties(
|
||||
nativeTexture->GetDesc().Format,
|
||||
D2D1_BITMAP_OPTIONS_NONE);
|
||||
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
|
||||
dxgiSurface.Get(),
|
||||
&bitmapProperties,
|
||||
resource.bitmap.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || resource.bitmap == nullptr) {
|
||||
m_lastRenderError = HrToString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(source)", hr);
|
||||
ClearActiveInteropSourceTextures();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_activeInteropBitmaps.emplace(resource.key, resource.bitmap);
|
||||
m_activeInteropSourceTextures.push_back(std::move(resource));
|
||||
}
|
||||
|
||||
return true;
|
||||
m_windowInterop.Detach();
|
||||
}
|
||||
|
||||
bool NativeRenderer::EnsureRenderTarget() {
|
||||
@@ -1068,18 +830,7 @@ bool NativeRenderer::ResolveTextureBitmap(
|
||||
bool NativeRenderer::ResolveInteropBitmap(
|
||||
const ::XCEngine::UI::UITextureHandle& texture,
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const {
|
||||
outBitmap.Reset();
|
||||
if (!IsInteropTextureHandle(texture)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto found = m_activeInteropBitmaps.find(texture.resourceHandle);
|
||||
if (found == m_activeInteropBitmaps.end() || found->second == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outBitmap = found->second;
|
||||
return true;
|
||||
return m_windowInterop.ResolveInteropBitmap(texture, outBitmap);
|
||||
}
|
||||
|
||||
bool NativeRenderer::RenderToTarget(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "D3D12WindowInteropContext.h"
|
||||
#include "D3D12WindowRenderer.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
|
||||
@@ -65,15 +66,6 @@ private:
|
||||
UINT width = 0u;
|
||||
UINT height = 0u;
|
||||
};
|
||||
struct D3D12BackBufferInteropTarget {
|
||||
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap1> targetBitmap = {};
|
||||
};
|
||||
struct D3D12SourceTextureInteropResource {
|
||||
std::uintptr_t key = 0u;
|
||||
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
|
||||
Microsoft::WRL::ComPtr<ID2D1Bitmap1> bitmap = {};
|
||||
};
|
||||
|
||||
bool EnsureRenderTarget();
|
||||
bool EnsureWindowRendererInterop();
|
||||
@@ -81,9 +73,6 @@ private:
|
||||
void DiscardRenderTarget();
|
||||
bool CreateDeviceResources();
|
||||
void ReleaseWindowRendererInterop();
|
||||
bool RebuildBackBufferInteropTargets();
|
||||
void ClearActiveInteropSourceTextures();
|
||||
bool PrepareActiveInteropSourceTextures(const ::XCEngine::UI::UIDrawData& drawData);
|
||||
void InvalidateCachedTextureBitmaps(const ID2D1RenderTarget* renderTarget);
|
||||
bool RenderToTarget(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
@@ -115,19 +104,11 @@ private:
|
||||
Microsoft::WRL::ComPtr<ID2D1Factory1> m_d2dFactory;
|
||||
Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory;
|
||||
Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory;
|
||||
Microsoft::WRL::ComPtr<ID3D11Device> m_d3d11Device;
|
||||
Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_d3d11DeviceContext;
|
||||
Microsoft::WRL::ComPtr<ID3D11On12Device> m_d3d11On12Device;
|
||||
Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice;
|
||||
Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dDeviceContext;
|
||||
Microsoft::WRL::ComPtr<ID2D1HwndRenderTarget> m_renderTarget;
|
||||
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_solidBrush;
|
||||
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_interopBrush;
|
||||
std::vector<D3D12BackBufferInteropTarget> m_backBufferInteropTargets = {};
|
||||
std::vector<D3D12SourceTextureInteropResource> m_activeInteropSourceTextures = {};
|
||||
std::unordered_map<std::uintptr_t, Microsoft::WRL::ComPtr<ID2D1Bitmap1>> m_activeInteropBitmaps;
|
||||
mutable std::unordered_map<int, Microsoft::WRL::ComPtr<IDWriteTextFormat>> m_textFormats;
|
||||
std::unordered_set<NativeTextureResource*> m_liveTextures;
|
||||
D3D12WindowInteropContext m_windowInterop = {};
|
||||
std::string m_lastRenderError = {};
|
||||
bool m_wicComInitialized = false;
|
||||
float m_dpiScale = 1.0f;
|
||||
|
||||
134
new_editor/app/Host/WindowMessageDispatcher.cpp
Normal file
134
new_editor/app/Host/WindowMessageDispatcher.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include "WindowMessageDispatcher.h"
|
||||
|
||||
#include "../Application.h"
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
void TryEnableNonClientDpiScaling(HWND hwnd) {
|
||||
if (hwnd == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||
if (user32 == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND);
|
||||
const auto enableNonClientDpiScaling =
|
||||
reinterpret_cast<EnableNonClientDpiScalingFn>(
|
||||
GetProcAddress(user32, "EnableNonClientDpiScaling"));
|
||||
if (enableNonClientDpiScaling != nullptr) {
|
||||
enableNonClientDpiScaling(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
Application* WindowMessageDispatcher::GetApplicationFromWindow(HWND hwnd) {
|
||||
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
bool WindowMessageDispatcher::TryHandleNonClientCreate(
|
||||
HWND hwnd,
|
||||
UINT message,
|
||||
LPARAM lParam,
|
||||
LRESULT& outResult) {
|
||||
if (message != WM_NCCREATE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TryEnableNonClientDpiScaling(hwnd);
|
||||
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
||||
auto* application = reinterpret_cast<Application*>(createStruct->lpCreateParams);
|
||||
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(application));
|
||||
outResult = TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowMessageDispatcher::TryDispatch(
|
||||
HWND hwnd,
|
||||
Application& application,
|
||||
UINT message,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT& outResult) {
|
||||
const auto renderAndValidateWindow = [&application, hwnd]() {
|
||||
if (!application.m_renderReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
application.RenderFrame();
|
||||
if (hwnd != nullptr && IsWindow(hwnd)) {
|
||||
ValidateRect(hwnd, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
switch (message) {
|
||||
case WM_GETMINMAXINFO:
|
||||
if (application.IsBorderlessWindowEnabled() &&
|
||||
application.HandleBorderlessWindowGetMinMaxInfo(lParam)) {
|
||||
outResult = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case WM_NCCALCSIZE:
|
||||
if (application.IsBorderlessWindowEnabled()) {
|
||||
outResult = application.HandleBorderlessWindowNcCalcSize(wParam, lParam);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case WM_NCACTIVATE:
|
||||
if (application.IsBorderlessWindowEnabled()) {
|
||||
outResult = TRUE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case WM_SYSCOMMAND:
|
||||
if (application.HandleBorderlessWindowSystemCommand(wParam)) {
|
||||
outResult = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case WM_SETCURSOR:
|
||||
if (LOWORD(lParam) == HTCLIENT && application.ApplyCurrentCursor()) {
|
||||
outResult = TRUE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case WM_DPICHANGED:
|
||||
if (lParam == 0) {
|
||||
return false;
|
||||
}
|
||||
application.OnDpiChanged(
|
||||
static_cast<UINT>(LOWORD(wParam)),
|
||||
*reinterpret_cast<const RECT*>(lParam));
|
||||
renderAndValidateWindow();
|
||||
outResult = 0;
|
||||
return true;
|
||||
case WM_ENTERSIZEMOVE:
|
||||
application.OnEnterSizeMove();
|
||||
outResult = 0;
|
||||
return true;
|
||||
case WM_EXITSIZEMOVE:
|
||||
application.OnExitSizeMove();
|
||||
renderAndValidateWindow();
|
||||
outResult = 0;
|
||||
return true;
|
||||
case WM_SIZE:
|
||||
if (wParam != SIZE_MINIMIZED) {
|
||||
application.OnResize(
|
||||
static_cast<UINT>(LOWORD(lParam)),
|
||||
static_cast<UINT>(HIWORD(lParam)));
|
||||
renderAndValidateWindow();
|
||||
}
|
||||
outResult = 0;
|
||||
return true;
|
||||
case WM_PAINT:
|
||||
application.OnPaintMessage();
|
||||
outResult = 0;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
32
new_editor/app/Host/WindowMessageDispatcher.h
Normal file
32
new_editor/app/Host/WindowMessageDispatcher.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
class Application;
|
||||
}
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class WindowMessageDispatcher {
|
||||
public:
|
||||
static Application* GetApplicationFromWindow(HWND hwnd);
|
||||
static bool TryHandleNonClientCreate(
|
||||
HWND hwnd,
|
||||
UINT message,
|
||||
LPARAM lParam,
|
||||
LRESULT& outResult);
|
||||
static bool TryDispatch(
|
||||
HWND hwnd,
|
||||
Application& application,
|
||||
UINT message,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT& outResult);
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
@@ -39,16 +39,16 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
BuildUIEditorWorkspaceSplit(
|
||||
"workspace-top",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.15f,
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"hierarchy-panel",
|
||||
"hierarchy",
|
||||
"Hierarchy",
|
||||
true),
|
||||
0.7875f,
|
||||
BuildUIEditorWorkspaceSplit(
|
||||
"workspace-main",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.75f,
|
||||
0.19047619f,
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"hierarchy-panel",
|
||||
"hierarchy",
|
||||
"Hierarchy",
|
||||
true),
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"center-tabs",
|
||||
{
|
||||
@@ -63,12 +63,12 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
"Game",
|
||||
false)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"inspector-panel",
|
||||
"inspector",
|
||||
"Inspector",
|
||||
true))),
|
||||
0u)),
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"inspector-panel",
|
||||
"inspector",
|
||||
"Inspector",
|
||||
true)),
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"bottom-tabs",
|
||||
{
|
||||
|
||||
@@ -14,12 +14,18 @@ void ProductViewportHostService::AttachWindowRenderer(
|
||||
Host::D3D12WindowRenderer& windowRenderer) {
|
||||
if (m_windowRenderer == &windowRenderer) {
|
||||
m_device = windowRenderer.GetRHIDevice();
|
||||
if (m_device != nullptr && !m_textureDescriptorAllocator.IsInitialized()) {
|
||||
m_textureDescriptorAllocator.Initialize(*m_device);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
m_windowRenderer = &windowRenderer;
|
||||
m_device = windowRenderer.GetRHIDevice();
|
||||
if (m_device != nullptr) {
|
||||
m_textureDescriptorAllocator.Initialize(*m_device);
|
||||
}
|
||||
}
|
||||
|
||||
void ProductViewportHostService::DetachWindowRenderer() {
|
||||
@@ -35,6 +41,7 @@ void ProductViewportHostService::Shutdown() {
|
||||
DestroyViewportEntry(entry);
|
||||
}
|
||||
|
||||
m_textureDescriptorAllocator.Shutdown();
|
||||
m_windowRenderer = nullptr;
|
||||
m_device = nullptr;
|
||||
m_surfacePresentationEnabled = false;
|
||||
@@ -113,7 +120,7 @@ const ProductViewportHostService::ViewportEntry& ProductViewportHostService::Get
|
||||
}
|
||||
|
||||
void ProductViewportHostService::DestroyViewportEntry(ViewportEntry& entry) {
|
||||
DestroyProductViewportRenderTargets(m_windowRenderer, entry.renderTargets);
|
||||
m_renderTargetManager.DestroyTargets(&m_textureDescriptorAllocator, entry.renderTargets);
|
||||
entry = {};
|
||||
}
|
||||
|
||||
@@ -135,12 +142,12 @@ bool ProductViewportHostService::EnsureViewportResources(ViewportEntry& entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CreateProductViewportRenderTargets(
|
||||
return m_renderTargetManager.EnsureTargets(
|
||||
entry.kind,
|
||||
entry.requestedWidth,
|
||||
entry.requestedHeight,
|
||||
m_device,
|
||||
*m_windowRenderer,
|
||||
*m_device,
|
||||
m_textureDescriptorAllocator,
|
||||
entry.renderTargets);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "ProductViewportRenderTargets.h"
|
||||
|
||||
#include <Host/D3D12WindowRenderer.h>
|
||||
#include <Host/D3D12ShaderResourceDescriptorAllocator.h>
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
@@ -57,6 +58,8 @@ private:
|
||||
|
||||
Host::D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||
Host::D3D12ShaderResourceDescriptorAllocator m_textureDescriptorAllocator = {};
|
||||
ProductViewportRenderTargetManager m_renderTargetManager = {};
|
||||
bool m_surfacePresentationEnabled = false;
|
||||
std::array<ViewportEntry, 2> m_entries = {};
|
||||
};
|
||||
|
||||
267
new_editor/app/Viewport/ProductViewportRenderTargetManager.cpp
Normal file
267
new_editor/app/Viewport/ProductViewportRenderTargetManager.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "ProductViewportRenderTargets.h"
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename ResourceType>
|
||||
void ShutdownAndDeleteViewportResource(ResourceType*& resource) {
|
||||
if (resource == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
resource->Shutdown();
|
||||
delete resource;
|
||||
resource = nullptr;
|
||||
}
|
||||
|
||||
bool CreateViewportColorResources(
|
||||
::XCEngine::RHI::RHIDevice& device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto colorDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.colorTexture = device.CreateTexture(colorDesc);
|
||||
if (targets.colorTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto colorViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.colorView = device.CreateRenderTargetView(targets.colorTexture, colorViewDesc);
|
||||
return targets.colorView != nullptr;
|
||||
}
|
||||
|
||||
bool CreateViewportDepthResources(
|
||||
::XCEngine::RHI::RHIDevice& device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto depthDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.depthTexture = device.CreateTexture(depthDesc);
|
||||
if (targets.depthTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto depthViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.depthView = device.CreateDepthStencilView(targets.depthTexture, depthViewDesc);
|
||||
if (targets.depthView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
::XCEngine::RHI::ResourceViewDesc depthShaderViewDesc = {};
|
||||
depthShaderViewDesc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D;
|
||||
depthShaderViewDesc.mipLevel = 0;
|
||||
targets.depthShaderView = device.CreateShaderResourceView(
|
||||
targets.depthTexture,
|
||||
depthShaderViewDesc);
|
||||
return targets.depthShaderView != nullptr;
|
||||
}
|
||||
|
||||
bool CreateViewportObjectIdResources(
|
||||
::XCEngine::RHI::RHIDevice& device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto objectIdDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.objectIdTexture = device.CreateTexture(objectIdDesc);
|
||||
if (targets.objectIdTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectIdViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.objectIdView = device.CreateRenderTargetView(targets.objectIdTexture, objectIdViewDesc);
|
||||
if (targets.objectIdView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.objectIdShaderView = device.CreateShaderResourceView(
|
||||
targets.objectIdTexture,
|
||||
objectIdViewDesc);
|
||||
if (targets.objectIdShaderView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectIdDepthDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.objectIdDepthTexture = device.CreateTexture(objectIdDepthDesc);
|
||||
if (targets.objectIdDepthTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectIdDepthViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.objectIdDepthView = device.CreateDepthStencilView(
|
||||
targets.objectIdDepthTexture,
|
||||
objectIdDepthViewDesc);
|
||||
return targets.objectIdDepthView != nullptr;
|
||||
}
|
||||
|
||||
bool CreateViewportSelectionMaskResources(
|
||||
::XCEngine::RHI::RHIDevice& device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto selectionMaskDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.selectionMaskTexture = device.CreateTexture(selectionMaskDesc);
|
||||
if (targets.selectionMaskTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto selectionMaskViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.selectionMaskView = device.CreateRenderTargetView(
|
||||
targets.selectionMaskTexture,
|
||||
selectionMaskViewDesc);
|
||||
if (targets.selectionMaskView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.selectionMaskShaderView = device.CreateShaderResourceView(
|
||||
targets.selectionMaskTexture,
|
||||
selectionMaskViewDesc);
|
||||
return targets.selectionMaskShaderView != nullptr;
|
||||
}
|
||||
|
||||
bool CreateViewportTextureDescriptor(
|
||||
Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
if (!textureDescriptorAllocator.CreateTextureDescriptor(
|
||||
targets.colorTexture,
|
||||
&targets.srvCpuHandle,
|
||||
&targets.srvGpuHandle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.textureHandle.nativeHandle = static_cast<std::uintptr_t>(targets.srvGpuHandle.ptr);
|
||||
targets.textureHandle.width = targets.width;
|
||||
targets.textureHandle.height = targets.height;
|
||||
targets.textureHandle.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
|
||||
targets.textureHandle.resourceHandle =
|
||||
reinterpret_cast<std::uintptr_t>(targets.colorTexture);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProductViewportResourceReuseQuery BuildProductViewportRenderTargetsReuseQuery(
|
||||
ProductViewportKind kind,
|
||||
const ProductViewportRenderTargets& targets,
|
||||
std::uint32_t requestedWidth,
|
||||
std::uint32_t requestedHeight) {
|
||||
ProductViewportResourceReuseQuery query = {};
|
||||
query.kind = kind;
|
||||
query.width = targets.width;
|
||||
query.height = targets.height;
|
||||
query.requestedWidth = requestedWidth;
|
||||
query.requestedHeight = requestedHeight;
|
||||
query.resources.hasColorTexture = targets.colorTexture != nullptr;
|
||||
query.resources.hasColorView = targets.colorView != nullptr;
|
||||
query.resources.hasDepthTexture = targets.depthTexture != nullptr;
|
||||
query.resources.hasDepthView = targets.depthView != nullptr;
|
||||
query.resources.hasDepthShaderView = targets.depthShaderView != nullptr;
|
||||
query.resources.hasObjectIdTexture = targets.objectIdTexture != nullptr;
|
||||
query.resources.hasObjectIdDepthTexture = targets.objectIdDepthTexture != nullptr;
|
||||
query.resources.hasObjectIdDepthView = targets.objectIdDepthView != nullptr;
|
||||
query.resources.hasObjectIdView = targets.objectIdView != nullptr;
|
||||
query.resources.hasObjectIdShaderView = targets.objectIdShaderView != nullptr;
|
||||
query.resources.hasSelectionMaskTexture = targets.selectionMaskTexture != nullptr;
|
||||
query.resources.hasSelectionMaskView = targets.selectionMaskView != nullptr;
|
||||
query.resources.hasSelectionMaskShaderView = targets.selectionMaskShaderView != nullptr;
|
||||
query.resources.hasTextureDescriptor = targets.textureHandle.IsValid();
|
||||
return query;
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderSurface BuildProductViewportColorSurface(
|
||||
const ProductViewportRenderTargets& targets) {
|
||||
return BuildProductViewportRenderSurface(
|
||||
targets.width,
|
||||
targets.height,
|
||||
targets.colorView,
|
||||
targets.depthView,
|
||||
targets.colorState);
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderSurface BuildProductViewportObjectIdSurface(
|
||||
const ProductViewportRenderTargets& targets) {
|
||||
return BuildProductViewportRenderSurface(
|
||||
targets.width,
|
||||
targets.height,
|
||||
targets.objectIdView,
|
||||
targets.objectIdDepthView,
|
||||
targets.objectIdState);
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderSurface BuildProductViewportSelectionMaskSurface(
|
||||
const ProductViewportRenderTargets& targets) {
|
||||
return BuildProductViewportRenderSurface(
|
||||
targets.width,
|
||||
targets.height,
|
||||
targets.selectionMaskView,
|
||||
targets.depthView,
|
||||
targets.selectionMaskState);
|
||||
}
|
||||
|
||||
bool ProductViewportRenderTargetManager::EnsureTargets(
|
||||
ProductViewportKind kind,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
::XCEngine::RHI::RHIDevice& device,
|
||||
Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator,
|
||||
ProductViewportRenderTargets& targets) const {
|
||||
if (width == 0u || height == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DestroyTargets(&textureDescriptorAllocator, targets);
|
||||
targets.width = width;
|
||||
targets.height = height;
|
||||
|
||||
if (!CreateViewportColorResources(device, targets) ||
|
||||
!CreateViewportDepthResources(device, targets) ||
|
||||
(ProductViewportRequiresObjectIdResources(kind) &&
|
||||
(!CreateViewportObjectIdResources(device, targets) ||
|
||||
!CreateViewportSelectionMaskResources(device, targets))) ||
|
||||
!CreateViewportTextureDescriptor(textureDescriptorAllocator, targets)) {
|
||||
DestroyTargets(&textureDescriptorAllocator, targets);
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.colorState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.hasValidObjectIdFrame = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProductViewportRenderTargetManager::DestroyTargets(
|
||||
Host::D3D12ShaderResourceDescriptorAllocator* textureDescriptorAllocator,
|
||||
ProductViewportRenderTargets& targets) const {
|
||||
if (textureDescriptorAllocator != nullptr && targets.srvCpuHandle.ptr != 0) {
|
||||
textureDescriptorAllocator->Free(targets.srvCpuHandle, targets.srvGpuHandle);
|
||||
}
|
||||
|
||||
ShutdownAndDeleteViewportResource(targets.objectIdView);
|
||||
ShutdownAndDeleteViewportResource(targets.objectIdShaderView);
|
||||
ShutdownAndDeleteViewportResource(targets.objectIdDepthView);
|
||||
ShutdownAndDeleteViewportResource(targets.objectIdDepthTexture);
|
||||
ShutdownAndDeleteViewportResource(targets.objectIdTexture);
|
||||
ShutdownAndDeleteViewportResource(targets.selectionMaskView);
|
||||
ShutdownAndDeleteViewportResource(targets.selectionMaskShaderView);
|
||||
ShutdownAndDeleteViewportResource(targets.selectionMaskTexture);
|
||||
ShutdownAndDeleteViewportResource(targets.depthShaderView);
|
||||
ShutdownAndDeleteViewportResource(targets.depthView);
|
||||
ShutdownAndDeleteViewportResource(targets.depthTexture);
|
||||
ShutdownAndDeleteViewportResource(targets.colorView);
|
||||
ShutdownAndDeleteViewportResource(targets.colorTexture);
|
||||
|
||||
targets.width = 0;
|
||||
targets.height = 0;
|
||||
targets.srvCpuHandle = {};
|
||||
targets.srvGpuHandle = {};
|
||||
targets.textureHandle = {};
|
||||
targets.colorState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.hasValidObjectIdFrame = false;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "ProductViewportSurfaceUtils.h"
|
||||
|
||||
#include <Host/D3D12WindowRenderer.h>
|
||||
#include <Host/D3D12ShaderResourceDescriptorAllocator.h>
|
||||
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
@@ -36,268 +36,31 @@ struct ProductViewportRenderTargets {
|
||||
bool hasValidObjectIdFrame = false;
|
||||
};
|
||||
|
||||
inline ProductViewportResourceReuseQuery BuildProductViewportRenderTargetsReuseQuery(
|
||||
ProductViewportResourceReuseQuery BuildProductViewportRenderTargetsReuseQuery(
|
||||
ProductViewportKind kind,
|
||||
const ProductViewportRenderTargets& targets,
|
||||
std::uint32_t requestedWidth,
|
||||
std::uint32_t requestedHeight) {
|
||||
ProductViewportResourceReuseQuery query = {};
|
||||
query.kind = kind;
|
||||
query.width = targets.width;
|
||||
query.height = targets.height;
|
||||
query.requestedWidth = requestedWidth;
|
||||
query.requestedHeight = requestedHeight;
|
||||
query.resources.hasColorTexture = targets.colorTexture != nullptr;
|
||||
query.resources.hasColorView = targets.colorView != nullptr;
|
||||
query.resources.hasDepthTexture = targets.depthTexture != nullptr;
|
||||
query.resources.hasDepthView = targets.depthView != nullptr;
|
||||
query.resources.hasDepthShaderView = targets.depthShaderView != nullptr;
|
||||
query.resources.hasObjectIdTexture = targets.objectIdTexture != nullptr;
|
||||
query.resources.hasObjectIdDepthTexture = targets.objectIdDepthTexture != nullptr;
|
||||
query.resources.hasObjectIdDepthView = targets.objectIdDepthView != nullptr;
|
||||
query.resources.hasObjectIdView = targets.objectIdView != nullptr;
|
||||
query.resources.hasObjectIdShaderView = targets.objectIdShaderView != nullptr;
|
||||
query.resources.hasSelectionMaskTexture = targets.selectionMaskTexture != nullptr;
|
||||
query.resources.hasSelectionMaskView = targets.selectionMaskView != nullptr;
|
||||
query.resources.hasSelectionMaskShaderView = targets.selectionMaskShaderView != nullptr;
|
||||
query.resources.hasTextureDescriptor = targets.textureHandle.IsValid();
|
||||
return query;
|
||||
}
|
||||
std::uint32_t requestedHeight);
|
||||
|
||||
inline ::XCEngine::Rendering::RenderSurface BuildProductViewportColorSurface(
|
||||
const ProductViewportRenderTargets& targets) {
|
||||
return BuildProductViewportRenderSurface(
|
||||
targets.width,
|
||||
targets.height,
|
||||
targets.colorView,
|
||||
targets.depthView,
|
||||
targets.colorState);
|
||||
}
|
||||
::XCEngine::Rendering::RenderSurface BuildProductViewportColorSurface(
|
||||
const ProductViewportRenderTargets& targets);
|
||||
::XCEngine::Rendering::RenderSurface BuildProductViewportObjectIdSurface(
|
||||
const ProductViewportRenderTargets& targets);
|
||||
::XCEngine::Rendering::RenderSurface BuildProductViewportSelectionMaskSurface(
|
||||
const ProductViewportRenderTargets& targets);
|
||||
|
||||
inline ::XCEngine::Rendering::RenderSurface BuildProductViewportObjectIdSurface(
|
||||
const ProductViewportRenderTargets& targets) {
|
||||
return BuildProductViewportRenderSurface(
|
||||
targets.width,
|
||||
targets.height,
|
||||
targets.objectIdView,
|
||||
targets.objectIdDepthView,
|
||||
targets.objectIdState);
|
||||
}
|
||||
|
||||
inline ::XCEngine::Rendering::RenderSurface BuildProductViewportSelectionMaskSurface(
|
||||
const ProductViewportRenderTargets& targets) {
|
||||
return BuildProductViewportRenderSurface(
|
||||
targets.width,
|
||||
targets.height,
|
||||
targets.selectionMaskView,
|
||||
targets.depthView,
|
||||
targets.selectionMaskState);
|
||||
}
|
||||
|
||||
namespace Internal {
|
||||
|
||||
template <typename ResourceType>
|
||||
inline void ShutdownAndDeleteViewportResource(ResourceType*& resource) {
|
||||
if (resource == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
resource->Shutdown();
|
||||
delete resource;
|
||||
resource = nullptr;
|
||||
}
|
||||
|
||||
inline bool CreateViewportColorResources(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto colorDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.colorTexture = device->CreateTexture(colorDesc);
|
||||
if (targets.colorTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto colorViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.colorView = device->CreateRenderTargetView(targets.colorTexture, colorViewDesc);
|
||||
return targets.colorView != nullptr;
|
||||
}
|
||||
|
||||
inline bool CreateViewportDepthResources(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto depthDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.depthTexture = device->CreateTexture(depthDesc);
|
||||
if (targets.depthTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto depthViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.depthView = device->CreateDepthStencilView(targets.depthTexture, depthViewDesc);
|
||||
if (targets.depthView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
::XCEngine::RHI::ResourceViewDesc depthShaderViewDesc = {};
|
||||
depthShaderViewDesc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D;
|
||||
depthShaderViewDesc.mipLevel = 0;
|
||||
targets.depthShaderView = device->CreateShaderResourceView(
|
||||
targets.depthTexture,
|
||||
depthShaderViewDesc);
|
||||
return targets.depthShaderView != nullptr;
|
||||
}
|
||||
|
||||
inline bool CreateViewportObjectIdResources(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto objectIdDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.objectIdTexture = device->CreateTexture(objectIdDesc);
|
||||
if (targets.objectIdTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectIdViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.objectIdView = device->CreateRenderTargetView(targets.objectIdTexture, objectIdViewDesc);
|
||||
if (targets.objectIdView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.objectIdShaderView = device->CreateShaderResourceView(
|
||||
targets.objectIdTexture,
|
||||
objectIdViewDesc);
|
||||
if (targets.objectIdShaderView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectIdDepthDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.objectIdDepthTexture = device->CreateTexture(objectIdDepthDesc);
|
||||
if (targets.objectIdDepthTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto objectIdDepthViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt);
|
||||
targets.objectIdDepthView = device->CreateDepthStencilView(
|
||||
targets.objectIdDepthTexture,
|
||||
objectIdDepthViewDesc);
|
||||
return targets.objectIdDepthView != nullptr;
|
||||
}
|
||||
|
||||
inline bool CreateViewportSelectionMaskResources(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
const auto selectionMaskDesc =
|
||||
BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.selectionMaskTexture = device->CreateTexture(selectionMaskDesc);
|
||||
if (targets.selectionMaskTexture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto selectionMaskViewDesc =
|
||||
BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm);
|
||||
targets.selectionMaskView = device->CreateRenderTargetView(
|
||||
targets.selectionMaskTexture,
|
||||
selectionMaskViewDesc);
|
||||
if (targets.selectionMaskView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.selectionMaskShaderView = device->CreateShaderResourceView(
|
||||
targets.selectionMaskTexture,
|
||||
selectionMaskViewDesc);
|
||||
return targets.selectionMaskShaderView != nullptr;
|
||||
}
|
||||
|
||||
inline bool CreateViewportTextureDescriptor(
|
||||
Host::D3D12WindowRenderer& windowRenderer,
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
if (!windowRenderer.CreateShaderResourceTextureDescriptor(
|
||||
device,
|
||||
targets.colorTexture,
|
||||
&targets.srvCpuHandle,
|
||||
&targets.srvGpuHandle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.textureHandle.nativeHandle = static_cast<std::uintptr_t>(targets.srvGpuHandle.ptr);
|
||||
targets.textureHandle.width = targets.width;
|
||||
targets.textureHandle.height = targets.height;
|
||||
targets.textureHandle.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
|
||||
targets.textureHandle.resourceHandle =
|
||||
reinterpret_cast<std::uintptr_t>(targets.colorTexture);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
inline void DestroyProductViewportRenderTargets(
|
||||
Host::D3D12WindowRenderer* windowRenderer,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
if (windowRenderer != nullptr && targets.srvCpuHandle.ptr != 0) {
|
||||
windowRenderer->FreeShaderResourceDescriptor(targets.srvCpuHandle, targets.srvGpuHandle);
|
||||
}
|
||||
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.objectIdView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.objectIdShaderView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.objectIdDepthView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.objectIdDepthTexture);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.objectIdTexture);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.selectionMaskView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.selectionMaskShaderView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.selectionMaskTexture);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.depthShaderView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.depthView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.depthTexture);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.colorView);
|
||||
Internal::ShutdownAndDeleteViewportResource(targets.colorTexture);
|
||||
|
||||
targets.width = 0;
|
||||
targets.height = 0;
|
||||
targets.srvCpuHandle = {};
|
||||
targets.srvGpuHandle = {};
|
||||
targets.textureHandle = {};
|
||||
targets.colorState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.hasValidObjectIdFrame = false;
|
||||
}
|
||||
|
||||
inline bool CreateProductViewportRenderTargets(
|
||||
ProductViewportKind kind,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
Host::D3D12WindowRenderer& windowRenderer,
|
||||
ProductViewportRenderTargets& targets) {
|
||||
if (width == 0u || height == 0u || device == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DestroyProductViewportRenderTargets(&windowRenderer, targets);
|
||||
targets.width = width;
|
||||
targets.height = height;
|
||||
|
||||
if (!Internal::CreateViewportColorResources(device, targets) ||
|
||||
!Internal::CreateViewportDepthResources(device, targets) ||
|
||||
(ProductViewportRequiresObjectIdResources(kind) &&
|
||||
(!Internal::CreateViewportObjectIdResources(device, targets) ||
|
||||
!Internal::CreateViewportSelectionMaskResources(device, targets))) ||
|
||||
!Internal::CreateViewportTextureDescriptor(windowRenderer, device, targets)) {
|
||||
DestroyProductViewportRenderTargets(&windowRenderer, targets);
|
||||
return false;
|
||||
}
|
||||
|
||||
targets.colorState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
targets.hasValidObjectIdFrame = false;
|
||||
return true;
|
||||
}
|
||||
class ProductViewportRenderTargetManager {
|
||||
public:
|
||||
bool EnsureTargets(
|
||||
ProductViewportKind kind,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
::XCEngine::RHI::RHIDevice& device,
|
||||
Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator,
|
||||
ProductViewportRenderTargets& targets) const;
|
||||
void DestroyTargets(
|
||||
Host::D3D12ShaderResourceDescriptorAllocator* textureDescriptorAllocator,
|
||||
ProductViewportRenderTargets& targets) const;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
@@ -15,6 +15,7 @@ using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
using Widgets::UIEditorDockHostHitTargetKind;
|
||||
|
||||
bool IsProductViewportPanel(std::string_view panelId) {
|
||||
return panelId == "scene" || panelId == "game";
|
||||
@@ -154,6 +155,66 @@ std::vector<UIInputEvent> FilterShellInputEventsForHostedContentCapture(
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
bool IsPointerInputEventType(UIInputEventType type) {
|
||||
switch (type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
case UIInputEventType::PointerWheel:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldHostedContentYieldPointerStream(
|
||||
const UIEditorShellInteractionFrame& shellFrame,
|
||||
bool shellInteractiveCaptureActive) {
|
||||
if (shellInteractiveCaptureActive ||
|
||||
shellFrame.result.requestPointerCapture ||
|
||||
shellFrame.result.releasePointerCapture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return shellFrame.result.workspaceResult.dockHostResult.hitTarget.kind ==
|
||||
UIEditorDockHostHitTargetKind::SplitterHandle;
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> FilterHostedContentInputEventsForShellOwnership(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool shellOwnsPointerStream) {
|
||||
if (!shellOwnsPointerStream) {
|
||||
return inputEvents;
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> filteredEvents = {};
|
||||
filteredEvents.reserve(inputEvents.size() + 1u);
|
||||
|
||||
bool strippedPointerInput = false;
|
||||
UIInputEvent lastPointerEvent = {};
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
if (IsPointerInputEventType(event.type)) {
|
||||
strippedPointerInput = true;
|
||||
lastPointerEvent = event;
|
||||
continue;
|
||||
}
|
||||
|
||||
filteredEvents.push_back(event);
|
||||
}
|
||||
|
||||
if (strippedPointerInput) {
|
||||
UIInputEvent leaveEvent = {};
|
||||
leaveEvent.type = UIInputEventType::PointerLeave;
|
||||
leaveEvent.position = lastPointerEvent.position;
|
||||
leaveEvent.modifiers = lastPointerEvent.modifiers;
|
||||
filteredEvents.push_back(leaveEvent);
|
||||
}
|
||||
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ProductEditorWorkspace::Initialize(
|
||||
@@ -198,7 +259,6 @@ void ProductEditorWorkspace::Update(
|
||||
context.BuildShellDefinition(captureText);
|
||||
m_viewportHostService.BeginFrame();
|
||||
definition.workspacePresentations = BuildWorkspacePresentations(definition);
|
||||
const std::vector<UIInputEvent> hostedContentEvents = inputEvents;
|
||||
const std::vector<UIInputEvent> shellEvents =
|
||||
HasHostedContentCapture()
|
||||
? FilterShellInputEventsForHostedContentCapture(inputEvents)
|
||||
@@ -212,6 +272,12 @@ void ProductEditorWorkspace::Update(
|
||||
shellEvents,
|
||||
context.GetShellServices(),
|
||||
metrics);
|
||||
const bool shellOwnsHostedContentPointerStream =
|
||||
ShouldHostedContentYieldPointerStream(m_shellFrame, HasShellInteractiveCapture());
|
||||
const std::vector<UIInputEvent> hostedContentEvents =
|
||||
FilterHostedContentInputEventsForShellOwnership(
|
||||
inputEvents,
|
||||
shellOwnsHostedContentPointerStream);
|
||||
ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService);
|
||||
context.SyncSessionFromWorkspace();
|
||||
context.UpdateStatusFromShellResult(m_shellFrame.result);
|
||||
|
||||
BIN
project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png
Normal file
BIN
project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 1
|
||||
guid: e632e900c73bd6cdc4c51951e1ab02b6
|
||||
folderAsset: false
|
||||
importer: TextureImporter
|
||||
importerVersion: 9
|
||||
BIN
project/Assets/Models/nahida/Avatar_Tex_MetalMap.png
Normal file
BIN
project/Assets/Models/nahida/Avatar_Tex_MetalMap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 1
|
||||
guid: b1928595729c1c6863c89d17b054288b
|
||||
folderAsset: false
|
||||
importer: TextureImporter
|
||||
importerVersion: 9
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user