Compare commits

...

24 Commits

Author SHA1 Message Date
ba91e0f5dd Fix borderless host resize presentation ordering 2026-04-14 01:42:44 +08:00
9064c2f5f2 Add borderless editor host chrome 2026-04-14 01:14:45 +08:00
5797a75619 Extract frame-plan fullscreen stage builder 2026-04-14 01:01:45 +08:00
e83f911aef Add borderless host migration plan 2026-04-14 00:56:23 +08:00
dd2299c8b0 Clarify frame plan compatibility adapters 2026-04-14 00:54:47 +08:00
b8d29e39f6 Migrate scene renderer callers to frame plans 2026-04-14 00:52:43 +08:00
72914b3865 Keep shadow execution state out of scene planner 2026-04-14 00:43:55 +08:00
e6950fa704 Switch scene viewport flow to frame plans 2026-04-14 00:40:11 +08:00
21b0530f7b Separate request and frame-stage execution contracts 2026-04-14 00:30:15 +08:00
c3d443eb85 Reduce host swap chain latency 2026-04-14 00:25:57 +08:00
d705cc839b Reduce redundant resize repaint passes 2026-04-13 23:38:45 +08:00
0d6b8bf7d8 Formalize directional shadow runtime contracts 2026-04-13 23:11:28 +08:00
4362008b39 Refactor new editor host resize pipeline 2026-04-13 23:09:02 +08:00
712f99e723 Refactor rendering frame execution contracts 2026-04-13 22:16:04 +08:00
48daaa1bd0 Fix Nahida toon binding and test assets 2026-04-13 21:09:40 +08:00
e462f7d6f7 Add 3DGS D3D12 composite debug checkpoint 2026-04-13 20:17:13 +08:00
5b89c2bb76 Separate viewport target and descriptor ownership 2026-04-13 19:57:25 +08:00
f3fc34898a Refactor new editor host orchestration 2026-04-13 19:37:10 +08:00
d2140bf5cc Checkpoint current new editor host iteration 2026-04-13 18:52:30 +08:00
a0d5e84516 Render 3DGS debug splats as quads 2026-04-13 13:38:41 +08:00
8ba05216fb Establish 3DGS D3D12 sorted baseline 2026-04-13 13:16:57 +08:00
0cc3d6da46 Fix new editor window resize presentation 2026-04-13 12:20:25 +08:00
adb6fe4659 rendering: improve main light shadow receiver filtering 2026-04-13 03:14:06 +08:00
95edf0435f rendering: stabilize single-map directional shadow fitting 2026-04-13 03:13:30 +08:00
110 changed files with 10881 additions and 2462 deletions

View File

@@ -6,6 +6,9 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_program(XC_DXC_EXECUTABLE NAMES dxc)
if(NOT XC_DXC_EXECUTABLE)
message(FATAL_ERROR "dxc is required to build the 3DGS D3D12 MVS sort shaders.")
endif()
get_filename_component(XCENGINE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
set(XCENGINE_BUILD_DIR "${XCENGINE_ROOT}/build")
@@ -35,16 +38,24 @@ add_executable(xc_3dgs_d3d12_mvs
shaders/PreparedSplatView.hlsli
shaders/PrepareGaussiansCS.hlsl
shaders/BuildSortKeysCS.hlsl
shaders/SortCommon.hlsl
shaders/DeviceRadixSort.hlsl
shaders/DebugPointsVS.hlsl
shaders/DebugPointsPS.hlsl
shaders/CompositeVS.hlsl
shaders/CompositePS.hlsl
)
set_source_files_properties(
shaders/PreparedSplatView.hlsli
shaders/PrepareGaussiansCS.hlsl
shaders/BuildSortKeysCS.hlsl
shaders/SortCommon.hlsl
shaders/DeviceRadixSort.hlsl
shaders/DebugPointsVS.hlsl
shaders/DebugPointsPS.hlsl
shaders/CompositeVS.hlsl
shaders/CompositePS.hlsl
PROPERTIES
HEADER_FILE_ONLY TRUE
)
@@ -93,12 +104,55 @@ add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders"
)
if(XC_DXC_EXECUTABLE)
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E MainCS
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/BuildSortKeysCS.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/BuildSortKeysCS.hlsl"
)
endif()
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E MainCS
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/BuildSortKeysCS.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/BuildSortKeysCS.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E InitDeviceRadixSort
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixInit.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E Upsweep
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixUpsweep.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E BuildGlobalHistogram
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixGlobalHistogram.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E Scan
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixScan.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E Downsweep
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixDownsweep.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
)

View File

@@ -67,12 +67,15 @@ private:
bool InitializePreparePassResources();
bool InitializeSortResources();
bool InitializeDebugDrawResources();
bool InitializeCompositeResources();
void ShutdownGaussianGpuResources();
void ShutdownPreparePassResources();
void ShutdownSortResources();
void ShutdownDebugDrawResources();
void ShutdownCompositeResources();
void Shutdown();
bool CaptureSortKeySnapshot();
bool CaptureSortSnapshot();
bool CapturePass3HistogramDebug();
void RenderFrame(bool captureScreenshot);
HWND m_hwnd = nullptr;
@@ -107,18 +110,42 @@ private:
XCEngine::RHI::RHIDescriptorPool* m_prepareDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_prepareDescriptorSet = nullptr;
XCEngine::RHI::D3D12Buffer* m_sortKeyBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_sortKeyScratchBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_orderBuffer = nullptr;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeySrv;
XCEngine::RHI::D3D12Buffer* m_orderScratchBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_passHistogramBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_globalHistogramBuffer = nullptr;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeyUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeyScratchUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderBufferSrv;
XCEngine::RHI::RHIPipelineLayout* m_sortPipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_sortPipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_sortDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_sortDescriptorSet = nullptr;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderBufferUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderScratchUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_passHistogramUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_globalHistogramUav;
XCEngine::RHI::RHIPipelineLayout* m_buildSortKeyPipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_buildSortKeyPipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_buildSortKeyDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_buildSortKeyDescriptorSet = nullptr;
XCEngine::RHI::RHIPipelineLayout* m_radixSortPipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortInitPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortUpsweepPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortGlobalHistogramPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortScanPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortDownsweepPipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_radixSortDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_radixSortDescriptorSetPrimaryToScratch = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_radixSortDescriptorSetScratchToPrimary = nullptr;
XCEngine::RHI::RHIPipelineLayout* m_debugPipelineLayout = nullptr;
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;

View 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);
}

View 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;
}

View File

@@ -1,4 +1,19 @@
float4 MainPS(float4 position : SV_Position, float4 color : COLOR0) : SV_Target0
struct PixelInput
{
return color;
float4 position : SV_Position;
float4 color : COLOR0;
float2 localPosition : TEXCOORD0;
};
float4 MainPS(PixelInput input) : SV_Target0
{
float alpha = exp(-dot(input.localPosition, input.localPosition));
alpha = saturate(alpha * input.color.a);
if (alpha < (1.0 / 255.0))
{
discard;
}
return float4(input.color.rgb * alpha, alpha);
}

View File

@@ -17,23 +17,32 @@ struct VertexOutput
{
float4 position : SV_Position;
float4 color : COLOR0;
float2 localPosition : TEXCOORD0;
};
VertexOutput MainVS(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
VertexOutput output;
VertexOutput output = (VertexOutput)0;
uint splatIndex = gOrderBuffer[instanceId];
PreparedSplatView view = gPreparedViews[splatIndex];
float4 color = UnpackPreparedColor(view);
if (view.clipPosition.w <= 0.0)
{
output.position = float4(2.0, 2.0, 2.0, 1.0);
output.color = 0.0;
const float nanValue = asfloat(0x7fc00000);
output.position = float4(nanValue, nanValue, nanValue, nanValue);
return output;
}
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 2.0 - 1.0;
quadPosition *= 2.0;
float2 deltaScreenPosition =
(quadPosition.x * view.axis1 + quadPosition.y * view.axis2) * 2.0 / gScreenParams.xy;
output.position = view.clipPosition;
output.color = float4(color.rgb, 1.0);
output.position.xy += deltaScreenPosition * view.clipPosition.w;
output.color = color;
output.localPosition = quadPosition;
return output;
}

View File

@@ -0,0 +1,477 @@
/******************************************************************************
* DeviceRadixSort
* Device Level 8-bit LSD Radix Sort using reduce then scan
*
* SPDX-License-Identifier: MIT
* Copyright Thomas Smith 5/17/2024
* https://github.com/b0nes164/GPUSorting
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
#include "SortCommon.hlsl"
#define US_DIM 128U //The number of threads in a Upsweep threadblock
#define SCAN_DIM 128U //The number of threads in a Scan threadblock
RWStructuredBuffer<uint> b_globalHist : register(u5); //buffer holding device level offsets for each binning pass
RWStructuredBuffer<uint> b_passHist : register(u4); //buffer used to store reduced sums of partition tiles
groupshared uint g_us[RADIX * 2]; //Shared memory for upsweep
groupshared uint g_scan[SCAN_DIM]; //Shared memory for the scan
//*****************************************************************************
//INIT KERNEL
//*****************************************************************************
//Clear the global histogram, as we will be adding to it atomically
[numthreads(1024, 1, 1)]
void InitDeviceRadixSort(int3 id : SV_DispatchThreadID)
{
b_globalHist[id.x] = 0;
}
//*****************************************************************************
//UPSWEEP KERNEL
//*****************************************************************************
//histogram, 64 threads to a histogram
inline void HistogramDigitCounts(uint gtid, uint gid)
{
const uint histOffset = gtid / 64 * RADIX;
const uint partitionEnd = gid == e_threadBlocks - 1 ?
e_numKeys : (gid + 1) * PART_SIZE;
for (uint i = gtid + gid * PART_SIZE; i < partitionEnd; i += US_DIM)
{
#if defined(KEY_UINT)
InterlockedAdd(g_us[ExtractDigit(b_sort[i]) + histOffset], 1);
#elif defined(KEY_INT)
InterlockedAdd(g_us[ExtractDigit(IntToUint(b_sort[i])) + histOffset], 1);
#elif defined(KEY_FLOAT)
InterlockedAdd(g_us[ExtractDigit(FloatToUint(b_sort[i])) + histOffset], 1);
#endif
}
}
//reduce and pass to tile histogram
inline void ReduceWriteDigitCounts(uint gtid, uint gid)
{
for (uint i = gtid; i < RADIX; i += US_DIM)
{
g_us[i] += g_us[i + RADIX];
b_passHist[i * e_threadBlocks + gid] = g_us[i];
}
}
//Build the per-pass 256-bin exclusive prefix from the reduced pass histogram.
inline void BuildGlobalHistogramExclusive(uint gtid)
{
uint digitIndices[2];
uint digitTotals[2];
uint digitCount = 0;
for (uint i = gtid; i < RADIX; i += US_DIM)
{
uint total = 0u;
const uint baseOffset = i * e_threadBlocks;
for (uint blockIndex = 0; blockIndex < e_threadBlocks; ++blockIndex)
{
total += b_passHist[baseOffset + blockIndex];
}
g_us[i] = total;
digitIndices[digitCount] = i;
digitTotals[digitCount] = total;
++digitCount;
}
GroupMemoryBarrierWithGroupSync();
for (uint offset = 1; offset < RADIX; offset <<= 1)
{
for (uint i = gtid; i < RADIX; i += US_DIM)
{
g_us[i + RADIX] = g_us[i] + (i >= offset ? g_us[i - offset] : 0u);
}
GroupMemoryBarrierWithGroupSync();
for (uint i = gtid; i < RADIX; i += US_DIM)
{
g_us[i] = g_us[i + RADIX];
}
GroupMemoryBarrierWithGroupSync();
}
const uint globalHistOffset = GlobalHistOffset();
for (uint localIndex = 0; localIndex < digitCount; ++localIndex)
{
const uint digitIndex = digitIndices[localIndex];
b_globalHist[digitIndex + globalHistOffset] = g_us[digitIndex] - digitTotals[localIndex];
}
}
[numthreads(US_DIM, 1, 1)]
void Upsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
{
//get the wave size
const uint waveSize = getWaveSize();
//clear shared memory
const uint histsEnd = RADIX * 2;
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
g_us[i] = 0;
GroupMemoryBarrierWithGroupSync();
HistogramDigitCounts(gtid.x, gid.x);
GroupMemoryBarrierWithGroupSync();
ReduceWriteDigitCounts(gtid.x, gid.x);
}
[numthreads(US_DIM, 1, 1)]
void BuildGlobalHistogram(uint3 gtid : SV_GroupThreadID)
{
const uint histsEnd = RADIX * 2;
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
g_us[i] = 0;
GroupMemoryBarrierWithGroupSync();
BuildGlobalHistogramExclusive(gtid.x);
}
//*****************************************************************************
//SCAN KERNEL
//*****************************************************************************
inline void ExclusiveThreadBlockScanFullWGE16(
uint gtid,
uint laneMask,
uint circularLaneShift,
uint partEnd,
uint deviceOffset,
uint waveSize,
inout uint reduction)
{
for (uint i = gtid; i < partEnd; i += SCAN_DIM)
{
g_scan[gtid] = b_passHist[i + deviceOffset];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
GroupMemoryBarrierWithGroupSync();
if (gtid < SCAN_DIM / waveSize)
{
g_scan[(gtid + 1) * waveSize - 1] +=
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
}
GroupMemoryBarrierWithGroupSync();
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
if (gtid >= waveSize)
t += WaveReadLaneAt(g_scan[gtid - 1], 0);
b_passHist[circularLaneShift + (i & ~laneMask) + deviceOffset] = t;
reduction += g_scan[SCAN_DIM - 1];
GroupMemoryBarrierWithGroupSync();
}
}
inline void ExclusiveThreadBlockScanPartialWGE16(
uint gtid,
uint laneMask,
uint circularLaneShift,
uint partEnd,
uint deviceOffset,
uint waveSize,
uint reduction)
{
uint i = gtid + partEnd;
if (i < e_threadBlocks)
g_scan[gtid] = b_passHist[deviceOffset + i];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
GroupMemoryBarrierWithGroupSync();
if (gtid < SCAN_DIM / waveSize)
{
g_scan[(gtid + 1) * waveSize - 1] +=
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
}
GroupMemoryBarrierWithGroupSync();
const uint index = circularLaneShift + (i & ~laneMask);
if (index < e_threadBlocks)
{
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
if (gtid >= waveSize)
t += g_scan[(gtid & ~laneMask) - 1];
b_passHist[index + deviceOffset] = t;
}
}
inline void ExclusiveThreadBlockScanWGE16(uint gtid, uint gid, uint waveSize)
{
uint reduction = 0;
const uint laneMask = waveSize - 1;
const uint circularLaneShift = WaveGetLaneIndex() + 1 & laneMask;
const uint partionsEnd = e_threadBlocks / SCAN_DIM * SCAN_DIM;
const uint deviceOffset = gid * e_threadBlocks;
ExclusiveThreadBlockScanFullWGE16(
gtid,
laneMask,
circularLaneShift,
partionsEnd,
deviceOffset,
waveSize,
reduction);
ExclusiveThreadBlockScanPartialWGE16(
gtid,
laneMask,
circularLaneShift,
partionsEnd,
deviceOffset,
waveSize,
reduction);
}
inline void ExclusiveThreadBlockScanFullWLT16(
uint gtid,
uint partitions,
uint deviceOffset,
uint laneLog,
uint circularLaneShift,
uint waveSize,
inout uint reduction)
{
for (uint k = 0; k < partitions; ++k)
{
g_scan[gtid] = b_passHist[gtid + k * SCAN_DIM + deviceOffset];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
GroupMemoryBarrierWithGroupSync();
if (gtid < waveSize)
{
b_passHist[circularLaneShift + k * SCAN_DIM + deviceOffset] =
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
}
uint offset = laneLog;
uint j = waveSize;
for (; j < (SCAN_DIM >> 1); j <<= laneLog)
{
if (gtid < (SCAN_DIM >> offset))
{
g_scan[((gtid + 1) << offset) - 1] +=
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
}
GroupMemoryBarrierWithGroupSync();
if ((gtid & ((j << laneLog) - 1)) >= j)
{
if (gtid < (j << laneLog))
{
b_passHist[gtid + k * SCAN_DIM + deviceOffset] =
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
}
else
{
if ((gtid + 1) & (j - 1))
{
g_scan[gtid] +=
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
}
}
}
offset += laneLog;
}
GroupMemoryBarrierWithGroupSync();
//If SCAN_DIM is not a power of lanecount
for (uint i = gtid + j; i < SCAN_DIM; i += SCAN_DIM)
{
b_passHist[i + k * SCAN_DIM + deviceOffset] =
WaveReadLaneAt(g_scan[((i >> offset) << offset) - 1], 0) +
((i & (j - 1)) ? g_scan[i - 1] : 0) + reduction;
}
reduction += WaveReadLaneAt(g_scan[SCAN_DIM - 1], 0) +
WaveReadLaneAt(g_scan[(((SCAN_DIM - 1) >> offset) << offset) - 1], 0);
GroupMemoryBarrierWithGroupSync();
}
}
inline void ExclusiveThreadBlockScanParitalWLT16(
uint gtid,
uint partitions,
uint deviceOffset,
uint laneLog,
uint circularLaneShift,
uint waveSize,
uint reduction)
{
const uint finalPartSize = e_threadBlocks - partitions * SCAN_DIM;
if (gtid < finalPartSize)
{
g_scan[gtid] = b_passHist[gtid + partitions * SCAN_DIM + deviceOffset];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
}
GroupMemoryBarrierWithGroupSync();
if (gtid < waveSize && circularLaneShift < finalPartSize)
{
b_passHist[circularLaneShift + partitions * SCAN_DIM + deviceOffset] =
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
}
uint offset = laneLog;
for (uint j = waveSize; j < finalPartSize; j <<= laneLog)
{
if (gtid < (finalPartSize >> offset))
{
g_scan[((gtid + 1) << offset) - 1] +=
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
}
GroupMemoryBarrierWithGroupSync();
if ((gtid & ((j << laneLog) - 1)) >= j && gtid < finalPartSize)
{
if (gtid < (j << laneLog))
{
b_passHist[gtid + partitions * SCAN_DIM + deviceOffset] =
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
}
else
{
if ((gtid + 1) & (j - 1))
{
g_scan[gtid] +=
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
}
}
}
offset += laneLog;
}
}
inline void ExclusiveThreadBlockScanWLT16(uint gtid, uint gid, uint waveSize)
{
uint reduction = 0;
const uint partitions = e_threadBlocks / SCAN_DIM;
const uint deviceOffset = gid * e_threadBlocks;
const uint laneLog = countbits(waveSize - 1);
const uint circularLaneShift = WaveGetLaneIndex() + 1 & waveSize - 1;
ExclusiveThreadBlockScanFullWLT16(
gtid,
partitions,
deviceOffset,
laneLog,
circularLaneShift,
waveSize,
reduction);
ExclusiveThreadBlockScanParitalWLT16(
gtid,
partitions,
deviceOffset,
laneLog,
circularLaneShift,
waveSize,
reduction);
}
//Scan does not need flattening of gids
[numthreads(SCAN_DIM, 1, 1)]
void Scan(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
{
if (gtid.x != 0u)
{
return;
}
const uint deviceOffset = gid.x * e_threadBlocks;
uint runningOffset = 0u;
for (uint blockIndex = 0u; blockIndex < e_threadBlocks; ++blockIndex)
{
const uint index = deviceOffset + blockIndex;
const uint count = b_passHist[index];
b_passHist[index] = runningOffset;
runningOffset += count;
}
}
//*****************************************************************************
//DOWNSWEEP KERNEL
//*****************************************************************************
inline void LoadThreadBlockReductions(uint gtid, uint gid, uint exclusiveHistReduction)
{
if (gtid < RADIX)
{
g_d[gtid + PART_SIZE] = b_globalHist[gtid + GlobalHistOffset()] +
b_passHist[gtid * e_threadBlocks + gid] - exclusiveHistReduction;
}
}
[numthreads(D_DIM, 1, 1)]
void Downsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
{
if (gtid.x != 0u)
{
return;
}
const uint partitionStart = gid.x * PART_SIZE;
const uint partitionEnd = min(partitionStart + PART_SIZE, e_numKeys);
uint digitOffsets[RADIX];
const uint globalHistOffset = GlobalHistOffset();
for (uint digit = 0u; digit < RADIX; ++digit)
{
digitOffsets[digit] =
b_globalHist[globalHistOffset + digit] +
b_passHist[digit * e_threadBlocks + gid.x];
}
for (uint index = partitionStart; index < partitionEnd; ++index)
{
uint key;
#if defined(KEY_UINT)
key = b_sort[index];
#elif defined(KEY_INT)
key = IntToUint(b_sort[index]);
#elif defined(KEY_FLOAT)
key = FloatToUint(b_sort[index]);
#endif
const uint digit = ExtractDigit(key);
const uint destinationIndex = digitOffsets[digit]++;
#if defined(KEY_UINT)
b_alt[destinationIndex] = key;
#elif defined(KEY_INT)
b_alt[destinationIndex] = UintToInt(key);
#elif defined(KEY_FLOAT)
b_alt[destinationIndex] = UintToFloat(key);
#endif
#if defined(SORT_PAIRS)
#if defined(PAYLOAD_UINT)
b_altPayload[destinationIndex] = b_sortPayload[index];
#elif defined(PAYLOAD_INT)
b_altPayload[destinationIndex] = b_sortPayload[index];
#elif defined(PAYLOAD_FLOAT)
b_altPayload[destinationIndex] = b_sortPayload[index];
#endif
#endif
}
}

View File

@@ -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);

View File

@@ -0,0 +1,959 @@
/******************************************************************************
* SortCommon
* Common functions for GPUSorting
*
* SPDX-License-Identifier: MIT
* Copyright Thomas Smith 5/17/2024
* https://github.com/b0nes164/GPUSorting
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
#define KEYS_PER_THREAD 15U
#define D_DIM 256U
#define PART_SIZE 3840U
#define D_TOTAL_SMEM 4096U
#define RADIX 256U //Number of digit bins
#define RADIX_MASK 255U //Mask of digit bins
#define HALF_RADIX 128U //For smaller waves where bit packing is necessary
#define HALF_MASK 127U // ''
#define RADIX_LOG 8U //log2(RADIX)
#define RADIX_PASSES 4U //(Key width) / RADIX_LOG
cbuffer cbGpuSorting : register(b0)
{
uint e_numKeys;
uint e_radixShift;
uint e_threadBlocks;
uint padding;
};
#if defined(KEY_UINT)
RWStructuredBuffer<uint> b_sort : register(u0);
RWStructuredBuffer<uint> b_alt : register(u1);
#elif defined(KEY_INT)
RWStructuredBuffer<int> b_sort : register(u0);
RWStructuredBuffer<int> b_alt : register(u1);
#elif defined(KEY_FLOAT)
RWStructuredBuffer<float> b_sort : register(u0);
RWStructuredBuffer<float> b_alt : register(u1);
#endif
#if defined(PAYLOAD_UINT)
RWStructuredBuffer<uint> b_sortPayload : register(u2);
RWStructuredBuffer<uint> b_altPayload : register(u3);
#elif defined(PAYLOAD_INT)
RWStructuredBuffer<int> b_sortPayload : register(u2);
RWStructuredBuffer<int> b_altPayload : register(u3);
#elif defined(PAYLOAD_FLOAT)
RWStructuredBuffer<float> b_sortPayload : register(u2);
RWStructuredBuffer<float> b_altPayload : register(u3);
#endif
groupshared uint g_d[D_TOTAL_SMEM]; //Shared memory for DigitBinningPass and DownSweep kernels
struct KeyStruct
{
uint k[KEYS_PER_THREAD];
};
struct OffsetStruct
{
#if defined(ENABLE_16_BIT)
uint16_t o[KEYS_PER_THREAD];
#else
uint o[KEYS_PER_THREAD];
#endif
};
struct DigitStruct
{
#if defined(ENABLE_16_BIT)
uint16_t d[KEYS_PER_THREAD];
#else
uint d[KEYS_PER_THREAD];
#endif
};
//*****************************************************************************
//HELPER FUNCTIONS
//*****************************************************************************
//Due to a bug with SPIRV pre 1.6, we cannot use WaveGetLaneCount() to get the currently active wavesize
inline uint getWaveSize()
{
#if defined(VULKAN)
GroupMemoryBarrierWithGroupSync(); //Make absolutely sure the wave is not diverged here
return dot(countbits(WaveActiveBallot(true)), uint4(1, 1, 1, 1));
#else
return WaveGetLaneCount();
#endif
}
inline uint getWaveIndex(uint gtid, uint waveSize)
{
return gtid / waveSize;
}
//Radix Tricks by Michael Herf
//http://stereopsis.com/radix.html
inline uint FloatToUint(float f)
{
uint mask = -((int) (asuint(f) >> 31)) | 0x80000000;
return asuint(f) ^ mask;
}
inline float UintToFloat(uint u)
{
uint mask = ((u >> 31) - 1) | 0x80000000;
return asfloat(u ^ mask);
}
inline uint IntToUint(int i)
{
return asuint(i ^ 0x80000000);
}
inline int UintToInt(uint u)
{
return asint(u ^ 0x80000000);
}
inline uint getWaveCountPass(uint waveSize)
{
return D_DIM / waveSize;
}
inline uint ExtractDigit(uint key)
{
return key >> e_radixShift & RADIX_MASK;
}
inline uint ExtractDigit(uint key, uint shift)
{
return key >> shift & RADIX_MASK;
}
inline uint ExtractPackedIndex(uint key)
{
return key >> (e_radixShift + 1) & HALF_MASK;
}
inline uint ExtractPackedShift(uint key)
{
return (key >> e_radixShift & 1) ? 16 : 0;
}
inline uint ExtractPackedValue(uint packed, uint key)
{
return packed >> ExtractPackedShift(key) & 0xffff;
}
inline uint SubPartSizeWGE16(uint waveSize)
{
return KEYS_PER_THREAD * waveSize;
}
inline uint SharedOffsetWGE16(uint gtid, uint waveSize)
{
return WaveGetLaneIndex() + getWaveIndex(gtid, waveSize) * SubPartSizeWGE16(waveSize);
}
inline uint SubPartSizeWLT16(uint waveSize, uint _serialIterations)
{
return KEYS_PER_THREAD * waveSize * _serialIterations;
}
inline uint SharedOffsetWLT16(uint gtid, uint waveSize, uint _serialIterations)
{
return WaveGetLaneIndex() +
(getWaveIndex(gtid, waveSize) / _serialIterations * SubPartSizeWLT16(waveSize, _serialIterations)) +
(getWaveIndex(gtid, waveSize) % _serialIterations * waveSize);
}
inline uint DeviceOffsetWGE16(uint gtid, uint waveSize, uint partIndex)
{
return SharedOffsetWGE16(gtid, waveSize) + partIndex * PART_SIZE;
}
inline uint DeviceOffsetWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
{
return SharedOffsetWLT16(gtid, waveSize, serialIterations) + partIndex * PART_SIZE;
}
inline uint GlobalHistOffset()
{
return e_radixShift << 5;
}
inline uint WaveHistsSizeWGE16(uint waveSize)
{
return D_DIM / waveSize * RADIX;
}
inline uint WaveHistsSizeWLT16()
{
return D_TOTAL_SMEM;
}
//*****************************************************************************
//FUNCTIONS COMMON TO THE DOWNSWEEP / DIGIT BINNING PASS
//*****************************************************************************
//If the size of a wave is too small, we do not have enough space in
//shared memory to assign a histogram to each wave, so instead,
//some operations are peformed serially.
inline uint SerialIterations(uint waveSize)
{
return (D_DIM / waveSize + 31) >> 5;
}
inline void ClearWaveHists(uint gtid, uint waveSize)
{
const uint histsEnd = waveSize >= 16 ?
WaveHistsSizeWGE16(waveSize) : WaveHistsSizeWLT16();
for (uint i = gtid; i < histsEnd; i += D_DIM)
g_d[i] = 0;
}
inline void LoadKey(inout uint key, uint index)
{
#if defined(KEY_UINT)
key = b_sort[index];
#elif defined(KEY_INT)
key = UintToInt(b_sort[index]);
#elif defined(KEY_FLOAT)
key = FloatToUint(b_sort[index]);
#endif
}
inline void LoadDummyKey(inout uint key)
{
key = 0xffffffff;
}
inline KeyStruct LoadKeysWGE16(uint gtid, uint waveSize, uint partIndex)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
LoadKey(keys.k[i], t);
}
return keys;
}
inline KeyStruct LoadKeysWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
LoadKey(keys.k[i], t);
}
return keys;
}
inline KeyStruct LoadKeysPartialWGE16(uint gtid, uint waveSize, uint partIndex)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
if (t < e_numKeys)
LoadKey(keys.k[i], t);
else
LoadDummyKey(keys.k[i]);
}
return keys;
}
inline KeyStruct LoadKeysPartialWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
if (t < e_numKeys)
LoadKey(keys.k[i], t);
else
LoadDummyKey(keys.k[i]);
}
return keys;
}
inline uint WaveFlagsWGE16(uint waveSize)
{
return (waveSize & 31) ? (1U << waveSize) - 1 : 0xffffffff;
}
inline uint WaveFlagsWLT16(uint waveSize)
{
return (1U << waveSize) - 1;;
}
inline void WarpLevelMultiSplitWGE16(uint key, inout uint4 waveFlags)
{
[unroll]
for (uint k = 0; k < RADIX_LOG; ++k)
{
const uint currentBit = 1U << (k + e_radixShift);
const bool t = (key & currentBit) != 0;
GroupMemoryBarrierWithGroupSync(); //Play on the safe side, throw in a barrier for convergence
const uint4 ballot = WaveActiveBallot(t);
if(t)
waveFlags &= ballot;
else
waveFlags &= (~ballot);
}
}
inline uint2 CountBitsWGE16(uint waveSize, uint ltMask, uint4 waveFlags)
{
uint2 count = uint2(0, 0);
for(uint wavePart = 0; wavePart < waveSize; wavePart += 32)
{
uint t = countbits(waveFlags[wavePart >> 5]);
if (WaveGetLaneIndex() >= wavePart)
{
if (WaveGetLaneIndex() >= wavePart + 32)
count.x += t;
else
count.x += countbits(waveFlags[wavePart >> 5] & ltMask);
}
count.y += t;
}
return count;
}
inline void WarpLevelMultiSplitWLT16(uint key, inout uint waveFlags)
{
[unroll]
for (uint k = 0; k < RADIX_LOG; ++k)
{
const bool t = key >> (k + e_radixShift) & 1;
waveFlags &= (t ? 0 : 0xffffffff) ^ (uint) WaveActiveBallot(t);
}
}
inline OffsetStruct RankKeysWGE16(
uint waveSize,
uint waveOffset,
KeyStruct keys)
{
OffsetStruct offsets;
const uint initialFlags = WaveFlagsWGE16(waveSize);
const uint ltMask = (1U << (WaveGetLaneIndex() & 31)) - 1;
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
uint4 waveFlags = initialFlags;
WarpLevelMultiSplitWGE16(keys.k[i], waveFlags);
const uint index = ExtractDigit(keys.k[i]) + waveOffset;
const uint2 bitCount = CountBitsWGE16(waveSize, ltMask, waveFlags);
offsets.o[i] = g_d[index] + bitCount.x;
GroupMemoryBarrierWithGroupSync();
if (bitCount.x == 0)
g_d[index] += bitCount.y;
GroupMemoryBarrierWithGroupSync();
}
return offsets;
}
inline OffsetStruct RankKeysWLT16(uint waveSize, uint waveIndex, KeyStruct keys, uint serialIterations)
{
OffsetStruct offsets;
const uint ltMask = (1U << WaveGetLaneIndex()) - 1;
const uint initialFlags = WaveFlagsWLT16(waveSize);
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
uint waveFlags = initialFlags;
WarpLevelMultiSplitWLT16(keys.k[i], waveFlags);
const uint index = ExtractPackedIndex(keys.k[i]) +
(waveIndex / serialIterations * HALF_RADIX);
const uint peerBits = countbits(waveFlags & ltMask);
for (uint k = 0; k < serialIterations; ++k)
{
if (waveIndex % serialIterations == k)
offsets.o[i] = ExtractPackedValue(g_d[index], keys.k[i]) + peerBits;
GroupMemoryBarrierWithGroupSync();
if (waveIndex % serialIterations == k && peerBits == 0)
{
InterlockedAdd(g_d[index],
countbits(waveFlags) << ExtractPackedShift(keys.k[i]));
}
GroupMemoryBarrierWithGroupSync();
}
}
return offsets;
}
inline uint WaveHistInclusiveScanCircularShiftWGE16(uint gtid, uint waveSize)
{
uint histReduction = g_d[gtid];
for (uint i = gtid + RADIX; i < WaveHistsSizeWGE16(waveSize); i += RADIX)
{
histReduction += g_d[i];
g_d[i] = histReduction - g_d[i];
}
return histReduction;
}
inline uint WaveHistInclusiveScanCircularShiftWLT16(uint gtid)
{
uint histReduction = g_d[gtid];
for (uint i = gtid + HALF_RADIX; i < WaveHistsSizeWLT16(); i += HALF_RADIX)
{
histReduction += g_d[i];
g_d[i] = histReduction - g_d[i];
}
return histReduction;
}
inline void WaveHistReductionExclusiveScanWGE16(uint gtid, uint waveSize, uint histReduction)
{
if (gtid < RADIX)
{
const uint laneMask = waveSize - 1;
g_d[((WaveGetLaneIndex() + 1) & laneMask) + (gtid & ~laneMask)] = histReduction;
}
GroupMemoryBarrierWithGroupSync();
if (gtid < RADIX / waveSize)
{
g_d[gtid * waveSize] =
WavePrefixSum(g_d[gtid * waveSize]);
}
GroupMemoryBarrierWithGroupSync();
uint t = WaveReadLaneAt(g_d[gtid], 0);
if (gtid < RADIX && WaveGetLaneIndex())
g_d[gtid] += t;
}
//inclusive/exclusive prefix sum up the histograms,
//use a blelloch scan for in place packed exclusive
inline void WaveHistReductionExclusiveScanWLT16(uint gtid)
{
uint shift = 1;
for (uint j = RADIX >> 2; j > 0; j >>= 1)
{
GroupMemoryBarrierWithGroupSync();
if (gtid < j)
{
g_d[((((gtid << 1) + 2) << shift) - 1) >> 1] +=
g_d[((((gtid << 1) + 1) << shift) - 1) >> 1] & 0xffff0000;
}
shift++;
}
GroupMemoryBarrierWithGroupSync();
if (gtid == 0)
g_d[HALF_RADIX - 1] &= 0xffff;
for (uint j = 1; j < RADIX >> 1; j <<= 1)
{
--shift;
GroupMemoryBarrierWithGroupSync();
if (gtid < j)
{
const uint t = ((((gtid << 1) + 1) << shift) - 1) >> 1;
const uint t2 = ((((gtid << 1) + 2) << shift) - 1) >> 1;
const uint t3 = g_d[t];
g_d[t] = (g_d[t] & 0xffff) | (g_d[t2] & 0xffff0000);
g_d[t2] += t3 & 0xffff0000;
}
}
GroupMemoryBarrierWithGroupSync();
if (gtid < HALF_RADIX)
{
const uint t = g_d[gtid];
g_d[gtid] = (t >> 16) + (t << 16) + (t & 0xffff0000);
}
}
inline void UpdateOffsetsWGE16(
uint gtid,
uint waveSize,
inout OffsetStruct offsets,
KeyStruct keys)
{
if (gtid >= waveSize)
{
const uint t = getWaveIndex(gtid, waveSize) * RADIX;
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
const uint t2 = ExtractDigit(keys.k[i]);
offsets.o[i] += g_d[t2 + t] + g_d[t2];
}
}
else
{
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
offsets.o[i] += g_d[ExtractDigit(keys.k[i])];
}
}
inline void UpdateOffsetsWLT16(
uint gtid,
uint waveSize,
uint serialIterations,
inout OffsetStruct offsets,
KeyStruct keys)
{
if (gtid >= waveSize * serialIterations)
{
const uint t = getWaveIndex(gtid, waveSize) / serialIterations * HALF_RADIX;
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
const uint t2 = ExtractPackedIndex(keys.k[i]);
offsets.o[i] += ExtractPackedValue(g_d[t2 + t] + g_d[t2], keys.k[i]);
}
}
else
{
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
offsets.o[i] += ExtractPackedValue(g_d[ExtractPackedIndex(keys.k[i])], keys.k[i]);
}
}
inline void ScatterKeysShared(OffsetStruct offsets, KeyStruct keys)
{
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
g_d[offsets.o[i]] = keys.k[i];
}
inline uint DescendingIndex(uint deviceIndex)
{
return e_numKeys - deviceIndex - 1;
}
inline void WriteKey(uint deviceIndex, uint groupSharedIndex)
{
#if defined(KEY_UINT)
b_alt[deviceIndex] = g_d[groupSharedIndex];
#elif defined(KEY_INT)
b_alt[deviceIndex] = UintToInt(g_d[groupSharedIndex]);
#elif defined(KEY_FLOAT)
b_alt[deviceIndex] = UintToFloat(g_d[groupSharedIndex]);
#endif
}
inline void LoadPayload(inout uint payload, uint deviceIndex)
{
#if defined(PAYLOAD_UINT)
payload = b_sortPayload[deviceIndex];
#elif defined(PAYLOAD_INT) || defined(PAYLOAD_FLOAT)
payload = asuint(b_sortPayload[deviceIndex]);
#endif
}
inline void ScatterPayloadsShared(OffsetStruct offsets, KeyStruct payloads)
{
ScatterKeysShared(offsets, payloads);
}
inline void WritePayload(uint deviceIndex, uint groupSharedIndex)
{
#if defined(PAYLOAD_UINT)
b_altPayload[deviceIndex] = g_d[groupSharedIndex];
#elif defined(PAYLOAD_INT)
b_altPayload[deviceIndex] = asint(g_d[groupSharedIndex]);
#elif defined(PAYLOAD_FLOAT)
b_altPayload[deviceIndex] = asfloat(g_d[groupSharedIndex]);
#endif
}
//*****************************************************************************
//SCATTERING: FULL PARTITIONS
//*****************************************************************************
//KEYS ONLY
inline void ScatterKeysOnlyDeviceAscending(uint gtid)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
}
inline void ScatterKeysOnlyDeviceDescending(uint gtid)
{
if (e_radixShift == 24)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
}
else
{
ScatterKeysOnlyDeviceAscending(gtid);
}
}
inline void ScatterKeysOnlyDevice(uint gtid)
{
#if defined(SHOULD_ASCEND)
ScatterKeysOnlyDeviceAscending(gtid);
#else
ScatterKeysOnlyDeviceDescending(gtid);
#endif
}
//KEY VALUE PAIRS
inline void ScatterPairsKeyPhaseAscending(
uint gtid,
inout DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
}
}
inline void ScatterPairsKeyPhaseDescending(
uint gtid,
inout DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
}
else
{
ScatterPairsKeyPhaseAscending(gtid, digits);
}
}
inline void LoadPayloadsWGE16(
uint gtid,
uint waveSize,
uint partIndex,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
LoadPayload(payloads.k[i], t);
}
}
inline void LoadPayloadsWLT16(
uint gtid,
uint waveSize,
uint partIndex,
uint serialIterations,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
LoadPayload(payloads.k[i], t);
}
}
inline void ScatterPayloadsAscending(uint gtid, DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
}
inline void ScatterPayloadsDescending(uint gtid, DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
else
{
ScatterPayloadsAscending(gtid, digits);
}
}
inline void ScatterPairsDevice(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
DigitStruct digits;
#if defined(SHOULD_ASCEND)
ScatterPairsKeyPhaseAscending(gtid, digits);
#else
ScatterPairsKeyPhaseDescending(gtid, digits);
#endif
GroupMemoryBarrierWithGroupSync();
KeyStruct payloads;
if (waveSize >= 16)
LoadPayloadsWGE16(gtid, waveSize, partIndex, payloads);
else
LoadPayloadsWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
ScatterPayloadsShared(offsets, payloads);
GroupMemoryBarrierWithGroupSync();
#if defined(SHOULD_ASCEND)
ScatterPayloadsAscending(gtid, digits);
#else
ScatterPayloadsDescending(gtid, digits);
#endif
}
inline void ScatterDevice(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
#if defined(SORT_PAIRS)
ScatterPairsDevice(
gtid,
waveSize,
partIndex,
offsets);
#else
ScatterKeysOnlyDevice(gtid);
#endif
}
//*****************************************************************************
//SCATTERING: PARTIAL PARTITIONS
//*****************************************************************************
//KEYS ONLY
inline void ScatterKeysOnlyDevicePartialAscending(uint gtid, uint finalPartSize)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
{
if (i < finalPartSize)
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
}
}
inline void ScatterKeysOnlyDevicePartialDescending(uint gtid, uint finalPartSize)
{
if (e_radixShift == 24)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
{
if (i < finalPartSize)
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
}
}
else
{
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
}
}
inline void ScatterKeysOnlyDevicePartial(uint gtid, uint partIndex)
{
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
#if defined(SHOULD_ASCEND)
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
#else
ScatterKeysOnlyDevicePartialDescending(gtid, finalPartSize);
#endif
}
//KEY VALUE PAIRS
inline void ScatterPairsKeyPhaseAscendingPartial(
uint gtid,
uint finalPartSize,
inout DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
}
}
}
inline void ScatterPairsKeyPhaseDescendingPartial(
uint gtid,
uint finalPartSize,
inout DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
}
}
else
{
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
}
}
inline void LoadPayloadsPartialWGE16(
uint gtid,
uint waveSize,
uint partIndex,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
if (t < e_numKeys)
LoadPayload(payloads.k[i], t);
}
}
inline void LoadPayloadsPartialWLT16(
uint gtid,
uint waveSize,
uint partIndex,
uint serialIterations,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
if (t < e_numKeys)
LoadPayload(payloads.k[i], t);
}
}
inline void ScatterPayloadsAscendingPartial(
uint gtid,
uint finalPartSize,
DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
}
}
inline void ScatterPayloadsDescendingPartial(
uint gtid,
uint finalPartSize,
DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
}
else
{
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
}
}
inline void ScatterPairsDevicePartial(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
DigitStruct digits;
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
#if defined(SHOULD_ASCEND)
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
#else
ScatterPairsKeyPhaseDescendingPartial(gtid, finalPartSize, digits);
#endif
GroupMemoryBarrierWithGroupSync();
KeyStruct payloads;
if (waveSize >= 16)
LoadPayloadsPartialWGE16(gtid, waveSize, partIndex, payloads);
else
LoadPayloadsPartialWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
ScatterPayloadsShared(offsets, payloads);
GroupMemoryBarrierWithGroupSync();
#if defined(SHOULD_ASCEND)
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
#else
ScatterPayloadsDescendingPartial(gtid, finalPartSize, digits);
#endif
}
inline void ScatterDevicePartial(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
#if defined(SORT_PAIRS)
ScatterPairsDevicePartial(
gtid,
waveSize,
partIndex,
offsets);
#else
ScatterKeysOnlyDevicePartial(gtid, partIndex);
#endif
}

File diff suppressed because it is too large Load Diff

View 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 手感继续改善。
## 阶段 6viewport 生命周期收口
### 目标
让 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 窗口合成进一步分离。

View 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. 阶段 Bhit-test / resize 手势
3. 阶段 C应用驱动 resize 主链
4. 阶段 Dviewport 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。

View 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 整理成一个真正可扩展的内核。`

View 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. 工作包拆分
## 工作包 Arequest / 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 的边界可以一句话说清
## 工作包 Bscene 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 时,不需要再发明第二套“可见对象数组解释逻辑”
## 工作包 Cpipeline 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 的边界清楚
## 工作包 DBuiltinForwardPipeline 瘦身
目标:
`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
## 工作包 Eeditor 注入点正式化
目标:
保留当前 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` 才会接得稳。

View File

@@ -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

View File

@@ -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) {

View File

@@ -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()) {

View File

@@ -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

View File

@@ -93,6 +93,49 @@ Shader "Builtin Forward Lit"
return output;
}
float SampleShadowVisibility(float2 sampleUv, float receiverDepth)
{
if (sampleUv.x < 0.0f || sampleUv.x > 1.0f ||
sampleUv.y < 0.0f || sampleUv.y > 1.0f) {
return 1.0f;
}
const float shadowDepth = ShadowMapTexture.Sample(ShadowMapSampler, sampleUv).r;
return receiverDepth <= shadowDepth ? 1.0f : 0.0f;
}
float ResolveShadowPcfAxisWeight(int offset)
{
const int absoluteOffset = abs(offset);
if (absoluteOffset == 0) {
return 6.0f;
}
if (absoluteOffset == 1) {
return 4.0f;
}
return 1.0f;
}
float ComputeShadowPcfVisibility(float2 shadowUv, float receiverDepth, float2 shadowTexelSize)
{
float weightedVisibility = 0.0f;
float weightSum = 0.0f;
[unroll]
for (int offsetY = -2; offsetY <= 2; ++offsetY) {
const float weightY = ResolveShadowPcfAxisWeight(offsetY);
[unroll]
for (int offsetX = -2; offsetX <= 2; ++offsetX) {
const float weight = weightY * ResolveShadowPcfAxisWeight(offsetX);
const float2 sampleUv =
shadowUv + float2((float)offsetX, (float)offsetY) * shadowTexelSize;
weightedVisibility += SampleShadowVisibility(sampleUv, receiverDepth) * weight;
weightSum += weight;
}
}
return weightSum > 0.0f ? weightedVisibility / weightSum : 1.0f;
}
float ComputeShadowAttenuation(float3 positionWS, float3 normalWS, float3 lightDirectionWS)
{
#ifndef XC_MAIN_LIGHT_SHADOWS
@@ -136,25 +179,10 @@ Shader "Builtin Forward Lit"
const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias;
#endif
const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize;
float visibility = 0.0f;
[unroll]
for (int offsetY = -1; offsetY <= 1; ++offsetY) {
[unroll]
for (int offsetX = -1; offsetX <= 1; ++offsetX) {
const float2 sampleUv =
shadowUv + float2((float)offsetX, (float)offsetY) * shadowTexelSize;
if (sampleUv.x < 0.0f || sampleUv.x > 1.0f ||
sampleUv.y < 0.0f || sampleUv.y > 1.0f) {
visibility += 1.0f;
continue;
}
const float shadowDepth = ShadowMapTexture.Sample(ShadowMapSampler, sampleUv).r;
visibility += receiverDepth <= shadowDepth ? 1.0f : 0.0f;
}
}
visibility *= (1.0f / 9.0f);
const float visibility = ComputeShadowPcfVisibility(
shadowUv,
receiverDepth,
shadowTexelSize);
const float shadowStrength = saturate(gShadowSampling.shadowStrength);
return lerp(1.0f - shadowStrength, 1.0f, visibility);
#endif

View File

@@ -25,12 +25,17 @@ public:
uint32_t GetCurrentBackBufferIndex() const override;
RHITexture* GetCurrentBackBuffer() override;
D3D12Texture* TryGetBackBuffer(uint32_t index);
const D3D12Texture* TryGetBackBuffer(uint32_t index) const;
D3D12Texture& GetBackBuffer(uint32_t index);
const D3D12Texture& GetBackBuffer(uint32_t index) const;
ID3D12CommandQueue* GetNativeCommandQueue() const { return m_commandQueue.Get(); }
void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override;
void Resize(uint32_t width, uint32_t height) override;
void* GetNativeHandle() override;
HRESULT GetLastResizeResult() const { return m_lastResizeResult; }
uint32_t GetWidth() const { return m_width; }
uint32_t GetHeight() const { return m_height; }
private:
bool RefreshBackBuffers();
@@ -42,6 +47,7 @@ private:
uint32_t m_height;
uint32_t m_bufferCount;
std::vector<D3D12Texture> m_backBuffers;
HRESULT m_lastResizeResult = S_OK;
};
} // namespace RHI

View File

@@ -1,3 +0,0 @@
#pragma once
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>

View File

@@ -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

View File

@@ -0,0 +1,196 @@
#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();
}
};
// Compatibility adapter for callers that still require CameraRenderRequest snapshots.
inline CameraRenderRequest BuildCompatibilityCameraRenderRequest(
const CameraFramePlan& plan) {
CameraRenderRequest request = plan.request;
request.shadowCaster = plan.shadowCaster;
request.directionalShadow = plan.directionalShadow;
request.postProcess = plan.postProcess;
request.finalOutput = plan.finalOutput;
request.finalColorPolicy = plan.finalColorPolicy;
request.preScenePasses = plan.preScenePasses;
request.postScenePasses = plan.postScenePasses;
request.overlayPasses = plan.overlayPasses;
return request;
}
} // namespace Rendering
} // namespace XCEngine

View File

@@ -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

View File

@@ -1,5 +1,6 @@
#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>
@@ -19,7 +20,8 @@ class RHIResourceView;
namespace Rendering {
class DirectionalShadowSurfaceCache;
struct DirectionalShadowExecutionState;
class DirectionalShadowRuntime;
class FullscreenPassSurfaceCache;
class RenderSurface;
class RenderPipelineAsset;
@@ -49,20 +51,17 @@ public:
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 +70,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;
};

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

@@ -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:

View File

@@ -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);

View File

@@ -16,6 +16,7 @@ public:
const char* GetName() const override;
protected:
RendererListType GetRendererListType() const override;
bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const override;
};

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 &&

View File

@@ -16,13 +16,15 @@ namespace Rendering {
// fitting and the default sampling / caster bias values emitted into the
// runtime shadow contract.
struct DirectionalShadowPlanningSettings {
uint32_t mapDimension = 1024u;
// Keep the single-map MainLight shadow at a higher baseline so the current
// non-cascaded implementation has enough texel density to be usable.
uint32_t mapDimension = 2048u;
float minFocusDistance = 5.0f;
float maxFocusDistance = 64.0f;
float maxFocusDistance = 32.0f;
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 = {};

View File

@@ -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;

View File

@@ -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,

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -42,6 +42,7 @@ bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* comm
m_width = width;
m_height = height;
m_bufferCount = bufferCount;
m_lastResizeResult = S_OK;
return RefreshBackBuffers();
}
@@ -61,6 +62,7 @@ bool D3D12SwapChain::Initialize(IDXGISwapChain* swapChain, uint32_t width, uint3
m_width = width;
m_height = height;
m_bufferCount = desc.BufferCount;
m_lastResizeResult = S_OK;
return RefreshBackBuffers();
}
@@ -71,20 +73,31 @@ void D3D12SwapChain::Shutdown() {
m_swapChain.Reset();
m_width = 0;
m_height = 0;
m_lastResizeResult = S_OK;
}
uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const {
return m_swapChain->GetCurrentBackBufferIndex();
}
D3D12Texture* D3D12SwapChain::TryGetBackBuffer(uint32_t index) {
return index < m_backBuffers.size() ? &m_backBuffers[index] : nullptr;
}
const D3D12Texture* D3D12SwapChain::TryGetBackBuffer(uint32_t index) const {
return index < m_backBuffers.size() ? &m_backBuffers[index] : nullptr;
}
D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) {
assert(index < m_backBuffers.size() && "BackBuffer index out of range");
return m_backBuffers[index];
D3D12Texture* backBuffer = TryGetBackBuffer(index);
assert(backBuffer != nullptr && "BackBuffer index out of range");
return *backBuffer;
}
const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const {
assert(index < m_backBuffers.size() && "BackBuffer index out of range");
return m_backBuffers[index];
const D3D12Texture* backBuffer = TryGetBackBuffer(index);
assert(backBuffer != nullptr && "BackBuffer index out of range");
return *backBuffer;
}
void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) {
@@ -92,10 +105,26 @@ void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) {
}
void D3D12SwapChain::Resize(uint32_t width, uint32_t height) {
if (!m_swapChain) {
m_lastResizeResult = E_POINTER;
return;
}
const uint32_t previousWidth = m_width;
const uint32_t previousHeight = m_height;
ReleaseBackBuffers();
const HRESULT hResult = m_swapChain->ResizeBuffers(m_bufferCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
const HRESULT hResult = m_swapChain->ResizeBuffers(
0,
width,
height,
DXGI_FORMAT_UNKNOWN,
0);
m_lastResizeResult = hResult;
if (FAILED(hResult)) {
m_width = previousWidth;
m_height = previousHeight;
RefreshBackBuffers();
return;
}

View File

@@ -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

View File

@@ -1,14 +1,15 @@
#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/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"
@@ -289,134 +290,115 @@ bool ExecuteFullscreenPassSequenceStage(
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;
}
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;
}
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 +425,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 +445,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 +521,35 @@ 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;
}
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();
@@ -613,15 +560,15 @@ bool CameraRenderer::ExecuteRenderPlan(
executionState.finalOutputSurfaceCache = m_finalOutputSurfaceCache.get();
for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) {
if (!request.HasFrameStage(stageInfo.stage) &&
if (!plan.HasFrameStage(stageInfo.stage) &&
stageInfo.stage != CameraFrameStage::MainScene) {
continue;
}
if (!ExecuteFrameStage(
stageInfo.stage,
request,
resolvedShadowCaster,
plan,
shadowState,
sceneData,
executionState)) {
return false;
@@ -633,14 +580,19 @@ bool CameraRenderer::ExecuteRenderPlan(
bool CameraRenderer::Render(
const CameraRenderRequest& request) {
if (!request.IsValid() || m_pipeline == nullptr) {
return Render(CameraFramePlan::FromRequest(request));
}
bool CameraRenderer::Render(
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 +600,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");

View File

@@ -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;
@@ -94,44 +49,71 @@ std::vector<CameraRenderRequest> SceneRenderer::BuildRenderRequests(
Components::CameraComponent* overrideCamera,
const RenderContext& context,
const RenderSurface& surface) {
std::vector<CameraRenderRequest> requests =
m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface);
ResolveCameraFinalColorPolicies(requests);
AttachFullscreenStageRequests(context, requests);
const std::vector<CameraFramePlan> plans =
BuildFramePlans(scene, overrideCamera, context, surface);
std::vector<CameraRenderRequest> requests = {};
requests.reserve(plans.size());
for (const CameraFramePlan& plan : plans) {
requests.push_back(BuildCompatibilityCameraRenderRequest(plan));
}
return requests;
}
std::vector<CameraFramePlan> SceneRenderer::BuildFramePlans(
const Components::Scene& scene,
Components::CameraComponent* overrideCamera,
const RenderContext& context,
const RenderSurface& surface) {
const std::vector<CameraRenderRequest> requests =
m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface);
return m_framePlanBuilder != nullptr
? m_framePlanBuilder->BuildPlans(requests, GetPipelineAsset())
: std::vector<CameraFramePlan>();
}
bool SceneRenderer::Render(const CameraRenderRequest& request) {
return m_cameraRenderer.Render(request);
return Render(CameraFramePlan::FromRequest(request));
}
bool SceneRenderer::Render(const std::vector<CameraRenderRequest>& requests) {
if (requests.empty()) {
std::vector<CameraFramePlan> plans = CreateFramePlansFromLegacyRequests(requests);
return Render(plans);
}
bool SceneRenderer::Render(const CameraFramePlan& plan) {
return m_cameraRenderer.Render(plan);
}
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 +127,18 @@ bool SceneRenderer::Render(
Components::CameraComponent* overrideCamera,
const RenderContext& context,
const RenderSurface& surface) {
return Render(BuildRenderRequests(scene, overrideCamera, context, surface));
return Render(BuildFramePlans(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);
std::vector<CameraFramePlan> SceneRenderer::CreateFramePlansFromLegacyRequests(
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));
}
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 plans;
}
} // namespace Rendering

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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;
}

View 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 = CreatePlansFromCompatibilityRequests(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::CreatePlansFromCompatibilityRequests(
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

View 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> CreatePlansFromCompatibilityRequests(
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

View File

@@ -286,7 +286,7 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
gameObject->GetComponent<Components::MeshRendererComponent>();
if (meshRenderer == nullptr ||
!meshRenderer->IsEnabled() ||
(!meshRenderer->GetCastShadows() && !meshRenderer->GetReceiveShadows())) {
!meshRenderer->GetCastShadows()) {
continue;
}

View File

@@ -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;
}
}
}

View 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

View 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

View File

@@ -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
@@ -141,6 +148,7 @@ xcui_editor_apply_common_target_settings(XCUIEditorHost PUBLIC)
target_link_libraries(XCUIEditorHost PUBLIC
XCEngine
d2d1.lib
d3d11.lib
d3d12.lib
d3dcompiler.lib
dwrite.lib
@@ -164,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

View File

@@ -1,5 +1,7 @@
#include "Application.h"
#include <Host/WindowMessageDispatcher.h>
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
@@ -36,9 +38,16 @@ constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost";
constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor";
constexpr UINT kDefaultDpi = 96u;
constexpr float kBaseDpiScale = 96.0f;
constexpr DWORD kBorderlessWindowStyle =
WS_POPUP | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
Application* GetApplicationFromWindow(HWND hwnd) {
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
bool ResolveVerboseRuntimeTraceEnabled() {
wchar_t buffer[8] = {};
const DWORD length = GetEnvironmentVariableW(
L"XCUIEDITOR_VERBOSE_TRACE",
buffer,
static_cast<DWORD>(std::size(buffer)));
return length > 0u && buffer[0] != L'0';
}
UINT QuerySystemDpi() {
@@ -121,25 +130,6 @@ void EnableDpiAwareness() {
}
}
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);
}
}
std::string TruncateText(const std::string& text, std::size_t maxLength) {
if (text.size() <= maxLength) {
return text;
@@ -394,7 +384,6 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) {
}
RenderFrame();
Sleep(8);
}
Shutdown();
@@ -419,7 +408,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
WNDCLASSEXW windowClass = {};
windowClass.cbSize = sizeof(windowClass);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
windowClass.lpfnWndProc = &Application::WndProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
@@ -431,10 +420,10 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
}
m_hwnd = CreateWindowExW(
0,
WS_EX_APPWINDOW,
kWindowClassName,
kWindowTitle,
WS_OVERLAPPEDWINDOW,
kBorderlessWindowStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
1540,
@@ -447,9 +436,10 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
LogRuntimeTrace("app", "window creation failed");
return false;
}
m_windowDpi = QueryWindowDpi(m_hwnd);
m_dpiScale = GetDpiScale();
m_renderer.SetDpiScale(m_dpiScale);
Host::EnableBorderlessWindowShadow(m_hwnd);
m_hostRuntime.Reset();
m_hostRuntime.SetWindowDpi(QueryWindowDpi(m_hwnd));
m_renderer.SetDpiScale(GetDpiScale());
m_editorContext.SetExitRequestHandler([this]() {
if (m_hwnd != nullptr) {
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
@@ -457,7 +447,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
});
std::ostringstream dpiTrace = {};
dpiTrace << "initial dpi=" << m_windowDpi << " scale=" << m_dpiScale;
dpiTrace << "initial dpi=" << m_hostRuntime.GetWindowDpi() << " scale=" << GetDpiScale();
LogRuntimeTrace("window", dpiTrace.str());
if (!m_renderer.Initialize(m_hwnd)) {
@@ -472,10 +462,18 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
LogRuntimeTrace("app", "d3d12 window renderer initialization failed");
return false;
}
const Host::D3D12WindowRenderLoopAttachResult attachResult =
m_windowRenderLoop.Attach(m_renderer, m_windowRenderer);
if (!attachResult.interopWarning.empty()) {
LogRuntimeTrace(
"app",
attachResult.interopWarning);
}
m_editorContext.AttachTextMeasurer(m_renderer);
m_editorWorkspace.Initialize(repoRoot, m_renderer);
m_editorWorkspace.AttachViewportWindowRenderer(m_windowRenderer);
m_editorWorkspace.SetViewportSurfacePresentationEnabled(false);
m_editorWorkspace.SetViewportSurfacePresentationEnabled(
attachResult.hasViewportSurfacePresentation);
if (!m_editorWorkspace.GetBuiltInIconError().empty()) {
LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError());
}
@@ -483,6 +481,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
"app",
"workspace initialized: " +
m_editorContext.DescribeWorkspaceState(m_editorWorkspace.GetShellInteractionState()));
m_renderReady = true;
ShowWindow(m_hwnd, nCmdShow);
UpdateWindow(m_hwnd);
@@ -498,12 +497,14 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
void Application::Shutdown() {
LogRuntimeTrace("app", "shutdown begin");
m_renderReady = false;
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
m_autoScreenshot.Shutdown();
m_editorWorkspace.Shutdown();
m_windowRenderLoop.Detach();
m_windowRenderer.Shutdown();
m_renderer.Shutdown();
@@ -522,16 +523,15 @@ void Application::Shutdown() {
}
void Application::RenderFrame() {
if (m_hwnd == nullptr) {
if (!m_renderReady || m_hwnd == nullptr) {
return;
}
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
const unsigned int pixelWidth =
static_cast<unsigned int>((std::max)(clientRect.right - clientRect.left, 1L));
const unsigned int pixelHeight =
static_cast<unsigned int>((std::max)(clientRect.bottom - clientRect.top, 1L));
UINT pixelWidth = 0u;
UINT pixelHeight = 0u;
if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) {
return;
}
const float width = PixelsToDips(static_cast<float>(pixelWidth));
const float height = PixelsToDips(static_cast<float>(pixelHeight));
@@ -544,7 +544,7 @@ void Application::RenderFrame() {
if (m_editorContext.IsValid()) {
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
m_pendingInputEvents.clear();
if (!frameEvents.empty()) {
if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) {
LogRuntimeTrace(
"input",
DescribeInputEvents(frameEvents) + " | " +
@@ -552,9 +552,10 @@ void Application::RenderFrame() {
m_editorWorkspace.GetShellInteractionState()));
}
const bool d3d12FrameBegun = m_windowRenderer.BeginFrame();
if (!d3d12FrameBegun) {
LogRuntimeTrace("viewport", "d3d12 frame begin failed");
const Host::D3D12WindowRenderLoopFrameContext frameContext =
m_windowRenderLoop.BeginFrame();
if (!frameContext.warning.empty()) {
LogRuntimeTrace("viewport", frameContext.warning);
}
m_editorWorkspace.Update(
@@ -562,17 +563,12 @@ void Application::RenderFrame() {
UIRect(0.0f, 0.0f, width, height),
frameEvents,
BuildCaptureStatusText());
if (d3d12FrameBegun) {
m_editorWorkspace.RenderRequestedViewports(m_windowRenderer.GetRenderContext());
if (!m_windowRenderer.SubmitFrame(false)) {
LogRuntimeTrace("viewport", "d3d12 offscreen frame submit failed");
}
}
const UIEditorShellInteractionFrame& shellFrame =
m_editorWorkspace.GetShellFrame();
if (!frameEvents.empty() ||
shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
if (IsVerboseRuntimeTraceEnabled() &&
(!frameEvents.empty() ||
shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) {
std::ostringstream frameTrace = {};
frameTrace << "result consumed="
<< (shellFrame.result.consumed ? "true" : "false")
@@ -595,6 +591,9 @@ void Application::RenderFrame() {
ApplyHostedContentCaptureRequests();
ApplyCurrentCursor();
m_editorWorkspace.Append(drawList);
if (frameContext.canRenderViewports) {
m_editorWorkspace.RenderRequestedViewports(frameContext.renderContext);
}
} else {
drawList.AddText(
UIPoint(28.0f, 28.0f),
@@ -610,18 +609,46 @@ void Application::RenderFrame() {
12.0f);
}
const bool framePresented = m_renderer.Render(drawData);
AppendBorderlessWindowChrome(drawList, width);
const Host::D3D12WindowRenderLoopPresentResult presentResult =
m_windowRenderLoop.Present(drawData);
if (!presentResult.warning.empty()) {
LogRuntimeTrace("present", presentResult.warning);
}
m_autoScreenshot.CaptureIfRequested(
m_renderer,
drawData,
pixelWidth,
pixelHeight,
framePresented);
presentResult.framePresented);
}
void Application::OnPaintMessage() {
if (!m_renderReady || m_hwnd == nullptr) {
return;
}
PAINTSTRUCT paintStruct = {};
BeginPaint(m_hwnd, &paintStruct);
RenderFrame();
EndPaint(m_hwnd, &paintStruct);
}
bool Application::IsBorderlessWindowEnabled() const {
return true;
}
LRESULT Application::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const {
return Host::HandleBorderlessWindowNcCalcSize(
m_hwnd,
wParam,
lParam,
m_hostRuntime.GetWindowDpi());
}
float Application::GetDpiScale() const {
const UINT dpi = m_windowDpi == 0u ? kDefaultDpi : m_windowDpi;
return static_cast<float>(dpi) / kBaseDpiScale;
return m_hostRuntime.GetDpiScale(kBaseDpiScale);
}
float Application::PixelsToDips(float pixels) const {
@@ -646,6 +673,14 @@ bool Application::IsPointerInsideClientArea() const {
}
LPCWSTR Application::ResolveCurrentCursorResource() const {
const Host::BorderlessWindowResizeEdge borderlessResizeEdge =
m_hostRuntime.IsBorderlessResizeActive()
? m_hostRuntime.GetBorderlessResizeEdge()
: m_hostRuntime.GetHoveredBorderlessResizeEdge();
if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) {
return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge);
}
switch (m_editorWorkspace.GetHostedContentCursorKind()) {
case App::ProductProjectPanel::CursorKind::ResizeEW:
return IDC_SIZEWE;
@@ -679,6 +714,329 @@ bool Application::ApplyCurrentCursor() const {
return true;
}
Host::BorderlessWindowResizeEdge Application::HitTestBorderlessWindowResizeEdge(LPARAM lParam) const {
if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr || IsZoomed(m_hwnd)) {
return Host::BorderlessWindowResizeEdge::None;
}
RECT clientRect = {};
if (!GetClientRect(m_hwnd, &clientRect)) {
return Host::BorderlessWindowResizeEdge::None;
}
const float clientWidthDips =
PixelsToDips(static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L)));
const float clientHeightDips =
PixelsToDips(static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L)));
return Host::HitTestBorderlessWindowResizeEdge(
::XCEngine::UI::UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips),
ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
}
bool Application::UpdateBorderlessWindowResizeHover(LPARAM lParam) {
const Host::BorderlessWindowResizeEdge hoveredEdge = HitTestBorderlessWindowResizeEdge(lParam);
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == hoveredEdge) {
return false;
}
m_hostRuntime.SetHoveredBorderlessResizeEdge(hoveredEdge);
ApplyBorderlessWindowResizeCursorHoverPriority();
return true;
}
bool Application::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) {
const Host::BorderlessWindowResizeEdge edge = HitTestBorderlessWindowResizeEdge(lParam);
if (edge == Host::BorderlessWindowResizeEdge::None || m_hwnd == nullptr) {
return false;
}
POINT screenPoint = {};
if (!GetCursorPos(&screenPoint)) {
return false;
}
RECT windowRect = {};
if (!GetWindowRect(m_hwnd, &windowRect)) {
return false;
}
m_hostRuntime.BeginBorderlessResize(edge, screenPoint, windowRect);
SetCapture(m_hwnd);
InvalidateHostWindow();
return true;
}
bool Application::HandleBorderlessWindowResizeButtonUp() {
if (!m_hostRuntime.IsBorderlessResizeActive()) {
return false;
}
m_hostRuntime.EndBorderlessResize();
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
InvalidateHostWindow();
return true;
}
bool Application::HandleBorderlessWindowResizePointerMove() {
if (!m_hostRuntime.IsBorderlessResizeActive() || m_hwnd == nullptr) {
return false;
}
POINT currentScreenPoint = {};
if (!GetCursorPos(&currentScreenPoint)) {
return false;
}
RECT targetRect = Host::ComputeBorderlessWindowResizeRect(
m_hostRuntime.GetBorderlessResizeInitialWindowRect(),
m_hostRuntime.GetBorderlessResizeInitialScreenPoint(),
currentScreenPoint,
m_hostRuntime.GetBorderlessResizeEdge(),
640,
360);
const int width = targetRect.right - targetRect.left;
const int height = targetRect.bottom - targetRect.top;
if (width <= 0 || height <= 0) {
return true;
}
m_hostRuntime.SetPredictedClientPixelSize(
static_cast<UINT>(width),
static_cast<UINT>(height));
ApplyWindowResize(
static_cast<UINT>(width),
static_cast<UINT>(height));
RenderFrame();
SetWindowPos(
m_hwnd,
nullptr,
targetRect.left,
targetRect.top,
width,
height,
SWP_NOZORDER | SWP_NOACTIVATE);
return true;
}
void Application::ClearBorderlessWindowResizeState() {
if (m_hostRuntime.IsBorderlessResizeActive()) {
return;
}
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None) {
return;
}
m_hostRuntime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None);
InvalidateHostWindow();
}
void Application::ForceClearBorderlessWindowResizeState() {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None &&
!m_hostRuntime.IsBorderlessResizeActive()) {
return;
}
m_hostRuntime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None);
m_hostRuntime.EndBorderlessResize();
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
InvalidateHostWindow();
}
void Application::ApplyBorderlessWindowResizeCursorHoverPriority() {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
m_hostRuntime.IsBorderlessResizeActive()) {
m_borderlessWindowChromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None;
}
}
Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLayout(
float clientWidthDips) const {
const auto& menuBarMetrics = ResolveUIEditorMenuBarMetrics();
::XCEngine::UI::UIRect titleBarRect(0.0f, 0.0f, clientWidthDips, menuBarMetrics.barHeight);
float leadingOccupiedRight = titleBarRect.x;
const UIEditorShellInteractionFrame& shellFrame = m_editorWorkspace.GetShellFrame();
if (shellFrame.shellFrame.layout.menuBarRect.width > 0.0f &&
shellFrame.shellFrame.layout.menuBarRect.height > 0.0f) {
titleBarRect = shellFrame.shellFrame.layout.menuBarRect;
}
const auto& buttonRects = shellFrame.shellFrame.layout.menuBarLayout.buttonRects;
if (!buttonRects.empty()) {
const auto& lastRect = buttonRects.back();
leadingOccupiedRight = lastRect.x + lastRect.width;
}
return Host::BuildBorderlessWindowChromeLayout(titleBarRect, leadingOccupiedRight);
}
Host::BorderlessWindowChromeHitTarget Application::HitTestBorderlessWindowChrome(LPARAM lParam) const {
if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr) {
return Host::BorderlessWindowChromeHitTarget::None;
}
RECT clientRect = {};
if (!GetClientRect(m_hwnd, &clientRect)) {
return Host::BorderlessWindowChromeHitTarget::None;
}
const float clientWidthDips = PixelsToDips(
static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L)));
const Host::BorderlessWindowChromeLayout layout =
ResolveBorderlessWindowChromeLayout(clientWidthDips);
return Host::HitTestBorderlessWindowChrome(
layout,
ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
}
bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
m_hostRuntime.IsBorderlessResizeActive()) {
const bool changed =
m_borderlessWindowChromeState.hoveredTarget != Host::BorderlessWindowChromeHitTarget::None;
m_borderlessWindowChromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None;
return changed;
}
const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam);
const Host::BorderlessWindowChromeHitTarget buttonTarget =
hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton ||
hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton ||
hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton
? hitTarget
: Host::BorderlessWindowChromeHitTarget::None;
if (m_borderlessWindowChromeState.hoveredTarget == buttonTarget) {
return false;
}
m_borderlessWindowChromeState.hoveredTarget = buttonTarget;
return true;
}
bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) {
if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None ||
m_hostRuntime.IsBorderlessResizeActive()) {
return false;
}
const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam);
switch (hitTarget) {
case Host::BorderlessWindowChromeHitTarget::MinimizeButton:
case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
case Host::BorderlessWindowChromeHitTarget::CloseButton:
m_borderlessWindowChromeState.pressedTarget = hitTarget;
if (m_hwnd != nullptr) {
SetCapture(m_hwnd);
}
InvalidateHostWindow();
return true;
case Host::BorderlessWindowChromeHitTarget::DragRegion:
if (m_hwnd != nullptr) {
ReleaseCapture();
SendMessageW(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
return true;
case Host::BorderlessWindowChromeHitTarget::None:
default:
return false;
}
}
bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) {
const Host::BorderlessWindowChromeHitTarget pressedTarget =
m_borderlessWindowChromeState.pressedTarget;
if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton &&
pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton &&
pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) {
return false;
}
const Host::BorderlessWindowChromeHitTarget releasedTarget =
HitTestBorderlessWindowChrome(lParam);
m_borderlessWindowChromeState.pressedTarget = Host::BorderlessWindowChromeHitTarget::None;
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
InvalidateHostWindow();
if (pressedTarget == releasedTarget) {
ExecuteBorderlessWindowChromeAction(pressedTarget);
}
return true;
}
bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) {
if (HitTestBorderlessWindowChrome(lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) {
return false;
}
ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton);
return true;
}
void Application::ClearBorderlessWindowChromeState() {
if (m_borderlessWindowChromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None &&
m_borderlessWindowChromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None) {
return;
}
m_borderlessWindowChromeState = {};
InvalidateHostWindow();
}
void Application::AppendBorderlessWindowChrome(
::XCEngine::UI::UIDrawList& drawList,
float clientWidthDips) const {
if (!IsBorderlessWindowEnabled()) {
return;
}
const Host::BorderlessWindowChromeLayout layout =
ResolveBorderlessWindowChromeLayout(clientWidthDips);
Host::AppendBorderlessWindowChrome(
drawList,
layout,
m_borderlessWindowChromeState,
m_hwnd != nullptr && IsZoomed(m_hwnd));
}
void Application::ExecuteBorderlessWindowChromeAction(
Host::BorderlessWindowChromeHitTarget target) {
if (m_hwnd == nullptr) {
return;
}
switch (target) {
case Host::BorderlessWindowChromeHitTarget::MinimizeButton:
ShowWindow(m_hwnd, SW_MINIMIZE);
break;
case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
ShowWindow(m_hwnd, IsZoomed(m_hwnd) ? SW_RESTORE : SW_MAXIMIZE);
break;
case Host::BorderlessWindowChromeHitTarget::CloseButton:
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
break;
case Host::BorderlessWindowChromeHitTarget::DragRegion:
case Host::BorderlessWindowChromeHitTarget::None:
default:
break;
}
InvalidateHostWindow();
}
void Application::InvalidateHostWindow() const {
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
InvalidateRect(m_hwnd, nullptr, FALSE);
}
}
UIPoint Application::ConvertClientPixelsToDips(LONG x, LONG y) const {
return UIPoint(
PixelsToDips(static_cast<float>(x)),
@@ -707,6 +1065,11 @@ void Application::LogRuntimeTrace(
AppendUIEditorRuntimeTrace(channel, message);
}
bool Application::IsVerboseRuntimeTraceEnabled() {
static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled();
return s_enabled;
}
std::string Application::DescribeInputEvents(
const std::vector<UIInputEvent>& events) const {
std::ostringstream stream = {};
@@ -727,32 +1090,100 @@ std::string Application::DescribeInputEvents(
}
void Application::OnResize(UINT width, UINT height) {
if (width == 0 || height == 0) {
return;
m_hostRuntime.ClearPredictedClientPixelSize();
ApplyWindowResize(width, height);
}
void Application::OnEnterSizeMove() {
m_hostRuntime.BeginInteractiveResize();
}
void Application::OnExitSizeMove() {
m_hostRuntime.EndInteractiveResize();
m_hostRuntime.ClearPredictedClientPixelSize();
UINT width = 0u;
UINT height = 0u;
if (QueryCurrentClientPixelSize(width, height)) {
ApplyWindowResize(width, height);
}
}
bool Application::ApplyWindowResize(UINT width, UINT height) {
if (!m_renderReady || width == 0u || height == 0u) {
return false;
}
m_renderer.Resize(width, height);
m_windowRenderer.Resize(static_cast<int>(width), static_cast<int>(height));
const Host::D3D12WindowRenderLoopResizeResult resizeResult =
m_windowRenderLoop.ApplyResize(width, height);
m_editorWorkspace.SetViewportSurfacePresentationEnabled(
resizeResult.hasViewportSurfacePresentation);
if (!resizeResult.windowRendererWarning.empty()) {
LogRuntimeTrace("present", resizeResult.windowRendererWarning);
}
if (!resizeResult.interopWarning.empty()) {
LogRuntimeTrace("present", resizeResult.interopWarning);
}
return resizeResult.hasViewportSurfacePresentation;
}
bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const {
outWidth = 0u;
outHeight = 0u;
if (m_hwnd == nullptr || !IsWindow(m_hwnd)) {
return false;
}
RECT clientRect = {};
if (!GetClientRect(m_hwnd, &clientRect)) {
return false;
}
const LONG width = clientRect.right - clientRect.left;
const LONG height = clientRect.bottom - clientRect.top;
if (width <= 0 || height <= 0) {
return false;
}
outWidth = static_cast<UINT>(width);
outHeight = static_cast<UINT>(height);
return true;
}
bool Application::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const {
if (m_hostRuntime.TryGetPredictedClientPixelSize(outWidth, outHeight)) {
return true;
}
return QueryCurrentClientPixelSize(outWidth, outHeight);
}
void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) {
m_windowDpi = dpi == 0u ? kDefaultDpi : dpi;
m_dpiScale = GetDpiScale();
m_renderer.SetDpiScale(m_dpiScale);
m_hostRuntime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi);
m_renderer.SetDpiScale(GetDpiScale());
if (m_hwnd != nullptr) {
const LONG windowWidth = suggestedRect.right - suggestedRect.left;
const LONG windowHeight = suggestedRect.bottom - suggestedRect.top;
SetWindowPos(
m_hwnd,
nullptr,
suggestedRect.left,
suggestedRect.top,
suggestedRect.right - suggestedRect.left,
suggestedRect.bottom - suggestedRect.top,
windowWidth,
windowHeight,
SWP_NOZORDER | SWP_NOACTIVATE);
InvalidateRect(m_hwnd, nullptr, FALSE);
UINT clientWidth = 0u;
UINT clientHeight = 0u;
if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) {
ApplyWindowResize(clientWidth, clientHeight);
}
Host::EnableBorderlessWindowShadow(m_hwnd);
}
std::ostringstream trace = {};
trace << "dpi changed to " << m_windowDpi << " scale=" << m_dpiScale;
trace << "dpi changed to " << m_hostRuntime.GetWindowDpi() << " scale=" << GetDpiScale();
LogRuntimeTrace("window", trace.str());
}
@@ -872,47 +1303,41 @@ LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionI
}
LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_NCCREATE) {
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));
return TRUE;
LRESULT dispatcherResult = 0;
if (Host::WindowMessageDispatcher::TryHandleNonClientCreate(
hwnd,
message,
lParam,
dispatcherResult)) {
return dispatcherResult;
}
Application* application = Host::WindowMessageDispatcher::GetApplicationFromWindow(hwnd);
if (application != nullptr &&
Host::WindowMessageDispatcher::TryDispatch(
hwnd,
*application,
message,
wParam,
lParam,
dispatcherResult)) {
return dispatcherResult;
}
Application* application = GetApplicationFromWindow(hwnd);
switch (message) {
case WM_SETCURSOR:
if (application != nullptr &&
LOWORD(lParam) == HTCLIENT &&
application->ApplyCurrentCursor()) {
return TRUE;
}
break;
case WM_DPICHANGED:
if (application != nullptr && lParam != 0) {
application->OnDpiChanged(
static_cast<UINT>(LOWORD(wParam)),
*reinterpret_cast<RECT*>(lParam));
return 0;
}
break;
case WM_SIZE:
if (application != nullptr && wParam != SIZE_MINIMIZED) {
application->OnResize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
}
return 0;
case WM_PAINT:
if (application != nullptr) {
PAINTSTRUCT paintStruct = {};
BeginPaint(hwnd, &paintStruct);
application->RenderFrame();
EndPaint(hwnd, &paintStruct);
return 0;
}
break;
case WM_MOUSEMOVE:
if (application != nullptr) {
if (application->HandleBorderlessWindowResizePointerMove()) {
return 0;
}
const bool resizeHoverChanged =
application->UpdateBorderlessWindowResizeHover(lParam);
if (application->UpdateBorderlessWindowChromeHover(lParam)) {
application->InvalidateHostWindow();
}
if (resizeHoverChanged) {
application->InvalidateHostWindow();
}
if (!application->m_trackingMouseLeave) {
TRACKMOUSEEVENT trackMouseEvent = {};
trackMouseEvent.cbSize = sizeof(trackMouseEvent);
@@ -922,6 +1347,17 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
application->m_trackingMouseLeave = true;
}
}
if (application->m_hostRuntime.GetHoveredBorderlessResizeEdge() !=
Host::BorderlessWindowResizeEdge::None) {
return 0;
}
const Host::BorderlessWindowChromeHitTarget chromeHitTarget =
application->HitTestBorderlessWindowChrome(lParam);
if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton ||
chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton ||
chromeHitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton) {
return 0;
}
application->QueuePointerEvent(
UIInputEventType::PointerMove,
UIPointerButton::None,
@@ -933,12 +1369,20 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
case WM_MOUSELEAVE:
if (application != nullptr) {
application->m_trackingMouseLeave = false;
application->ClearBorderlessWindowResizeState();
application->ClearBorderlessWindowChromeState();
application->QueuePointerLeaveEvent();
return 0;
}
break;
case WM_LBUTTONDOWN:
if (application != nullptr) {
if (application->HandleBorderlessWindowResizeButtonDown(lParam)) {
return 0;
}
if (application->HandleBorderlessWindowChromeButtonDown(lParam)) {
return 0;
}
SetFocus(hwnd);
application->QueuePointerEvent(
UIInputEventType::PointerButtonDown,
@@ -950,6 +1394,12 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
break;
case WM_LBUTTONUP:
if (application != nullptr) {
if (application->HandleBorderlessWindowResizeButtonUp()) {
return 0;
}
if (application->HandleBorderlessWindowChromeButtonUp(lParam)) {
return 0;
}
application->QueuePointerEvent(
UIInputEventType::PointerButtonUp,
UIPointerButton::Left,
@@ -958,6 +1408,12 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
return 0;
}
break;
case WM_LBUTTONDBLCLK:
if (application != nullptr &&
application->HandleBorderlessWindowChromeDoubleClick(lParam)) {
return 0;
}
break;
case WM_MOUSEWHEEL:
if (application != nullptr) {
application->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam);
@@ -983,8 +1439,15 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP
reinterpret_cast<HWND>(lParam) != hwnd &&
application->HasInteractiveCaptureState()) {
application->QueueWindowFocusEvent(UIInputEventType::FocusLost);
application->ForceClearBorderlessWindowResizeState();
application->ClearBorderlessWindowChromeState();
return 0;
}
if (application != nullptr &&
reinterpret_cast<HWND>(lParam) != hwnd) {
application->ForceClearBorderlessWindowResizeState();
application->ClearBorderlessWindowChromeState();
}
break;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:

View File

@@ -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,13 +40,21 @@ 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 OnPaintMessage();
void OnResize(UINT width, UINT height);
void OnEnterSizeMove();
void OnExitSizeMove();
void OnDpiChanged(UINT dpi, const RECT& suggestedRect);
bool ApplyWindowResize(UINT width, UINT height);
bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const;
bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const;
bool IsPointerInsideClientArea() const;
bool ApplyCurrentCursor() const;
LPCWSTR ResolveCurrentCursorResource() const;
@@ -62,22 +78,48 @@ 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;
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);
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();
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();
HWND m_hwnd = nullptr;
HINSTANCE m_hInstance = nullptr;
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_renderReady = false;
::XCEngine::UI::Editor::Host::BorderlessWindowChromeState m_borderlessWindowChromeState = {};
::XCEngine::UI::Editor::Host::HostRuntimeState m_hostRuntime = {};
};
int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow);

View File

@@ -0,0 +1,328 @@
#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,
float insetX,
float insetY) {
const float y = rect.y + rect.height - insetY;
drawList.AddLine(
UIPoint(rect.x + insetX, y),
UIPoint(rect.x + rect.width - insetX, y),
color,
thickness);
}
void AppendMaximizeGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
drawList.AddRectOutline(
UIRect(
rect.x + insetX,
rect.y + insetY,
(std::max)(0.0f, rect.width - insetX * 2.0f),
(std::max)(0.0f, rect.height - insetY * 2.0f)),
color,
thickness);
}
void AppendRestoreGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
const float width = (std::max)(0.0f, rect.width - insetX * 2.0f);
const float height = (std::max)(0.0f, rect.height - insetY * 2.0f);
const float offset = 3.0f;
drawList.AddRectOutline(
UIRect(
rect.x + insetX + offset,
rect.y + insetY,
(std::max)(0.0f, width - offset),
(std::max)(0.0f, height - offset)),
color,
thickness);
drawList.AddRectOutline(
UIRect(
rect.x + insetX,
rect.y + insetY + offset,
(std::max)(0.0f, width - offset),
(std::max)(0.0f, height - offset)),
color,
thickness);
}
void AppendCloseGlyph(
UIDrawList& drawList,
const UIRect& rect,
const UIColor& color,
float thickness,
float insetX,
float insetY) {
drawList.AddLine(
UIPoint(rect.x + insetX, rect.y + insetY),
UIPoint(rect.x + rect.width - insetX, rect.y + rect.height - insetY),
color,
thickness);
drawList.AddLine(
UIPoint(rect.x + rect.width - insetX, rect.y + insetY),
UIPoint(rect.x + insetX, rect.y + rect.height - insetY),
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);
}
} // 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,
metrics.iconInsetX,
metrics.iconInsetY);
break;
case BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
if (maximized) {
AppendRestoreGlyph(
drawList,
button.rect,
iconColor,
metrics.iconThickness,
metrics.iconInsetX,
metrics.iconInsetY);
} else {
AppendMaximizeGlyph(
drawList,
button.rect,
iconColor,
metrics.iconThickness,
metrics.iconInsetX,
metrics.iconInsetY);
}
break;
case BorderlessWindowChromeHitTarget::CloseButton:
AppendCloseGlyph(
drawList,
button.rect,
iconColor,
metrics.iconThickness,
metrics.iconInsetX,
metrics.iconInsetY);
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 MARGINS margins = { 1, 1, 1, 1 };
extendFrameIntoClientArea(hwnd, &margins);
}
}
LRESULT HandleBorderlessWindowNcCalcSize(
HWND hwnd,
WPARAM wParam,
LPARAM lParam,
UINT dpi) {
if (wParam == FALSE || lParam == 0) {
return 0;
}
auto* params = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
if (params == nullptr) {
return 0;
}
if (IsZoomed(hwnd)) {
const int frameX =
QuerySystemMetricForDpi(SM_CXFRAME, dpi) +
QuerySystemMetricForDpi(SM_CXPADDEDBORDER, dpi);
const int frameY =
QuerySystemMetricForDpi(SM_CYFRAME, dpi) +
QuerySystemMetricForDpi(SM_CXPADDEDBORDER, dpi);
params->rgrc[0].left += frameX;
params->rgrc[0].right -= frameX;
params->rgrc[0].top += frameY;
params->rgrc[0].bottom -= frameY;
}
return 0;
}
} // namespace XCEngine::UI::Editor::Host

View File

@@ -0,0 +1,84 @@
#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 = 45.0f;
float buttonInsetX = 0.0f;
float dragPaddingLeft = 8.0f;
float dragPaddingRight = 8.0f;
float iconInsetX = 16.0f;
float iconInsetY = 8.0f;
float iconThickness = 1.2f;
};
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 EnableBorderlessWindowShadow(HWND hwnd);
LRESULT HandleBorderlessWindowNcCalcSize(
HWND hwnd,
WPARAM wParam,
LPARAM lParam,
UINT dpi);
} // namespace XCEngine::UI::Editor::Host

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -1,418 +1,159 @@
#include "D3D12WindowRenderer.h"
#include <XCEngine/RHI/RHIFactory.h>
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();
if (hwnd == nullptr || width <= 0 || height <= 0) {
m_lastError = "Initialize rejected an invalid hwnd or size.";
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) {
Shutdown();
return false;
}
RHIDeviceDesc deviceDesc = {};
#ifdef _DEBUG
deviceDesc.enableDebugLayer = true;
deviceDesc.enableGPUValidation = true;
#endif
if (!m_device->Initialize(deviceDesc)) {
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) {
Shutdown();
return false;
}
CommandListDesc commandListDesc = {};
commandListDesc.commandListType = static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
m_commandList = m_device->CreateCommandList(commandListDesc);
if (m_commandList == nullptr || GetD3D12CommandList() == nullptr) {
Shutdown();
return false;
}
m_commandList->Close();
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) {
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) {
Shutdown();
return false;
}
m_srvDescriptorSize = m_srvHeap->GetDescriptorSize();
m_srvUsage.assign(kSrvDescriptorCount, false);
if (!RecreateBackBufferViews()) {
if (!m_presenter.Initialize(m_hostDevice, hwnd, width, height)) {
m_lastError = m_presenter.GetLastError();
Shutdown();
return false;
}
m_lastError.clear();
return true;
}
void D3D12WindowRenderer::Shutdown() {
WaitForGpuIdle();
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;
}
if (m_commandList != nullptr) {
m_commandList->Shutdown();
delete m_commandList;
m_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_srvDescriptorSize = 0;
m_presenter.Shutdown();
m_hostDevice.Shutdown();
m_activeBackBufferIndex = 0u;
m_lastError.clear();
}
void D3D12WindowRenderer::Resize(int width, int height) {
if (width <= 0 || height <= 0 || m_swapChain == nullptr) {
return;
bool D3D12WindowRenderer::Resize(int width, int height) {
if (!m_presenter.Resize(width, height)) {
m_lastError = m_presenter.GetLastError();
return false;
}
WaitForGpuIdle();
ReleaseBackBufferViews();
m_swapChain->Resize(static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height));
m_width = width;
m_height = height;
RecreateBackBufferViews();
m_activeBackBufferIndex = m_presenter.GetCurrentBackBufferIndex();
m_lastError = m_presenter.GetLastError();
if (!m_lastError.empty()) {
return true;
}
m_lastError.clear();
return true;
}
bool D3D12WindowRenderer::BeginFrame() {
D3D12CommandQueue* d3d12Queue = GetD3D12CommandQueue();
D3D12CommandList* d3d12CommandList = GetD3D12CommandList();
if (m_swapChain == nullptr ||
d3d12Queue == nullptr ||
d3d12CommandList == nullptr ||
m_srvHeap == nullptr) {
if (m_presenter.GetSwapChain() == nullptr) {
m_lastError = "BeginFrame requires an initialized swap chain.";
return false;
}
d3d12Queue->WaitForPreviousFrame();
m_commandList->Reset();
m_activeBackBufferIndex = m_presenter.GetCurrentBackBufferIndex();
if (!m_hostDevice.BeginFrame(m_activeBackBufferIndex)) {
m_lastError = m_hostDevice.GetLastError();
return false;
}
m_lastError.clear();
return true;
}
bool D3D12WindowRenderer::PreparePresentSurface() {
::XCEngine::Rendering::RenderContext renderContext = GetRenderContext();
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
m_lastError = "PreparePresentSurface requires a valid render context.";
return false;
}
const bool prepared = m_presenter.PreparePresentSurface(renderContext);
m_lastError = prepared ? std::string() : m_presenter.GetLastError();
return prepared;
}
bool D3D12WindowRenderer::SubmitFrame(bool presentSwapChain) {
if (m_commandList == nullptr || m_commandQueue == nullptr) {
if (presentSwapChain && m_presenter.GetSwapChain() == nullptr) {
m_lastError = "SubmitFrame requested present without a swap chain.";
return false;
}
if (presentSwapChain && m_swapChain == nullptr) {
if (!m_hostDevice.SubmitFrame(m_activeBackBufferIndex)) {
m_lastError = m_hostDevice.GetLastError();
return false;
}
m_commandList->Close();
void* commandLists[] = { m_commandList };
m_commandQueue->ExecuteCommandLists(1, commandLists);
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() {
if (!m_hostDevice.SignalFrameCompletion(m_activeBackBufferIndex)) {
m_lastError = m_hostDevice.GetLastError();
return false;
}
m_lastError.clear();
return true;
}
bool D3D12WindowRenderer::PresentFrame() {
const bool presented = m_presenter.PresentFrame();
m_lastError = presented ? std::string() : m_presenter.GetLastError();
return presented;
}
ID3D12Device* D3D12WindowRenderer::GetDevice() const {
const D3D12Device* device = GetD3D12Device();
return device != nullptr ? device->GetDevice() : nullptr;
}
ID3D12DescriptorHeap* D3D12WindowRenderer::GetSrvHeap() const {
return m_srvHeap != nullptr ? m_srvHeap->GetDescriptorHeap() : nullptr;
return m_hostDevice.GetDevice();
}
ID3D12CommandQueue* D3D12WindowRenderer::GetCommandQueue() const {
const D3D12CommandQueue* queue = GetD3D12CommandQueue();
return queue != nullptr ? queue->GetCommandQueue() : nullptr;
return m_hostDevice.GetCommandQueue();
}
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;
const std::string& D3D12WindowRenderer::GetLastError() const {
return m_lastError;
}
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;
}
return m_presenter.GetCurrentRenderSurface();
}
const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
if (backBufferIndex >= m_backBufferSurfaces.size()) {
return nullptr;
}
const D3D12Texture* D3D12WindowRenderer::GetCurrentBackBufferTexture() const {
return m_presenter.GetCurrentBackBufferTexture();
}
return &m_backBufferSurfaces[backBufferIndex];
const D3D12Texture* D3D12WindowRenderer::GetBackBufferTexture(std::uint32_t index) const {
return m_presenter.GetBackBufferTexture(index);
}
std::uint32_t D3D12WindowRenderer::GetBackBufferCount() const {
return m_presenter.GetBackBufferCount();
}
::XCEngine::Rendering::RenderContext D3D12WindowRenderer::GetRenderContext() const {
::XCEngine::Rendering::RenderContext context = {};
context.device = m_device;
context.commandList = m_commandList;
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 {
return m_commandList != nullptr ? static_cast<D3D12CommandList*>(m_commandList) : nullptr;
}
D3D12SwapChain* D3D12WindowRenderer::GetD3D12SwapChain() const {
return m_swapChain != nullptr ? static_cast<D3D12SwapChain*>(m_swapChain) : 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;
}
}
void D3D12WindowRenderer::WaitForGpuIdle() {
if (m_commandQueue != nullptr) {
m_commandQueue->WaitForIdle();
}
}
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) {
m_backBufferViews[backBufferIndex] = m_device->CreateRenderTargetView(
&d3d12SwapChain->GetBackBuffer(backBufferIndex),
viewDesc);
if (m_backBufferViews[backBufferIndex] == nullptr) {
ReleaseBackBufferViews();
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

View File

@@ -4,90 +4,50 @@
#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 <vector>
#include <string>
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();
void Resize(int width, int height);
bool Resize(int width, int height);
bool BeginFrame();
bool PreparePresentSurface();
bool SubmitFrame(bool presentSwapChain);
bool SignalFrameCompletion();
bool PresentFrame();
ID3D12Device* GetDevice() const;
ID3D12DescriptorHeap* GetSrvHeap() const;
ID3D12CommandQueue* GetCommandQueue() 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;
const std::string& GetLastError() const;
::XCEngine::RHI::RHIDevice* GetRHIDevice() 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;
::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;
void AllocateShaderResourceDescriptorInternal(
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
void FreeShaderResourceDescriptorInternal(
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle);
void WaitForGpuIdle();
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;
::XCEngine::RHI::RHICommandList* m_commandList = nullptr;
::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 = {};
UINT m_srvDescriptorSize = 0;
D3D12HostDevice m_hostDevice = {};
D3D12WindowSwapChainPresenter m_presenter = {};
std::uint32_t m_activeBackBufferIndex = 0u;
std::string m_lastError = {};
};
} // namespace XCEngine::UI::Editor::Host

View 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

View 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

View File

@@ -0,0 +1,137 @@
#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;
};
class HostRuntimeState {
public:
void Reset() {
m_windowDpi = 96u;
m_inInteractiveResize = false;
m_borderlessResizeState = {};
m_predictedClientPixelSize = {};
}
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;
}
private:
UINT m_windowDpi = 96u;
bool m_inInteractiveResize = false;
BorderlessWindowResizeState m_borderlessResizeState = {};
PredictedClientPixelSize m_predictedClientPixelSize = {};
};
} // namespace XCEngine::UI::Editor::Host

View File

@@ -1,6 +1,7 @@
#include "NativeRenderer.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <filesystem>
#include <memory>
@@ -53,6 +54,41 @@ float ResolveStrokePixelOffset(float thickness) {
return std::fmod(roundedThickness, 2.0f) == 1.0f ? 0.5f : 0.0f;
}
D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties(
DXGI_FORMAT format,
D2D1_BITMAP_OPTIONS options,
D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_IGNORE) {
return D2D1::BitmapProperties1(
options,
D2D1::PixelFormat(format, alphaMode),
kBaseDpi,
kBaseDpi);
}
bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) {
return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView &&
texture.resourceHandle != 0u;
}
bool 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);
}
}
return true;
}
} // namespace
bool NativeRenderer::Initialize(HWND hwnd) {
@@ -64,7 +100,15 @@ bool NativeRenderer::Initialize(HWND hwnd) {
}
m_hwnd = hwnd;
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_d2dFactory.ReleaseAndGetAddressOf());
D2D1_FACTORY_OPTIONS factoryOptions = {};
#ifdef _DEBUG
factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory1),
&factoryOptions,
reinterpret_cast<void**>(m_d2dFactory.ReleaseAndGetAddressOf()));
if (FAILED(hr)) {
m_lastRenderError = HrToString("D2D1CreateFactory", hr);
Shutdown();
@@ -82,10 +126,11 @@ bool NativeRenderer::Initialize(HWND hwnd) {
}
m_lastRenderError.clear();
return EnsureRenderTarget();
return true;
}
void NativeRenderer::Shutdown() {
DetachWindowRenderer();
while (!m_liveTextures.empty()) {
auto it = m_liveTextures.begin();
delete *it;
@@ -126,6 +171,50 @@ void NativeRenderer::Resize(UINT width, UINT height) {
}
}
bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) {
if (m_windowRenderer != &windowRenderer) {
ReleaseWindowRendererInterop();
m_windowRenderer = &windowRenderer;
}
if (!EnsureWindowRendererInterop()) {
return false;
}
// Keep a single real window presentation path. The hwnd render target is
// fallback-only and should not stay alive while D3D11On12 interop is healthy.
DiscardRenderTarget();
if (m_windowInterop.HasBackBufferTargets()) {
return true;
}
return m_windowInterop.RebuildBackBufferTargets();
}
void NativeRenderer::DetachWindowRenderer() {
ReleaseWindowRendererInterop();
m_windowRenderer = nullptr;
}
void NativeRenderer::ReleaseWindowRendererBackBufferTargets() {
m_windowInterop.ReleaseBackBufferTargets();
}
bool NativeRenderer::RebuildWindowRendererBackBufferTargets() {
if (!EnsureWindowRendererInterop()) {
return false;
}
DiscardRenderTarget();
ReleaseWindowRendererBackBufferTargets();
return m_windowInterop.RebuildBackBufferTargets();
}
bool NativeRenderer::HasAttachedWindowRenderer() const {
return m_windowInterop.HasAttachedWindowRenderer();
}
bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) {
if (!EnsureRenderTarget()) {
if (m_lastRenderError.empty()) {
@@ -151,6 +240,120 @@ bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) {
return true;
}
bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData) {
if (!EnsureWindowRendererInterop()) {
if (m_lastRenderError.empty()) {
m_lastRenderError = "Window renderer interop is not available.";
}
return false;
}
if (!m_windowInterop.HasBackBufferTargets() &&
!m_windowInterop.RebuildBackBufferTargets()) {
if (m_lastRenderError.empty()) {
m_lastRenderError = "Window renderer back buffer interop targets are unavailable.";
}
return false;
}
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;
}
if (!m_windowRenderer->PreparePresentSurface()) {
m_lastRenderError = "Failed to prepare the D3D12 present surface: " +
m_windowRenderer->GetLastError();
return false;
}
if (!m_windowRenderer->SubmitFrame(false)) {
m_lastRenderError = "Failed to submit the D3D12 frame before UI composition.";
return false;
}
if (!m_windowInterop.PrepareSourceTextures(drawData)) {
ID3D11Resource* backBufferResource =
m_windowInterop.GetWrappedBackBufferResource(backBufferIndex);
if (backBufferResource != nullptr) {
d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u);
d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u);
}
d3d11DeviceContext->Flush();
m_windowInterop.ClearSourceTextures();
const bool signaled = m_windowRenderer->SignalFrameCompletion();
ReleaseWindowRendererInterop();
if (!signaled) {
m_lastRenderError =
"Failed to signal D3D12 frame completion after interop preparation failed.";
}
return false;
}
std::vector<ID3D11Resource*> acquiredResources = {};
m_windowInterop.BuildAcquiredResources(backBufferIndex, acquiredResources);
if (acquiredResources.empty()) {
m_lastRenderError = "No wrapped interop resources were prepared for UI composition.";
return false;
}
d3d11On12Device->AcquireWrappedResources(
acquiredResources.data(),
static_cast<UINT>(acquiredResources.size()));
d2dDeviceContext->SetTarget(m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex));
const bool rendered = RenderToTarget(*d2dDeviceContext, *interopBrush, drawData);
const HRESULT hr = d2dDeviceContext->EndDraw();
d3d11On12Device->ReleaseWrappedResources(
acquiredResources.data(),
static_cast<UINT>(acquiredResources.size()));
d3d11DeviceContext->Flush();
d2dDeviceContext->SetTarget(nullptr);
m_windowInterop.ClearSourceTextures();
if (!rendered || FAILED(hr)) {
m_lastRenderError = FAILED(hr)
? HrToString("ID2D1DeviceContext::EndDraw", hr)
: "RenderToTarget failed during D3D11On12 composition.";
const bool signaled = m_windowRenderer->SignalFrameCompletion();
ReleaseWindowRendererInterop();
if (!signaled) {
m_lastRenderError =
"Failed to signal D3D12 frame completion after UI composition failed.";
}
return false;
}
if (!m_windowRenderer->SignalFrameCompletion()) {
m_lastRenderError = "Failed to signal D3D12 frame completion after UI composition.";
ReleaseWindowRendererInterop();
return false;
}
if (!m_windowRenderer->PresentFrame()) {
m_lastRenderError = "Failed to present the D3D12 swap chain.";
ReleaseWindowRendererInterop();
return false;
}
m_lastRenderError.clear();
return true;
}
const std::string& NativeRenderer::GetLastRenderError() const {
return m_lastRenderError;
}
@@ -388,6 +591,29 @@ bool NativeRenderer::CaptureToPng(
return true;
}
bool NativeRenderer::EnsureWindowRendererInterop() {
if (m_windowRenderer == nullptr) {
m_lastRenderError = "EnsureWindowRendererInterop requires an attached D3D12 window renderer.";
return false;
}
if (m_d2dFactory == nullptr || m_dwriteFactory == nullptr) {
m_lastRenderError = "EnsureWindowRendererInterop requires initialized D2D and DWrite factories.";
return false;
}
const bool attached = m_windowInterop.Attach(*m_windowRenderer, *m_d2dFactory.Get());
if (!attached) {
m_lastRenderError = m_windowInterop.GetLastError();
} else {
m_lastRenderError.clear();
}
return attached;
}
void NativeRenderer::ReleaseWindowRendererInterop() {
m_windowInterop.Detach();
}
bool NativeRenderer::EnsureRenderTarget() {
if (!m_hwnd || !m_d2dFactory || !m_dwriteFactory) {
m_lastRenderError = "EnsureRenderTarget requires hwnd, D2D factory, and DWrite factory.";
@@ -601,6 +827,12 @@ bool NativeRenderer::ResolveTextureBitmap(
return true;
}
bool NativeRenderer::ResolveInteropBitmap(
const ::XCEngine::UI::UITextureHandle& texture,
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const {
return m_windowInterop.ResolveInteropBitmap(texture, outBitmap);
}
bool NativeRenderer::RenderToTarget(
ID2D1RenderTarget& renderTarget,
ID2D1SolidColorBrush& solidBrush,
@@ -798,23 +1030,33 @@ void NativeRenderer::RenderCommand(
break;
}
auto* texture = reinterpret_cast<NativeTextureResource*>(command.texture.nativeHandle);
if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) {
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
renderTarget.DrawRectangle(rect, &solidBrush, 1.0f);
break;
}
Microsoft::WRL::ComPtr<ID2D1Bitmap> bitmap;
if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) {
break;
if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) {
if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) {
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
renderTarget.DrawRectangle(rect, &solidBrush, 1.0f);
break;
}
} else {
auto* texture = reinterpret_cast<NativeTextureResource*>(command.texture.nativeHandle);
if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) {
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
renderTarget.DrawRectangle(rect, &solidBrush, 1.0f);
break;
}
if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) {
break;
}
}
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
const float sourceLeft = static_cast<float>(texture->width) * std::clamp(command.uvMin.x, 0.0f, 1.0f);
const float sourceTop = static_cast<float>(texture->height) * std::clamp(command.uvMin.y, 0.0f, 1.0f);
const float sourceRight = static_cast<float>(texture->width) * std::clamp(command.uvMax.x, 0.0f, 1.0f);
const float sourceBottom = static_cast<float>(texture->height) * std::clamp(command.uvMax.y, 0.0f, 1.0f);
const float sourceWidth = static_cast<float>(command.texture.width);
const float sourceHeight = static_cast<float>(command.texture.height);
const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f);
const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f);
const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f);
const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f);
renderTarget.DrawBitmap(
bitmap.Get(),
rect,

View File

@@ -4,12 +4,18 @@
#define NOMINMAX
#endif
#include "D3D12WindowInteropContext.h"
#include "D3D12WindowRenderer.h"
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <XCEngine/UI/DrawData.h>
#include <d2d1.h>
#include <d2d1_1.h>
#include <d3d11_4.h>
#include <d3d11on12.h>
#include <dwrite.h>
#include <dxgi1_2.h>
#include <wincodec.h>
#include <windows.h>
#include <wrl/client.h>
@@ -30,7 +36,13 @@ public:
void SetDpiScale(float dpiScale);
float GetDpiScale() const;
void Resize(UINT width, UINT height);
bool AttachWindowRenderer(D3D12WindowRenderer& windowRenderer);
void DetachWindowRenderer();
void ReleaseWindowRendererBackBufferTargets();
bool RebuildWindowRendererBackBufferTargets();
bool HasAttachedWindowRenderer() const;
bool Render(const ::XCEngine::UI::UIDrawData& drawData);
bool RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData);
const std::string& GetLastRenderError() const;
bool LoadTextureFromFile(
const std::filesystem::path& path,
@@ -47,15 +59,6 @@ public:
std::string& outError);
private:
bool EnsureRenderTarget();
bool EnsureWicFactory(std::string& outError);
void DiscardRenderTarget();
bool CreateDeviceResources();
void InvalidateCachedTextureBitmaps(const ID2D1RenderTarget* renderTarget);
bool RenderToTarget(
ID2D1RenderTarget& renderTarget,
ID2D1SolidColorBrush& solidBrush,
const ::XCEngine::UI::UIDrawData& drawData);
struct NativeTextureResource {
std::vector<std::uint8_t> pixels = {};
Microsoft::WRL::ComPtr<ID2D1Bitmap> cachedBitmap = {};
@@ -63,6 +66,18 @@ private:
UINT width = 0u;
UINT height = 0u;
};
bool EnsureRenderTarget();
bool EnsureWindowRendererInterop();
bool EnsureWicFactory(std::string& outError);
void DiscardRenderTarget();
bool CreateDeviceResources();
void ReleaseWindowRendererInterop();
void InvalidateCachedTextureBitmaps(const ID2D1RenderTarget* renderTarget);
bool RenderToTarget(
ID2D1RenderTarget& renderTarget,
ID2D1SolidColorBrush& solidBrush,
const ::XCEngine::UI::UIDrawData& drawData);
bool DecodeTextureFile(
const std::filesystem::path& path,
NativeTextureResource& outTexture,
@@ -71,6 +86,9 @@ private:
ID2D1RenderTarget& renderTarget,
NativeTextureResource& texture,
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap);
bool ResolveInteropBitmap(
const ::XCEngine::UI::UITextureHandle& texture,
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const;
void RenderCommand(
ID2D1RenderTarget& renderTarget,
ID2D1SolidColorBrush& solidBrush,
@@ -82,13 +100,15 @@ private:
static std::wstring Utf8ToWide(std::string_view text);
HWND m_hwnd = nullptr;
Microsoft::WRL::ComPtr<ID2D1Factory> m_d2dFactory;
D3D12WindowRenderer* m_windowRenderer = nullptr;
Microsoft::WRL::ComPtr<ID2D1Factory1> m_d2dFactory;
Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory;
Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory;
Microsoft::WRL::ComPtr<ID2D1HwndRenderTarget> m_renderTarget;
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_solidBrush;
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;

View File

@@ -0,0 +1,121 @@
#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_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_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

View 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

View File

@@ -429,11 +429,10 @@ UIEditorWorkspacePanelPresentationModel BuildViewportPresentation(
presentation.panelId = std::move(panelId);
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
presentation.viewportShellModel.spec.chrome.title = std::move(title);
presentation.viewportShellModel.spec.chrome.subtitle = std::move(subtitle);
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.statusText =
"Viewport request chain is active.";
presentation.viewportShellModel.spec.chrome.subtitle = {};
presentation.viewportShellModel.spec.chrome.showTopBar = false;
presentation.viewportShellModel.spec.chrome.showBottomBar = false;
presentation.viewportShellModel.frame.statusText.clear();
return presentation;
}
@@ -448,8 +447,8 @@ UIEditorShellInteractionDefinition BuildBaseShellDefinition() {
definition.statusSegments = {};
definition.workspacePresentations = {
BuildHostedContentPresentation("hierarchy"),
BuildViewportPresentation("scene", "Scene", "New Editor viewport shell"),
BuildViewportPresentation("game", "Game", "New Editor viewport shell"),
BuildViewportPresentation("scene", "Scene", {}),
BuildViewportPresentation("game", "Game", {}),
BuildHostedContentPresentation("inspector"),
BuildHostedContentPresentation("console"),
BuildHostedContentPresentation("project")

View File

@@ -8,36 +8,24 @@ namespace {
using ::XCEngine::RHI::ResourceStates;
std::string BuildViewportPendingStatus(ProductViewportKind kind) {
return kind == ProductViewportKind::Scene
? "Scene viewport host pending D3D12 presenter integration."
: "Game viewport host pending D3D12 presenter integration.";
}
std::string BuildViewportReadyStatus(ProductViewportKind kind) {
return kind == ProductViewportKind::Scene
? "Scene viewport render target ready; presenter still uses NativeRenderer."
: "Game viewport render target ready; presenter still uses NativeRenderer.";
}
std::string BuildViewportPresentedStatus(ProductViewportKind kind) {
return kind == ProductViewportKind::Scene
? "Scene viewport frame ready."
: "Game viewport frame ready.";
}
} // namespace
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() {
@@ -53,6 +41,7 @@ void ProductViewportHostService::Shutdown() {
DestroyViewportEntry(entry);
}
m_textureDescriptorAllocator.Shutdown();
m_windowRenderer = nullptr;
m_device = nullptr;
m_surfacePresentationEnabled = false;
@@ -81,23 +70,17 @@ ProductViewportFrame ProductViewportHostService::RequestViewport(
: 0u;
if (!entry.requestedThisFrame) {
entry.statusText = "Viewport is waiting for a visible surface.";
return BuildFrame(entry, requestedSize);
}
if (m_windowRenderer == nullptr || m_device == nullptr) {
entry.statusText = BuildViewportPendingStatus(kind);
return BuildFrame(entry, requestedSize);
}
if (!EnsureViewportResources(entry)) {
entry.statusText = "Failed to create viewport render targets.";
return BuildFrame(entry, requestedSize);
}
entry.statusText = m_surfacePresentationEnabled
? BuildViewportPresentedStatus(kind)
: BuildViewportReadyStatus(kind);
return BuildFrame(entry, requestedSize);
}
@@ -119,9 +102,6 @@ void ProductViewportHostService::RenderRequestedViewports(
}
entry.renderedThisFrame = true;
entry.statusText = m_surfacePresentationEnabled
? BuildViewportPresentedStatus(entry.kind)
: BuildViewportReadyStatus(entry.kind);
}
}
@@ -140,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 = {};
}
@@ -162,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);
}

View File

@@ -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 = {};
};

View 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

View File

@@ -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

View File

@@ -36,33 +36,62 @@ void ApplyViewportFrameToPresentation(
presentation.viewportShellModel.frame.statusText = viewportFrame.statusText;
}
std::vector<UIEditorWorkspacePanelPresentationModel> BuildWorkspacePresentations(
const UIEditorShellInteractionDefinition& definition,
const UIEditorShellInteractionRequest& shellRequest,
ProductViewportHostService& viewportHostService) {
std::vector<UIEditorWorkspacePanelPresentationModel> presentations =
definition.workspacePresentations;
for (UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
if (presentation.kind != UIEditorPanelPresentationKind::ViewportShell ||
!IsProductViewportPanel(presentation.panelId)) {
continue;
}
void ApplyViewportFrameToShellModel(
const ProductViewportFrame& viewportFrame,
UIEditorViewportShellModel& shellModel) {
shellModel.frame.texture = viewportFrame.texture;
shellModel.frame.requestedSize = viewportFrame.requestedSize;
shellModel.frame.presentedSize = viewportFrame.renderSize;
shellModel.frame.hasTexture = viewportFrame.hasTexture;
shellModel.frame.statusText = viewportFrame.statusText;
}
const UIEditorWorkspaceViewportComposeRequest* viewportRequest =
FindUIEditorWorkspaceViewportPresentationRequest(
shellRequest.shellRequest.workspaceRequest,
presentation.panelId);
const ::XCEngine::UI::UISize requestedSize =
viewportRequest != nullptr
? viewportRequest->viewportShellRequest.requestedViewportSize
: ::XCEngine::UI::UISize();
ApplyViewportFrameToPresentation(
viewportHostService.RequestViewport(
ResolveProductViewportKind(presentation.panelId),
requestedSize),
presentation);
UIEditorWorkspacePanelPresentationModel* FindMutableWorkspacePresentation(
std::vector<UIEditorWorkspacePanelPresentationModel>& presentations,
std::string_view panelId) {
for (UIEditorWorkspacePanelPresentationModel& presentation : presentations) {
if (presentation.panelId == panelId) {
return &presentation;
}
}
return presentations;
return nullptr;
}
void ApplyViewportFramesToShellFrame(
UIEditorShellInteractionFrame& shellFrame,
ProductViewportHostService& viewportHostService) {
auto applyToViewportFrames =
[&](std::vector<UIEditorWorkspaceViewportComposeFrame>& viewportFrames) {
for (UIEditorWorkspaceViewportComposeFrame& viewportComposeFrame : viewportFrames) {
if (!IsProductViewportPanel(viewportComposeFrame.panelId)) {
continue;
}
const ProductViewportFrame viewportFrame =
viewportHostService.RequestViewport(
ResolveProductViewportKind(viewportComposeFrame.panelId),
viewportComposeFrame.viewportShellFrame.requestedViewportSize);
ApplyViewportFrameToShellModel(
viewportFrame,
viewportComposeFrame.viewportShellModel);
if (UIEditorWorkspacePanelPresentationModel* presentation =
FindMutableWorkspacePresentation(
shellFrame.model.workspacePresentations,
viewportComposeFrame.panelId);
presentation != nullptr) {
ApplyViewportFrameToPresentation(viewportFrame, *presentation);
}
}
};
applyToViewportFrames(shellFrame.workspaceInteractionFrame.composeFrame.viewportFrames);
applyToViewportFrames(shellFrame.shellFrame.workspaceFrame.viewportFrames);
}
std::vector<UIEditorWorkspacePanelPresentationModel> BuildWorkspacePresentations(
const UIEditorShellInteractionDefinition& definition) {
return definition.workspacePresentations;
}
UIEditorShellComposeModel BuildShellComposeModelFromFrame(
@@ -168,18 +197,7 @@ void ProductEditorWorkspace::Update(
UIEditorShellInteractionDefinition definition =
context.BuildShellDefinition(captureText);
m_viewportHostService.BeginFrame();
const UIEditorShellInteractionRequest shellRequest =
ResolveUIEditorShellInteractionRequest(
bounds,
context.GetWorkspaceController(),
definition,
m_shellInteractionState,
metrics,
context.GetShellServices());
definition.workspacePresentations = BuildWorkspacePresentations(
definition,
shellRequest,
m_viewportHostService);
definition.workspacePresentations = BuildWorkspacePresentations(definition);
const std::vector<UIInputEvent> hostedContentEvents = inputEvents;
const std::vector<UIInputEvent> shellEvents =
HasHostedContentCapture()
@@ -194,6 +212,7 @@ void ProductEditorWorkspace::Update(
shellEvents,
context.GetShellServices(),
metrics);
ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService);
context.SyncSessionFromWorkspace();
context.UpdateStatusFromShellResult(m_shellFrame.result);

View File

@@ -19,5 +19,6 @@ void AppendUIEditorCrashTrace(
std::filesystem::path GetUIEditorRuntimeTracePath();
std::filesystem::path GetUIEditorCrashTracePath();
bool IsUIEditorRuntimeTraceInitialized();
} // namespace XCEngine::UI::Editor

View File

@@ -17,6 +17,8 @@ std::mutex g_traceMutex = {};
std::filesystem::path g_logRoot = {};
std::filesystem::path g_runtimeTracePath = {};
std::filesystem::path g_crashTracePath = {};
std::ofstream g_runtimeTraceStream = {};
std::ofstream g_crashTraceStream = {};
bool g_traceInitialized = false;
std::string BuildTimestampString() {
@@ -40,16 +42,9 @@ std::string BuildTimestampString() {
}
void AppendTraceLine(
const std::filesystem::path& path,
std::ofstream& stream,
std::string_view channel,
std::string_view message) {
if (path.empty()) {
return;
}
std::error_code errorCode = {};
std::filesystem::create_directories(path.parent_path(), errorCode);
std::ofstream stream(path, std::ios::out | std::ios::app);
if (!stream.is_open()) {
return;
}
@@ -70,11 +65,20 @@ void InitializeUIEditorRuntimeTrace(const std::filesystem::path& logRoot) {
g_logRoot = logRoot.lexically_normal();
g_runtimeTracePath = (g_logRoot / "runtime.log").lexically_normal();
g_crashTracePath = (g_logRoot / "crash.log").lexically_normal();
g_traceInitialized = true;
std::error_code errorCode = {};
std::filesystem::create_directories(g_logRoot, errorCode);
AppendTraceLine(g_runtimeTracePath, "trace", "trace session started");
g_runtimeTraceStream.close();
g_crashTraceStream.close();
g_runtimeTraceStream.open(g_runtimeTracePath, std::ios::out | std::ios::app);
g_crashTraceStream.open(g_crashTracePath, std::ios::out | std::ios::app);
g_traceInitialized = g_runtimeTraceStream.is_open() && g_crashTraceStream.is_open();
if (!g_traceInitialized) {
return;
}
AppendTraceLine(g_runtimeTraceStream, "trace", "trace session started");
g_runtimeTraceStream.flush();
}
void ShutdownUIEditorRuntimeTrace() {
@@ -83,7 +87,11 @@ void ShutdownUIEditorRuntimeTrace() {
return;
}
AppendTraceLine(g_runtimeTracePath, "trace", "trace session ended");
AppendTraceLine(g_runtimeTraceStream, "trace", "trace session ended");
g_runtimeTraceStream.flush();
g_crashTraceStream.flush();
g_runtimeTraceStream.close();
g_crashTraceStream.close();
g_traceInitialized = false;
}
@@ -95,7 +103,7 @@ void AppendUIEditorRuntimeTrace(
return;
}
AppendTraceLine(g_runtimeTracePath, channel, message);
AppendTraceLine(g_runtimeTraceStream, channel, message);
}
void AppendUIEditorCrashTrace(
@@ -113,8 +121,10 @@ void AppendUIEditorCrashTrace(
"Unhandled exception code=0x%08X address=%p",
exceptionCode,
exceptionAddress);
AppendTraceLine(g_crashTracePath, "crash", buffer);
AppendTraceLine(g_runtimeTracePath, "crash", buffer);
AppendTraceLine(g_crashTraceStream, "crash", buffer);
AppendTraceLine(g_runtimeTraceStream, "crash", buffer);
g_crashTraceStream.flush();
g_runtimeTraceStream.flush();
}
std::filesystem::path GetUIEditorRuntimeTracePath() {
@@ -127,4 +137,9 @@ std::filesystem::path GetUIEditorCrashTracePath() {
return g_crashTracePath;
}
bool IsUIEditorRuntimeTraceInitialized() {
std::lock_guard lock(g_traceMutex);
return g_traceInitialized;
}
} // namespace XCEngine::UI::Editor

View File

@@ -55,34 +55,6 @@ UISize ResolveFrameAspectSize(const UIEditorViewportSlotFrame& frame, const UISi
return fallback;
}
UIRect FitRectToAspect(const UIRect& container, const UISize& size) {
if (!HasArea(container) || size.width <= 0.0f || size.height <= 0.0f) {
return container;
}
const float containerAspect = container.width / container.height;
const float frameAspect = size.width / size.height;
if (frameAspect <= 0.0f) {
return container;
}
if (frameAspect >= containerAspect) {
const float fittedHeight = container.width / frameAspect;
return UIRect(
container.x,
container.y + (container.height - fittedHeight) * 0.5f,
container.width,
fittedHeight);
}
const float fittedWidth = container.height * frameAspect;
return UIRect(
container.x + (container.width - fittedWidth) * 0.5f,
container.y,
fittedWidth,
container.height);
}
UIColor ResolveToolFillColor(
const UIEditorViewportSlotToolItem& item,
bool hovered,
@@ -199,11 +171,7 @@ UIEditorViewportSlotLayout BuildUIEditorViewportSlotLayout(
layout.inputRect = InsetRect(layout.surfaceRect, metrics.surfaceInset);
layout.requestedSurfaceSize =
UISize(layout.inputRect.width, layout.inputRect.height);
layout.textureRect = frame.hasTexture
? FitRectToAspect(
layout.inputRect,
ResolveFrameAspectSize(frame, layout.requestedSurfaceSize))
: layout.inputRect;
layout.textureRect = layout.inputRect;
if (!layout.hasTopBar) {
return layout;
@@ -437,34 +405,8 @@ void AppendUIEditorViewportSlotForeground(
}
}
const UISize frameSize = ResolveFrameAspectSize(frame, layout.requestedSurfaceSize);
if (frame.hasTexture && frame.texture.IsValid()) {
drawList.AddImage(layout.textureRect, frame.texture, palette.imageTint);
drawList.AddText(
UIPoint(layout.inputRect.x + 14.0f, layout.inputRect.y + 14.0f),
"Texture " +
std::to_string(static_cast<int>(frameSize.width)) +
"x" +
std::to_string(static_cast<int>(frameSize.height)),
palette.textMuted,
12.0f);
} else {
const std::string statusText = frame.statusText.empty()
? std::string("Viewport is waiting for frame")
: frame.statusText;
drawList.AddText(
UIPoint(layout.inputRect.x + 16.0f, layout.inputRect.y + 18.0f),
statusText,
palette.textPrimary,
14.0f);
drawList.AddText(
UIPoint(layout.inputRect.x + 16.0f, layout.inputRect.y + 42.0f),
"Requested surface: " +
std::to_string(static_cast<int>(layout.requestedSurfaceSize.width)) +
"x" +
std::to_string(static_cast<int>(layout.requestedSurfaceSize.height)),
palette.textMuted,
12.0f);
}
if (layout.hasBottomBar) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,5 @@
fileFormatVersion: 1
guid: e632e900c73bd6cdc4c51951e1ab02b6
folderAsset: false
importer: TextureImporter
importerVersion: 9

Some files were not shown because too many files have changed in this diff Show More