Compare commits
36 Commits
0f60f0f657
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bd375cd24 | |||
| c0d62dc749 | |||
| c98b41f6f4 | |||
| 72d09a1c49 | |||
| ba91e0f5dd | |||
| 9064c2f5f2 | |||
| 5797a75619 | |||
| e83f911aef | |||
| dd2299c8b0 | |||
| b8d29e39f6 | |||
| 72914b3865 | |||
| e6950fa704 | |||
| 21b0530f7b | |||
| c3d443eb85 | |||
| d705cc839b | |||
| 0d6b8bf7d8 | |||
| 4362008b39 | |||
| 712f99e723 | |||
| 48daaa1bd0 | |||
| e462f7d6f7 | |||
| 5b89c2bb76 | |||
| f3fc34898a | |||
| d2140bf5cc | |||
| a0d5e84516 | |||
| 8ba05216fb | |||
| 0cc3d6da46 | |||
| adb6fe4659 | |||
| 95edf0435f | |||
| 82c55b3999 | |||
| 00875e0c90 | |||
| b7428b0ef1 | |||
| 1d6f2e290d | |||
| 2ee74e7761 | |||
| 64212a53c7 | |||
| 6f876678f5 | |||
| 2326857a43 |
158
MVS/3DGS-D3D12/CMakeLists.txt
Normal file
158
MVS/3DGS-D3D12/CMakeLists.txt
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(XC3DGSD3D12MVS LANGUAGES CXX)
|
||||||
|
|
||||||
|
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")
|
||||||
|
set(XCENGINE_INCLUDE_DIR "${XCENGINE_ROOT}/engine/include")
|
||||||
|
set(XCENGINE_LIBRARY_DEBUG "${XCENGINE_BUILD_DIR}/engine/Debug/XCEngine.lib")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${XCENGINE_LIBRARY_DEBUG}")
|
||||||
|
message(FATAL_ERROR "Prebuilt XCEngine library was not found: ${XCENGINE_LIBRARY_DEBUG}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(XCEngine STATIC IMPORTED GLOBAL)
|
||||||
|
set_target_properties(XCEngine PROPERTIES
|
||||||
|
IMPORTED_CONFIGURATIONS "Debug;Release;RelWithDebInfo;MinSizeRel"
|
||||||
|
IMPORTED_LOCATION_DEBUG "${XCENGINE_LIBRARY_DEBUG}"
|
||||||
|
IMPORTED_LOCATION_RELEASE "${XCENGINE_LIBRARY_DEBUG}"
|
||||||
|
IMPORTED_LOCATION_RELWITHDEBINFO "${XCENGINE_LIBRARY_DEBUG}"
|
||||||
|
IMPORTED_LOCATION_MINSIZEREL "${XCENGINE_LIBRARY_DEBUG}"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(xc_3dgs_d3d12_mvs
|
||||||
|
WIN32
|
||||||
|
src/main.cpp
|
||||||
|
src/App.cpp
|
||||||
|
src/GaussianPlyLoader.cpp
|
||||||
|
include/XC3DGSD3D12/App.h
|
||||||
|
include/XC3DGSD3D12/GaussianPlyLoader.h
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(xc_3dgs_d3d12_mvs PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
${XCENGINE_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(xc_3dgs_d3d12_mvs PRIVATE
|
||||||
|
UNICODE
|
||||||
|
_UNICODE
|
||||||
|
NOMINMAX
|
||||||
|
WIN32_LEAN_AND_MEAN
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(xc_3dgs_d3d12_mvs PRIVATE /utf-8)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(xc_3dgs_d3d12_mvs PRIVATE
|
||||||
|
XCEngine
|
||||||
|
d3d12
|
||||||
|
dxgi
|
||||||
|
dxguid
|
||||||
|
d3dcompiler
|
||||||
|
winmm
|
||||||
|
delayimp
|
||||||
|
bcrypt
|
||||||
|
opengl32
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(xc_3dgs_d3d12_mvs PROPERTIES
|
||||||
|
VS_DEBUGGER_WORKING_DIRECTORY "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/room.ply"
|
||||||
|
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/room.ply"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/shaders"
|
||||||
|
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
162
MVS/3DGS-D3D12/include/XC3DGSD3D12/App.h
Normal file
162
MVS/3DGS-D3D12/include/XC3DGSD3D12/App.h
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include "XC3DGSD3D12/GaussianPlyLoader.h"
|
||||||
|
#include "XCEngine/RHI/RHIEnums.h"
|
||||||
|
#include "XCEngine/RHI/RHITypes.h"
|
||||||
|
#include "XCEngine/RHI/D3D12/D3D12CommandAllocator.h"
|
||||||
|
#include "XCEngine/RHI/D3D12/D3D12Buffer.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/D3D12ResourceView.h"
|
||||||
|
#include "XCEngine/RHI/D3D12/D3D12SwapChain.h"
|
||||||
|
#include "XCEngine/RHI/D3D12/D3D12Texture.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHIDescriptorPool;
|
||||||
|
class RHIDescriptorSet;
|
||||||
|
class RHIPipelineLayout;
|
||||||
|
class RHIPipelineState;
|
||||||
|
} // namespace RHI
|
||||||
|
} // namespace XCEngine
|
||||||
|
|
||||||
|
namespace XC3DGSD3D12 {
|
||||||
|
|
||||||
|
struct PreparedSplatView {
|
||||||
|
float clipPosition[4] = {};
|
||||||
|
float axis1[2] = {};
|
||||||
|
float axis2[2] = {};
|
||||||
|
uint32_t packedColor[2] = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class App {
|
||||||
|
public:
|
||||||
|
App();
|
||||||
|
~App();
|
||||||
|
|
||||||
|
bool Initialize(HINSTANCE instance, int showCommand);
|
||||||
|
int Run();
|
||||||
|
void SetFrameLimit(unsigned int frameLimit);
|
||||||
|
void SetGaussianScenePath(std::wstring scenePath);
|
||||||
|
void SetSummaryPath(std::wstring summaryPath);
|
||||||
|
void SetScreenshotPath(std::wstring screenshotPath);
|
||||||
|
const std::wstring& GetLastErrorMessage() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int kBackBufferCount = 2;
|
||||||
|
static constexpr int kDefaultWidth = 1280;
|
||||||
|
static constexpr int kDefaultHeight = 720;
|
||||||
|
|
||||||
|
static LRESULT CALLBACK StaticWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||||
|
LRESULT WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
bool RegisterWindowClass(HINSTANCE instance);
|
||||||
|
bool CreateMainWindow(HINSTANCE instance, int showCommand);
|
||||||
|
bool LoadGaussianScene();
|
||||||
|
bool InitializeRhi();
|
||||||
|
bool InitializeGaussianGpuResources();
|
||||||
|
bool InitializePreparePassResources();
|
||||||
|
bool InitializeSortResources();
|
||||||
|
bool InitializeDebugDrawResources();
|
||||||
|
bool InitializeCompositeResources();
|
||||||
|
void ShutdownGaussianGpuResources();
|
||||||
|
void ShutdownPreparePassResources();
|
||||||
|
void ShutdownSortResources();
|
||||||
|
void ShutdownDebugDrawResources();
|
||||||
|
void ShutdownCompositeResources();
|
||||||
|
void Shutdown();
|
||||||
|
bool CaptureSortSnapshot();
|
||||||
|
bool CapturePass3HistogramDebug();
|
||||||
|
void RenderFrame(bool captureScreenshot);
|
||||||
|
|
||||||
|
HWND m_hwnd = nullptr;
|
||||||
|
HINSTANCE m_instance = nullptr;
|
||||||
|
int m_width = kDefaultWidth;
|
||||||
|
int m_height = kDefaultHeight;
|
||||||
|
bool m_running = false;
|
||||||
|
bool m_isInitialized = false;
|
||||||
|
bool m_hasRenderedAtLeastOneFrame = false;
|
||||||
|
unsigned int m_frameLimit = 0;
|
||||||
|
unsigned int m_renderedFrameCount = 0;
|
||||||
|
std::wstring m_gaussianScenePath = L"room.ply";
|
||||||
|
std::wstring m_summaryPath;
|
||||||
|
std::wstring m_screenshotPath = L"phase3_debug_points.ppm";
|
||||||
|
std::wstring m_sortKeySnapshotPath = L"phase3_sortkeys.txt";
|
||||||
|
std::wstring m_lastErrorMessage;
|
||||||
|
GaussianSplatRuntimeData m_gaussianSceneData;
|
||||||
|
XCEngine::RHI::D3D12Buffer m_gaussianPositionBuffer;
|
||||||
|
XCEngine::RHI::D3D12Buffer m_gaussianOtherBuffer;
|
||||||
|
XCEngine::RHI::D3D12Buffer m_gaussianShBuffer;
|
||||||
|
XCEngine::RHI::D3D12Texture m_gaussianColorTexture;
|
||||||
|
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianPositionView;
|
||||||
|
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianOtherView;
|
||||||
|
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianShView;
|
||||||
|
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianColorView;
|
||||||
|
std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>> m_gaussianUploadBuffers;
|
||||||
|
XCEngine::RHI::D3D12Buffer* m_preparedViewBuffer = nullptr;
|
||||||
|
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_preparedViewSrv;
|
||||||
|
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_preparedViewUav;
|
||||||
|
XCEngine::RHI::RHIPipelineLayout* m_preparePipelineLayout = nullptr;
|
||||||
|
XCEngine::RHI::RHIPipelineState* m_preparePipelineState = nullptr;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
XCEngine::RHI::D3D12SwapChain m_swapChain;
|
||||||
|
XCEngine::RHI::D3D12CommandAllocator m_commandAllocator;
|
||||||
|
XCEngine::RHI::D3D12CommandList m_commandList;
|
||||||
|
XCEngine::RHI::D3D12Texture m_depthStencil;
|
||||||
|
XCEngine::RHI::D3D12DescriptorHeap m_rtvHeap;
|
||||||
|
XCEngine::RHI::D3D12DescriptorHeap m_dsvHeap;
|
||||||
|
XCEngine::RHI::D3D12ResourceView m_rtvs[kBackBufferCount];
|
||||||
|
XCEngine::RHI::D3D12ResourceView m_dsv;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace XC3DGSD3D12
|
||||||
46
MVS/3DGS-D3D12/include/XC3DGSD3D12/GaussianPlyLoader.h
Normal file
46
MVS/3DGS-D3D12/include/XC3DGSD3D12/GaussianPlyLoader.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XC3DGSD3D12 {
|
||||||
|
|
||||||
|
struct Float3 {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GaussianSplatRuntimeData {
|
||||||
|
static constexpr uint32_t kColorTextureWidth = 2048;
|
||||||
|
static constexpr uint32_t kPositionStride = sizeof(float) * 3;
|
||||||
|
static constexpr uint32_t kOtherStride = sizeof(uint32_t) + sizeof(float) * 3;
|
||||||
|
static constexpr uint32_t kColorStride = sizeof(float) * 4;
|
||||||
|
static constexpr uint32_t kShCoefficientCount = 15;
|
||||||
|
static constexpr uint32_t kShStride = sizeof(float) * 3 * 16;
|
||||||
|
|
||||||
|
uint32_t splatCount = 0;
|
||||||
|
uint32_t colorTextureWidth = kColorTextureWidth;
|
||||||
|
uint32_t colorTextureHeight = 0;
|
||||||
|
Float3 boundsMin = {};
|
||||||
|
Float3 boundsMax = {};
|
||||||
|
std::vector<std::byte> positionData;
|
||||||
|
std::vector<std::byte> otherData;
|
||||||
|
std::vector<std::byte> colorData;
|
||||||
|
std::vector<std::byte> shData;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool LoadGaussianSceneFromPly(
|
||||||
|
const std::filesystem::path& filePath,
|
||||||
|
GaussianSplatRuntimeData& outData,
|
||||||
|
std::string& outErrorMessage);
|
||||||
|
|
||||||
|
bool WriteGaussianSceneSummary(
|
||||||
|
const std::filesystem::path& filePath,
|
||||||
|
const GaussianSplatRuntimeData& data,
|
||||||
|
std::string& outErrorMessage);
|
||||||
|
|
||||||
|
} // namespace XC3DGSD3D12
|
||||||
43
MVS/3DGS-D3D12/shaders/BuildSortKeysCS.hlsl
Normal file
43
MVS/3DGS-D3D12/shaders/BuildSortKeysCS.hlsl
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#define GROUP_SIZE 64
|
||||||
|
|
||||||
|
cbuffer FrameConstants : register(b0)
|
||||||
|
{
|
||||||
|
float4x4 gViewProjection;
|
||||||
|
float4x4 gView;
|
||||||
|
float4x4 gProjection;
|
||||||
|
float4 gCameraWorldPos;
|
||||||
|
float4 gScreenParams;
|
||||||
|
float4 gSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
ByteAddressBuffer gPositions : register(t0);
|
||||||
|
StructuredBuffer<uint> gOrderBuffer : register(t1);
|
||||||
|
RWStructuredBuffer<uint> gSortKeys : register(u0);
|
||||||
|
|
||||||
|
float3 LoadFloat3(ByteAddressBuffer buffer, uint byteOffset)
|
||||||
|
{
|
||||||
|
return asfloat(buffer.Load3(byteOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint FloatToSortableUint(float value)
|
||||||
|
{
|
||||||
|
uint bits = asuint(value);
|
||||||
|
uint mask = (0u - (bits >> 31)) | 0x80000000u;
|
||||||
|
return bits ^ mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[numthreads(GROUP_SIZE, 1, 1)]
|
||||||
|
void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||||
|
{
|
||||||
|
uint index = dispatchThreadId.x;
|
||||||
|
uint splatCount = (uint)gSettings.x;
|
||||||
|
if (index >= splatCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint splatIndex = gOrderBuffer[index];
|
||||||
|
float3 position = LoadFloat3(gPositions, splatIndex * 12);
|
||||||
|
float3 viewPosition = mul(float4(position, 1.0), gView).xyz;
|
||||||
|
gSortKeys[index] = FloatToSortableUint(viewPosition.z);
|
||||||
|
}
|
||||||
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Texture2D<float4> gSplatTexture : register(t0);
|
||||||
|
|
||||||
|
struct PixelInput
|
||||||
|
{
|
||||||
|
float4 position : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
float4 MainPS(PixelInput input) : SV_Target0
|
||||||
|
{
|
||||||
|
float4 color = gSplatTexture.Load(int3(int2(input.position.xy), 0));
|
||||||
|
return float4(color.rgb, color.a);
|
||||||
|
}
|
||||||
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
struct VertexOutput
|
||||||
|
{
|
||||||
|
float4 position : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
VertexOutput MainVS(uint vertexId : SV_VertexID)
|
||||||
|
{
|
||||||
|
VertexOutput output = (VertexOutput)0;
|
||||||
|
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 4.0 - 1.0;
|
||||||
|
output.position = float4(quadPosition, 1.0, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
19
MVS/3DGS-D3D12/shaders/DebugPointsPS.hlsl
Normal file
19
MVS/3DGS-D3D12/shaders/DebugPointsPS.hlsl
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
struct PixelInput
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
48
MVS/3DGS-D3D12/shaders/DebugPointsVS.hlsl
Normal file
48
MVS/3DGS-D3D12/shaders/DebugPointsVS.hlsl
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include "PreparedSplatView.hlsli"
|
||||||
|
|
||||||
|
cbuffer FrameConstants : register(b0)
|
||||||
|
{
|
||||||
|
float4x4 gViewProjection;
|
||||||
|
float4x4 gView;
|
||||||
|
float4x4 gProjection;
|
||||||
|
float4 gCameraWorldPos;
|
||||||
|
float4 gScreenParams;
|
||||||
|
float4 gSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
StructuredBuffer<PreparedSplatView> gPreparedViews : register(t0);
|
||||||
|
StructuredBuffer<uint> gOrderBuffer : register(t1);
|
||||||
|
|
||||||
|
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)0;
|
||||||
|
uint splatIndex = gOrderBuffer[instanceId];
|
||||||
|
PreparedSplatView view = gPreparedViews[splatIndex];
|
||||||
|
float4 color = UnpackPreparedColor(view);
|
||||||
|
|
||||||
|
if (view.clipPosition.w <= 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.position.xy += deltaScreenPosition * view.clipPosition.w;
|
||||||
|
output.color = color;
|
||||||
|
output.localPosition = quadPosition;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
477
MVS/3DGS-D3D12/shaders/DeviceRadixSort.hlsl
Normal file
477
MVS/3DGS-D3D12/shaders/DeviceRadixSort.hlsl
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
272
MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl
Normal file
272
MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#include "PreparedSplatView.hlsli"
|
||||||
|
|
||||||
|
#define GROUP_SIZE 64
|
||||||
|
|
||||||
|
cbuffer FrameConstants : register(b0)
|
||||||
|
{
|
||||||
|
float4x4 gViewProjection;
|
||||||
|
float4x4 gView;
|
||||||
|
float4x4 gProjection;
|
||||||
|
float4 gCameraWorldPos;
|
||||||
|
float4 gScreenParams;
|
||||||
|
float4 gSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
ByteAddressBuffer gPositions : register(t0);
|
||||||
|
ByteAddressBuffer gOther : register(t1);
|
||||||
|
Texture2D<float4> gColor : register(t2);
|
||||||
|
ByteAddressBuffer gSh : register(t3);
|
||||||
|
RWStructuredBuffer<PreparedSplatView> gPreparedViews : register(u0);
|
||||||
|
|
||||||
|
static const float SH_C1 = 0.4886025;
|
||||||
|
static const float SH_C2[] = { 1.0925484, -1.0925484, 0.3153916, -1.0925484, 0.5462742 };
|
||||||
|
static const float SH_C3[] = { -0.5900436, 2.8906114, -0.4570458, 0.3731763, -0.4570458, 1.4453057, -0.5900436 };
|
||||||
|
static const uint kColorTextureWidth = 2048;
|
||||||
|
static const uint kOtherStride = 16;
|
||||||
|
static const uint kShStride = 192;
|
||||||
|
|
||||||
|
struct SplatSHData
|
||||||
|
{
|
||||||
|
float3 col;
|
||||||
|
float3 sh[15];
|
||||||
|
};
|
||||||
|
|
||||||
|
float3 LoadFloat3(ByteAddressBuffer buffer, uint byteOffset)
|
||||||
|
{
|
||||||
|
return asfloat(buffer.Load3(byteOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint EncodeMorton2D_16x16(uint2 c)
|
||||||
|
{
|
||||||
|
uint t = ((c.y & 0xF) << 8) | (c.x & 0xF);
|
||||||
|
t = (t ^ (t << 2)) & 0x3333;
|
||||||
|
t = (t ^ (t << 1)) & 0x5555;
|
||||||
|
return (t | (t >> 7)) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint2 DecodeMorton2D_16x16(uint t)
|
||||||
|
{
|
||||||
|
t = (t & 0xFF) | ((t & 0xFE) << 7);
|
||||||
|
t &= 0x5555;
|
||||||
|
t = (t ^ (t >> 1)) & 0x3333;
|
||||||
|
t = (t ^ (t >> 2)) & 0x0F0F;
|
||||||
|
return uint2(t & 0xF, t >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint3 SplatIndexToPixelIndex(uint index)
|
||||||
|
{
|
||||||
|
uint2 xy = DecodeMorton2D_16x16(index);
|
||||||
|
uint tileWidth = kColorTextureWidth / 16;
|
||||||
|
index >>= 8;
|
||||||
|
|
||||||
|
uint3 result;
|
||||||
|
result.x = (index % tileWidth) * 16 + xy.x;
|
||||||
|
result.y = (index / tileWidth) * 16 + xy.y;
|
||||||
|
result.z = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 DecodePacked_10_10_10_2(uint encoded)
|
||||||
|
{
|
||||||
|
return float4(
|
||||||
|
(encoded & 1023) / 1023.0,
|
||||||
|
((encoded >> 10) & 1023) / 1023.0,
|
||||||
|
((encoded >> 20) & 1023) / 1023.0,
|
||||||
|
((encoded >> 30) & 3) / 3.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 DecodeRotation(float4 packedRotation)
|
||||||
|
{
|
||||||
|
uint droppedIndex = (uint)round(packedRotation.w * 3.0);
|
||||||
|
float4 rotation;
|
||||||
|
rotation.xyz = packedRotation.xyz * sqrt(2.0) - (1.0 / sqrt(2.0));
|
||||||
|
rotation.w = sqrt(1.0 - saturate(dot(rotation.xyz, rotation.xyz)));
|
||||||
|
|
||||||
|
if (droppedIndex == 0)
|
||||||
|
{
|
||||||
|
rotation = rotation.wxyz;
|
||||||
|
}
|
||||||
|
if (droppedIndex == 1)
|
||||||
|
{
|
||||||
|
rotation = rotation.xwyz;
|
||||||
|
}
|
||||||
|
if (droppedIndex == 2)
|
||||||
|
{
|
||||||
|
rotation = rotation.xywz;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3x3 CalcMatrixFromRotationScale(float4 rotation, float3 scale)
|
||||||
|
{
|
||||||
|
float3x3 scaleMatrix = float3x3(
|
||||||
|
scale.x, 0, 0,
|
||||||
|
0, scale.y, 0,
|
||||||
|
0, 0, scale.z);
|
||||||
|
|
||||||
|
float x = rotation.x;
|
||||||
|
float y = rotation.y;
|
||||||
|
float z = rotation.z;
|
||||||
|
float w = rotation.w;
|
||||||
|
|
||||||
|
float3x3 rotationMatrix = float3x3(
|
||||||
|
1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y),
|
||||||
|
2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x),
|
||||||
|
2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y));
|
||||||
|
|
||||||
|
return mul(rotationMatrix, scaleMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalcCovariance3D(float3x3 rotationScaleMatrix, out float3 sigma0, out float3 sigma1)
|
||||||
|
{
|
||||||
|
float3x3 sigma = mul(rotationScaleMatrix, transpose(rotationScaleMatrix));
|
||||||
|
sigma0 = float3(sigma._m00, sigma._m01, sigma._m02);
|
||||||
|
sigma1 = float3(sigma._m11, sigma._m12, sigma._m22);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 CalcCovariance2D(float3 worldPosition, float3 covariance0, float3 covariance1)
|
||||||
|
{
|
||||||
|
float3 viewPosition = mul(float4(worldPosition, 1.0), gView).xyz;
|
||||||
|
|
||||||
|
float aspect = gProjection._m00 / gProjection._m11;
|
||||||
|
float tanFovX = rcp(gProjection._m00);
|
||||||
|
float tanFovY = rcp(gProjection._m11 * aspect);
|
||||||
|
float clampX = 1.3 * tanFovX;
|
||||||
|
float clampY = 1.3 * tanFovY;
|
||||||
|
viewPosition.x = clamp(viewPosition.x / viewPosition.z, -clampX, clampX) * viewPosition.z;
|
||||||
|
viewPosition.y = clamp(viewPosition.y / viewPosition.z, -clampY, clampY) * viewPosition.z;
|
||||||
|
|
||||||
|
float focal = gScreenParams.x * gProjection._m00 * 0.5;
|
||||||
|
|
||||||
|
float3x3 jacobian = float3x3(
|
||||||
|
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 = transpose((float3x3)gView);
|
||||||
|
float3x3 transform = mul(jacobian, worldToView);
|
||||||
|
float3x3 covariance = float3x3(
|
||||||
|
covariance0.x, covariance0.y, covariance0.z,
|
||||||
|
covariance0.y, covariance1.x, covariance1.y,
|
||||||
|
covariance0.z, covariance1.y, covariance1.z);
|
||||||
|
float3x3 projected = mul(transform, mul(covariance, transpose(transform)));
|
||||||
|
projected._m00 += 0.3;
|
||||||
|
projected._m11 += 0.3;
|
||||||
|
return float3(projected._m00, projected._m01, projected._m11);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DecomposeCovariance(float3 covariance2D, out float2 axis1, out float2 axis2)
|
||||||
|
{
|
||||||
|
float diagonal0 = covariance2D.x;
|
||||||
|
float diagonal1 = covariance2D.z;
|
||||||
|
float offDiagonal = covariance2D.y;
|
||||||
|
float mid = 0.5 * (diagonal0 + diagonal1);
|
||||||
|
float radius = length(float2((diagonal0 - diagonal1) * 0.5, offDiagonal));
|
||||||
|
float lambda0 = mid + radius;
|
||||||
|
float lambda1 = max(mid - radius, 0.1);
|
||||||
|
float2 diagonalVector = normalize(float2(offDiagonal, lambda0 - diagonal0));
|
||||||
|
diagonalVector.y = -diagonalVector.y;
|
||||||
|
const float maxSize = 4096.0;
|
||||||
|
axis1 = min(sqrt(2.0 * lambda0), maxSize) * diagonalVector;
|
||||||
|
axis2 = min(sqrt(2.0 * lambda1), maxSize) * float2(diagonalVector.y, -diagonalVector.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
SplatSHData LoadSplatSH(uint index)
|
||||||
|
{
|
||||||
|
SplatSHData sh;
|
||||||
|
const uint shBaseOffset = index * kShStride;
|
||||||
|
sh.col = gColor.Load(int3(SplatIndexToPixelIndex(index).xy, 0)).rgb;
|
||||||
|
|
||||||
|
[unroll]
|
||||||
|
for (uint coefficientIndex = 0; coefficientIndex < 15; ++coefficientIndex)
|
||||||
|
{
|
||||||
|
sh.sh[coefficientIndex] = LoadFloat3(gSh, shBaseOffset + coefficientIndex * 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sh;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 ShadeSH(SplatSHData sh, float3 direction, int shOrder)
|
||||||
|
{
|
||||||
|
direction *= -1.0;
|
||||||
|
float x = direction.x;
|
||||||
|
float y = direction.y;
|
||||||
|
float z = direction.z;
|
||||||
|
|
||||||
|
float3 result = sh.col;
|
||||||
|
if (shOrder >= 1)
|
||||||
|
{
|
||||||
|
result += SH_C1 * (-sh.sh[0] * y + sh.sh[1] * z - sh.sh[2] * x);
|
||||||
|
if (shOrder >= 2)
|
||||||
|
{
|
||||||
|
float xx = x * x;
|
||||||
|
float yy = y * y;
|
||||||
|
float zz = z * z;
|
||||||
|
float xy = x * y;
|
||||||
|
float yz = y * z;
|
||||||
|
float xz = x * z;
|
||||||
|
result +=
|
||||||
|
(SH_C2[0] * xy) * sh.sh[3] +
|
||||||
|
(SH_C2[1] * yz) * sh.sh[4] +
|
||||||
|
(SH_C2[2] * (2 * zz - xx - yy)) * sh.sh[5] +
|
||||||
|
(SH_C2[3] * xz) * sh.sh[6] +
|
||||||
|
(SH_C2[4] * (xx - yy)) * sh.sh[7];
|
||||||
|
if (shOrder >= 3)
|
||||||
|
{
|
||||||
|
result +=
|
||||||
|
(SH_C3[0] * y * (3 * xx - yy)) * sh.sh[8] +
|
||||||
|
(SH_C3[1] * xy * z) * sh.sh[9] +
|
||||||
|
(SH_C3[2] * y * (4 * zz - xx - yy)) * sh.sh[10] +
|
||||||
|
(SH_C3[3] * z * (2 * zz - 3 * xx - 3 * yy)) * sh.sh[11] +
|
||||||
|
(SH_C3[4] * x * (4 * zz - xx - yy)) * sh.sh[12] +
|
||||||
|
(SH_C3[5] * z * (xx - yy)) * sh.sh[13] +
|
||||||
|
(SH_C3[6] * x * (xx - 3 * yy)) * sh.sh[14];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(result, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[numthreads(GROUP_SIZE, 1, 1)]
|
||||||
|
void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||||
|
{
|
||||||
|
uint index = dispatchThreadId.x;
|
||||||
|
uint splatCount = (uint)gSettings.x;
|
||||||
|
if (index >= splatCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedSplatView view = (PreparedSplatView)0;
|
||||||
|
|
||||||
|
float3 position = LoadFloat3(gPositions, index * 12);
|
||||||
|
uint packedRotation = gOther.Load(index * kOtherStride);
|
||||||
|
float4 rotation = DecodeRotation(DecodePacked_10_10_10_2(packedRotation));
|
||||||
|
float3 scale = LoadFloat3(gOther, index * kOtherStride + 4);
|
||||||
|
float4 colorOpacity = gColor.Load(int3(SplatIndexToPixelIndex(index).xy, 0));
|
||||||
|
|
||||||
|
view.clipPosition = mul(float4(position, 1.0), gViewProjection);
|
||||||
|
if (view.clipPosition.w > 0.0)
|
||||||
|
{
|
||||||
|
float3x3 rotationScale = CalcMatrixFromRotationScale(rotation, scale);
|
||||||
|
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);
|
||||||
|
|
||||||
|
SplatSHData sh = LoadSplatSH(index);
|
||||||
|
float3 viewDirection = normalize(gCameraWorldPos.xyz - position);
|
||||||
|
float3 shadedColor = ShadeSH(sh, viewDirection, (int)gSettings.z);
|
||||||
|
float opacity = saturate(colorOpacity.a * gSettings.y);
|
||||||
|
|
||||||
|
view.packedColor.x = (f32tof16(shadedColor.r) << 16) | f32tof16(shadedColor.g);
|
||||||
|
view.packedColor.y = (f32tof16(shadedColor.b) << 16) | f32tof16(opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
gPreparedViews[index] = view;
|
||||||
|
}
|
||||||
22
MVS/3DGS-D3D12/shaders/PreparedSplatView.hlsli
Normal file
22
MVS/3DGS-D3D12/shaders/PreparedSplatView.hlsli
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef PREPARED_SPLAT_VIEW_HLSLI
|
||||||
|
#define PREPARED_SPLAT_VIEW_HLSLI
|
||||||
|
|
||||||
|
struct PreparedSplatView
|
||||||
|
{
|
||||||
|
float4 clipPosition;
|
||||||
|
float2 axis1;
|
||||||
|
float2 axis2;
|
||||||
|
uint2 packedColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
float4 UnpackPreparedColor(PreparedSplatView view)
|
||||||
|
{
|
||||||
|
float4 color;
|
||||||
|
color.r = f16tof32((view.packedColor.x >> 16) & 0xFFFF);
|
||||||
|
color.g = f16tof32(view.packedColor.x & 0xFFFF);
|
||||||
|
color.b = f16tof32((view.packedColor.y >> 16) & 0xFFFF);
|
||||||
|
color.a = f16tof32(view.packedColor.y & 0xFFFF);
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
959
MVS/3DGS-D3D12/shaders/SortCommon.hlsl
Normal file
959
MVS/3DGS-D3D12/shaders/SortCommon.hlsl
Normal 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
|
||||||
|
}
|
||||||
2193
MVS/3DGS-D3D12/src/App.cpp
Normal file
2193
MVS/3DGS-D3D12/src/App.cpp
Normal file
File diff suppressed because it is too large
Load Diff
617
MVS/3DGS-D3D12/src/GaussianPlyLoader.cpp
Normal file
617
MVS/3DGS-D3D12/src/GaussianPlyLoader.cpp
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
#include "XC3DGSD3D12/GaussianPlyLoader.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XC3DGSD3D12 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr float kSHC0 = 0.2820948f;
|
||||||
|
|
||||||
|
enum class PlyPropertyType {
|
||||||
|
None,
|
||||||
|
Float32,
|
||||||
|
Float64,
|
||||||
|
UInt8,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlyProperty {
|
||||||
|
std::string name;
|
||||||
|
PlyPropertyType type = PlyPropertyType::None;
|
||||||
|
uint32_t offset = 0;
|
||||||
|
uint32_t size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlyHeader {
|
||||||
|
uint32_t vertexCount = 0;
|
||||||
|
uint32_t vertexStride = 0;
|
||||||
|
std::vector<PlyProperty> properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Float4 {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
float w = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RawGaussianSplat {
|
||||||
|
Float3 position = {};
|
||||||
|
Float3 dc0 = {};
|
||||||
|
std::array<Float3, GaussianSplatRuntimeData::kShCoefficientCount> sh = {};
|
||||||
|
float opacity = 0.0f;
|
||||||
|
Float3 scale = {};
|
||||||
|
Float4 rotation = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GaussianPlyPropertyLayout {
|
||||||
|
const PlyProperty* position[3] = {};
|
||||||
|
const PlyProperty* dc0[3] = {};
|
||||||
|
const PlyProperty* opacity = nullptr;
|
||||||
|
const PlyProperty* scale[3] = {};
|
||||||
|
const PlyProperty* rotation[4] = {};
|
||||||
|
std::array<const PlyProperty*, GaussianSplatRuntimeData::kShCoefficientCount * 3> sh = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string TrimTrailingCarriageReturn(std::string line) {
|
||||||
|
if (!line.empty() && line.back() == '\r') {
|
||||||
|
line.pop_back();
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t PropertyTypeSize(PlyPropertyType type) {
|
||||||
|
switch (type) {
|
||||||
|
case PlyPropertyType::Float32:
|
||||||
|
return 4;
|
||||||
|
case PlyPropertyType::Float64:
|
||||||
|
return 8;
|
||||||
|
case PlyPropertyType::UInt8:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParsePropertyType(const std::string& token, PlyPropertyType& outType) {
|
||||||
|
if (token == "float") {
|
||||||
|
outType = PlyPropertyType::Float32;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (token == "double") {
|
||||||
|
outType = PlyPropertyType::Float64;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (token == "uchar") {
|
||||||
|
outType = PlyPropertyType::UInt8;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
outType = PlyPropertyType::None;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParsePlyHeader(std::ifstream& input, PlyHeader& outHeader, std::string& outErrorMessage) {
|
||||||
|
std::string line;
|
||||||
|
if (!std::getline(input, line)) {
|
||||||
|
outErrorMessage = "Failed to read PLY magic line.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TrimTrailingCarriageReturn(line) != "ply") {
|
||||||
|
outErrorMessage = "Input file is not a valid PLY file.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sawFormat = false;
|
||||||
|
std::string currentElement;
|
||||||
|
|
||||||
|
while (std::getline(input, line)) {
|
||||||
|
line = TrimTrailingCarriageReturn(line);
|
||||||
|
if (line == "end_header") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istringstream stream(line);
|
||||||
|
std::string token;
|
||||||
|
stream >> token;
|
||||||
|
|
||||||
|
if (token == "comment") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == "format") {
|
||||||
|
std::string formatName;
|
||||||
|
std::string version;
|
||||||
|
stream >> formatName >> version;
|
||||||
|
if (formatName != "binary_little_endian") {
|
||||||
|
outErrorMessage = "Only binary_little_endian PLY files are supported.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sawFormat = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == "element") {
|
||||||
|
stream >> currentElement;
|
||||||
|
if (currentElement == "vertex") {
|
||||||
|
stream >> outHeader.vertexCount;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == "property" && currentElement == "vertex") {
|
||||||
|
std::string typeToken;
|
||||||
|
std::string name;
|
||||||
|
stream >> typeToken >> name;
|
||||||
|
|
||||||
|
PlyPropertyType propertyType = PlyPropertyType::None;
|
||||||
|
if (!ParsePropertyType(typeToken, propertyType)) {
|
||||||
|
outErrorMessage = "Unsupported PLY vertex property type: " + typeToken;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlyProperty property;
|
||||||
|
property.name = name;
|
||||||
|
property.type = propertyType;
|
||||||
|
property.offset = outHeader.vertexStride;
|
||||||
|
property.size = PropertyTypeSize(propertyType);
|
||||||
|
outHeader.vertexStride += property.size;
|
||||||
|
outHeader.properties.push_back(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sawFormat) {
|
||||||
|
outErrorMessage = "PLY header is missing a valid format declaration.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outHeader.vertexCount == 0) {
|
||||||
|
outErrorMessage = "PLY file does not contain any vertex data.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outHeader.vertexStride == 0 || outHeader.properties.empty()) {
|
||||||
|
outErrorMessage = "PLY vertex layout is empty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadPropertyAsFloat(
|
||||||
|
const std::byte* vertexBytes,
|
||||||
|
const PlyProperty& property,
|
||||||
|
float& outValue) {
|
||||||
|
const std::byte* propertyPtr = vertexBytes + property.offset;
|
||||||
|
switch (property.type) {
|
||||||
|
case PlyPropertyType::Float32: {
|
||||||
|
std::memcpy(&outValue, propertyPtr, sizeof(float));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case PlyPropertyType::Float64: {
|
||||||
|
double value = 0.0;
|
||||||
|
std::memcpy(&value, propertyPtr, sizeof(double));
|
||||||
|
outValue = static_cast<float>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case PlyPropertyType::UInt8: {
|
||||||
|
uint8_t value = 0;
|
||||||
|
std::memcpy(&value, propertyPtr, sizeof(uint8_t));
|
||||||
|
outValue = static_cast<float>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuildPropertyMap(
|
||||||
|
const PlyHeader& header,
|
||||||
|
std::unordered_map<std::string_view, const PlyProperty*>& outMap,
|
||||||
|
std::string& outErrorMessage) {
|
||||||
|
outMap.clear();
|
||||||
|
outMap.reserve(header.properties.size());
|
||||||
|
|
||||||
|
for (const PlyProperty& property : header.properties) {
|
||||||
|
const auto [it, inserted] = outMap.emplace(property.name, &property);
|
||||||
|
if (!inserted) {
|
||||||
|
outErrorMessage = "Duplicate PLY vertex property found: " + property.name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequireProperty(
|
||||||
|
const std::unordered_map<std::string_view, const PlyProperty*>& propertyMap,
|
||||||
|
std::string_view name,
|
||||||
|
const PlyProperty*& outProperty,
|
||||||
|
std::string& outErrorMessage) {
|
||||||
|
const auto iterator = propertyMap.find(name);
|
||||||
|
if (iterator == propertyMap.end()) {
|
||||||
|
outErrorMessage = "Missing required PLY property: " + std::string(name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outProperty = iterator->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuildGaussianPlyPropertyLayout(
|
||||||
|
const std::unordered_map<std::string_view, const PlyProperty*>& propertyMap,
|
||||||
|
GaussianPlyPropertyLayout& outLayout,
|
||||||
|
std::string& outErrorMessage) {
|
||||||
|
outLayout = {};
|
||||||
|
|
||||||
|
if (!RequireProperty(propertyMap, "x", outLayout.position[0], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "y", outLayout.position[1], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "z", outLayout.position[2], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "f_dc_0", outLayout.dc0[0], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "f_dc_1", outLayout.dc0[1], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "f_dc_2", outLayout.dc0[2], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "opacity", outLayout.opacity, outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "scale_0", outLayout.scale[0], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "scale_1", outLayout.scale[1], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "scale_2", outLayout.scale[2], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "rot_0", outLayout.rotation[0], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "rot_1", outLayout.rotation[1], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "rot_2", outLayout.rotation[2], outErrorMessage) ||
|
||||||
|
!RequireProperty(propertyMap, "rot_3", outLayout.rotation[3], outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t index = 0; index < outLayout.sh.size(); ++index) {
|
||||||
|
const std::string propertyName = "f_rest_" + std::to_string(index);
|
||||||
|
if (!RequireProperty(propertyMap, propertyName, outLayout.sh[index], outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float3 Min(const Float3& a, const Float3& b) {
|
||||||
|
return {
|
||||||
|
std::min(a.x, b.x),
|
||||||
|
std::min(a.y, b.y),
|
||||||
|
std::min(a.z, b.z),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Float3 Max(const Float3& a, const Float3& b) {
|
||||||
|
return {
|
||||||
|
std::max(a.x, b.x),
|
||||||
|
std::max(a.y, b.y),
|
||||||
|
std::max(a.z, b.z),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
float Dot(const Float4& a, const Float4& b) {
|
||||||
|
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float4 NormalizeSwizzleRotation(const Float4& wxyz) {
|
||||||
|
const float lengthSquared = Dot(wxyz, wxyz);
|
||||||
|
if (lengthSquared <= std::numeric_limits<float>::epsilon()) {
|
||||||
|
return { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||||
|
}
|
||||||
|
|
||||||
|
const float inverseLength = 1.0f / std::sqrt(lengthSquared);
|
||||||
|
return {
|
||||||
|
wxyz.y * inverseLength,
|
||||||
|
wxyz.z * inverseLength,
|
||||||
|
wxyz.w * inverseLength,
|
||||||
|
wxyz.x * inverseLength,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Float4 PackSmallest3Rotation(Float4 rotation) {
|
||||||
|
const Float4 absoluteRotation = {
|
||||||
|
std::fabs(rotation.x),
|
||||||
|
std::fabs(rotation.y),
|
||||||
|
std::fabs(rotation.z),
|
||||||
|
std::fabs(rotation.w),
|
||||||
|
};
|
||||||
|
|
||||||
|
int largestIndex = 0;
|
||||||
|
float largestValue = absoluteRotation.x;
|
||||||
|
if (absoluteRotation.y > largestValue) {
|
||||||
|
largestIndex = 1;
|
||||||
|
largestValue = absoluteRotation.y;
|
||||||
|
}
|
||||||
|
if (absoluteRotation.z > largestValue) {
|
||||||
|
largestIndex = 2;
|
||||||
|
largestValue = absoluteRotation.z;
|
||||||
|
}
|
||||||
|
if (absoluteRotation.w > largestValue) {
|
||||||
|
largestIndex = 3;
|
||||||
|
largestValue = absoluteRotation.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (largestIndex == 0) {
|
||||||
|
rotation = { rotation.y, rotation.z, rotation.w, rotation.x };
|
||||||
|
} else if (largestIndex == 1) {
|
||||||
|
rotation = { rotation.x, rotation.z, rotation.w, rotation.y };
|
||||||
|
} else if (largestIndex == 2) {
|
||||||
|
rotation = { rotation.x, rotation.y, rotation.w, rotation.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
const float sign = rotation.w >= 0.0f ? 1.0f : -1.0f;
|
||||||
|
const float invSqrt2 = std::sqrt(2.0f) * 0.5f;
|
||||||
|
const Float3 encoded = {
|
||||||
|
(rotation.x * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
|
||||||
|
(rotation.y * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
|
||||||
|
(rotation.z * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
|
||||||
|
};
|
||||||
|
|
||||||
|
(void)invSqrt2;
|
||||||
|
return { encoded.x, encoded.y, encoded.z, static_cast<float>(largestIndex) / 3.0f };
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EncodeQuatToNorm10(const Float4& packedRotation) {
|
||||||
|
const auto saturate = [](float value) {
|
||||||
|
return std::clamp(value, 0.0f, 1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint32_t x = static_cast<uint32_t>(saturate(packedRotation.x) * 1023.5f);
|
||||||
|
const uint32_t y = static_cast<uint32_t>(saturate(packedRotation.y) * 1023.5f);
|
||||||
|
const uint32_t z = static_cast<uint32_t>(saturate(packedRotation.z) * 1023.5f);
|
||||||
|
const uint32_t w = static_cast<uint32_t>(saturate(packedRotation.w) * 3.5f);
|
||||||
|
return x | (y << 10) | (z << 20) | (w << 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
Float3 LinearScale(const Float3& logarithmicScale) {
|
||||||
|
return {
|
||||||
|
std::fabs(std::exp(logarithmicScale.x)),
|
||||||
|
std::fabs(std::exp(logarithmicScale.y)),
|
||||||
|
std::fabs(std::exp(logarithmicScale.z)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Float3 SH0ToColor(const Float3& dc0) {
|
||||||
|
return {
|
||||||
|
dc0.x * kSHC0 + 0.5f,
|
||||||
|
dc0.y * kSHC0 + 0.5f,
|
||||||
|
dc0.z * kSHC0 + 0.5f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
float Sigmoid(float value) {
|
||||||
|
return 1.0f / (1.0f + std::exp(-value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint32_t, 2> DecodeMorton2D16x16(uint32_t value) {
|
||||||
|
value = (value & 0xFFu) | ((value & 0xFEu) << 7u);
|
||||||
|
value &= 0x5555u;
|
||||||
|
value = (value ^ (value >> 1u)) & 0x3333u;
|
||||||
|
value = (value ^ (value >> 2u)) & 0x0F0Fu;
|
||||||
|
return { value & 0xFu, value >> 8u };
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SplatIndexToTextureIndex(uint32_t index) {
|
||||||
|
const std::array<uint32_t, 2> morton = DecodeMorton2D16x16(index);
|
||||||
|
const uint32_t widthInBlocks = GaussianSplatRuntimeData::kColorTextureWidth / 16u;
|
||||||
|
index >>= 8u;
|
||||||
|
const uint32_t x = (index % widthInBlocks) * 16u + morton[0];
|
||||||
|
const uint32_t y = (index / widthInBlocks) * 16u + morton[1];
|
||||||
|
return y * GaussianSplatRuntimeData::kColorTextureWidth + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void WriteValue(std::vector<std::byte>& bytes, size_t offset, const T& value) {
|
||||||
|
std::memcpy(bytes.data() + offset, &value, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteFloat3(std::vector<std::byte>& bytes, size_t offset, const Float3& value) {
|
||||||
|
WriteValue(bytes, offset + 0, value.x);
|
||||||
|
WriteValue(bytes, offset + 4, value.y);
|
||||||
|
WriteValue(bytes, offset + 8, value.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteFloat4(std::vector<std::byte>& bytes, size_t offset, float x, float y, float z, float w) {
|
||||||
|
WriteValue(bytes, offset + 0, x);
|
||||||
|
WriteValue(bytes, offset + 4, y);
|
||||||
|
WriteValue(bytes, offset + 8, z);
|
||||||
|
WriteValue(bytes, offset + 12, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadGaussianSplat(
|
||||||
|
const std::byte* vertexBytes,
|
||||||
|
const GaussianPlyPropertyLayout& propertyLayout,
|
||||||
|
RawGaussianSplat& outSplat,
|
||||||
|
std::string& outErrorMessage) {
|
||||||
|
auto readFloat = [&](const PlyProperty* property, float& outValue) -> bool {
|
||||||
|
if (property == nullptr) {
|
||||||
|
outErrorMessage = "Gaussian PLY property layout is incomplete.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ReadPropertyAsFloat(vertexBytes, *property, outValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!readFloat(propertyLayout.position[0], outSplat.position.x) ||
|
||||||
|
!readFloat(propertyLayout.position[1], outSplat.position.y) ||
|
||||||
|
!readFloat(propertyLayout.position[2], outSplat.position.z) ||
|
||||||
|
!readFloat(propertyLayout.dc0[0], outSplat.dc0.x) ||
|
||||||
|
!readFloat(propertyLayout.dc0[1], outSplat.dc0.y) ||
|
||||||
|
!readFloat(propertyLayout.dc0[2], outSplat.dc0.z) ||
|
||||||
|
!readFloat(propertyLayout.opacity, outSplat.opacity) ||
|
||||||
|
!readFloat(propertyLayout.scale[0], outSplat.scale.x) ||
|
||||||
|
!readFloat(propertyLayout.scale[1], outSplat.scale.y) ||
|
||||||
|
!readFloat(propertyLayout.scale[2], outSplat.scale.z) ||
|
||||||
|
!readFloat(propertyLayout.rotation[0], outSplat.rotation.x) ||
|
||||||
|
!readFloat(propertyLayout.rotation[1], outSplat.rotation.y) ||
|
||||||
|
!readFloat(propertyLayout.rotation[2], outSplat.rotation.z) ||
|
||||||
|
!readFloat(propertyLayout.rotation[3], outSplat.rotation.w)) {
|
||||||
|
if (outErrorMessage.empty()) {
|
||||||
|
outErrorMessage = "Failed to read required Gaussian splat PLY properties.";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<float, GaussianSplatRuntimeData::kShCoefficientCount * 3> shRaw = {};
|
||||||
|
for (uint32_t index = 0; index < shRaw.size(); ++index) {
|
||||||
|
if (!readFloat(propertyLayout.sh[index], shRaw[index])) {
|
||||||
|
if (outErrorMessage.empty()) {
|
||||||
|
outErrorMessage = "Failed to read SH rest coefficients from PLY.";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t coefficientIndex = 0; coefficientIndex < GaussianSplatRuntimeData::kShCoefficientCount; ++coefficientIndex) {
|
||||||
|
outSplat.sh[coefficientIndex] = {
|
||||||
|
shRaw[coefficientIndex + 0],
|
||||||
|
shRaw[coefficientIndex + GaussianSplatRuntimeData::kShCoefficientCount],
|
||||||
|
shRaw[coefficientIndex + GaussianSplatRuntimeData::kShCoefficientCount * 2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinearizeGaussianSplat(RawGaussianSplat& splat) {
|
||||||
|
const Float4 normalizedQuaternion = NormalizeSwizzleRotation(splat.rotation);
|
||||||
|
const Float4 packedQuaternion = PackSmallest3Rotation(normalizedQuaternion);
|
||||||
|
splat.rotation = packedQuaternion;
|
||||||
|
splat.scale = LinearScale(splat.scale);
|
||||||
|
splat.dc0 = SH0ToColor(splat.dc0);
|
||||||
|
splat.opacity = Sigmoid(splat.opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool LoadGaussianSceneFromPly(
|
||||||
|
const std::filesystem::path& filePath,
|
||||||
|
GaussianSplatRuntimeData& outData,
|
||||||
|
std::string& outErrorMessage) {
|
||||||
|
outData = {};
|
||||||
|
outErrorMessage.clear();
|
||||||
|
|
||||||
|
std::ifstream input(filePath, std::ios::binary);
|
||||||
|
if (!input.is_open()) {
|
||||||
|
outErrorMessage = "Failed to open PLY file: " + filePath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlyHeader header;
|
||||||
|
if (!ParsePlyHeader(input, header, outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string_view, const PlyProperty*> propertyMap;
|
||||||
|
if (!BuildPropertyMap(header, propertyMap, outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GaussianPlyPropertyLayout propertyLayout;
|
||||||
|
if (!BuildGaussianPlyPropertyLayout(propertyMap, propertyLayout, outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outData.splatCount = header.vertexCount;
|
||||||
|
outData.colorTextureWidth = GaussianSplatRuntimeData::kColorTextureWidth;
|
||||||
|
outData.colorTextureHeight =
|
||||||
|
std::max<uint32_t>(1u, (header.vertexCount + outData.colorTextureWidth - 1u) / outData.colorTextureWidth);
|
||||||
|
outData.colorTextureHeight = (outData.colorTextureHeight + 15u) / 16u * 16u;
|
||||||
|
|
||||||
|
outData.positionData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kPositionStride);
|
||||||
|
outData.otherData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kOtherStride);
|
||||||
|
outData.colorData.resize(
|
||||||
|
static_cast<size_t>(outData.colorTextureWidth) *
|
||||||
|
static_cast<size_t>(outData.colorTextureHeight) *
|
||||||
|
GaussianSplatRuntimeData::kColorStride);
|
||||||
|
outData.shData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kShStride);
|
||||||
|
|
||||||
|
outData.boundsMin = {
|
||||||
|
std::numeric_limits<float>::infinity(),
|
||||||
|
std::numeric_limits<float>::infinity(),
|
||||||
|
std::numeric_limits<float>::infinity(),
|
||||||
|
};
|
||||||
|
outData.boundsMax = {
|
||||||
|
-std::numeric_limits<float>::infinity(),
|
||||||
|
-std::numeric_limits<float>::infinity(),
|
||||||
|
-std::numeric_limits<float>::infinity(),
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::byte> vertexBytes(header.vertexStride);
|
||||||
|
for (uint32_t splatIndex = 0; splatIndex < header.vertexCount; ++splatIndex) {
|
||||||
|
input.read(reinterpret_cast<char*>(vertexBytes.data()), static_cast<std::streamsize>(vertexBytes.size()));
|
||||||
|
if (input.gcount() != static_cast<std::streamsize>(vertexBytes.size())) {
|
||||||
|
outErrorMessage =
|
||||||
|
"Unexpected end of file while reading Gaussian splat vertex " + std::to_string(splatIndex) + ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RawGaussianSplat splat;
|
||||||
|
if (!ReadGaussianSplat(vertexBytes.data(), propertyLayout, splat, outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearizeGaussianSplat(splat);
|
||||||
|
|
||||||
|
outData.boundsMin = Min(outData.boundsMin, splat.position);
|
||||||
|
outData.boundsMax = Max(outData.boundsMax, splat.position);
|
||||||
|
|
||||||
|
const size_t positionOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kPositionStride;
|
||||||
|
WriteFloat3(outData.positionData, positionOffset, splat.position);
|
||||||
|
|
||||||
|
const size_t otherOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kOtherStride;
|
||||||
|
const uint32_t packedRotation = EncodeQuatToNorm10(splat.rotation);
|
||||||
|
WriteValue(outData.otherData, otherOffset, packedRotation);
|
||||||
|
WriteFloat3(outData.otherData, otherOffset + sizeof(uint32_t), splat.scale);
|
||||||
|
|
||||||
|
const size_t shOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kShStride;
|
||||||
|
for (uint32_t coefficientIndex = 0; coefficientIndex < GaussianSplatRuntimeData::kShCoefficientCount; ++coefficientIndex) {
|
||||||
|
const size_t coefficientOffset = shOffset + static_cast<size_t>(coefficientIndex) * sizeof(float) * 3u;
|
||||||
|
WriteFloat3(outData.shData, coefficientOffset, splat.sh[coefficientIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t textureIndex = SplatIndexToTextureIndex(splatIndex);
|
||||||
|
const size_t colorOffset = static_cast<size_t>(textureIndex) * GaussianSplatRuntimeData::kColorStride;
|
||||||
|
WriteFloat4(outData.colorData, colorOffset, splat.dc0.x, splat.dc0.y, splat.dc0.z, splat.opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteGaussianSceneSummary(
|
||||||
|
const std::filesystem::path& filePath,
|
||||||
|
const GaussianSplatRuntimeData& data,
|
||||||
|
std::string& outErrorMessage) {
|
||||||
|
outErrorMessage.clear();
|
||||||
|
|
||||||
|
std::ofstream output(filePath, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!output.is_open()) {
|
||||||
|
outErrorMessage = "Failed to open summary output file: " + filePath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output << "splat_count=" << data.splatCount << '\n';
|
||||||
|
output << "color_texture_width=" << data.colorTextureWidth << '\n';
|
||||||
|
output << "color_texture_height=" << data.colorTextureHeight << '\n';
|
||||||
|
output << "bounds_min=" << data.boundsMin.x << "," << data.boundsMin.y << "," << data.boundsMin.z << '\n';
|
||||||
|
output << "bounds_max=" << data.boundsMax.x << "," << data.boundsMax.y << "," << data.boundsMax.z << '\n';
|
||||||
|
output << "position_bytes=" << data.positionData.size() << '\n';
|
||||||
|
output << "other_bytes=" << data.otherData.size() << '\n';
|
||||||
|
output << "color_bytes=" << data.colorData.size() << '\n';
|
||||||
|
output << "sh_bytes=" << data.shData.size() << '\n';
|
||||||
|
|
||||||
|
return output.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XC3DGSD3D12
|
||||||
42
MVS/3DGS-D3D12/src/main.cpp
Normal file
42
MVS/3DGS-D3D12/src/main.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "XC3DGSD3D12/App.h"
|
||||||
|
|
||||||
|
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int showCommand) {
|
||||||
|
XC3DGSD3D12::App app;
|
||||||
|
|
||||||
|
int argumentCount = 0;
|
||||||
|
LPWSTR* arguments = CommandLineToArgvW(GetCommandLineW(), &argumentCount);
|
||||||
|
for (int index = 1; index + 1 < argumentCount; ++index) {
|
||||||
|
if (std::wstring(arguments[index]) == L"--frame-limit") {
|
||||||
|
app.SetFrameLimit(static_cast<unsigned int>(_wtoi(arguments[index + 1])));
|
||||||
|
++index;
|
||||||
|
} else if (std::wstring(arguments[index]) == L"--scene") {
|
||||||
|
app.SetGaussianScenePath(arguments[index + 1]);
|
||||||
|
++index;
|
||||||
|
} else if (std::wstring(arguments[index]) == L"--summary-file") {
|
||||||
|
app.SetSummaryPath(arguments[index + 1]);
|
||||||
|
++index;
|
||||||
|
} else if (std::wstring(arguments[index]) == L"--screenshot-file") {
|
||||||
|
app.SetScreenshotPath(arguments[index + 1]);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arguments != nullptr) {
|
||||||
|
LocalFree(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.Initialize(instance, showCommand)) {
|
||||||
|
const std::wstring message =
|
||||||
|
app.GetLastErrorMessage().empty()
|
||||||
|
? L"Failed to initialize XC 3DGS D3D12 MVS."
|
||||||
|
: app.GetLastErrorMessage();
|
||||||
|
MessageBoxW(nullptr, message.c_str(), L"Initialization Error", MB_OK | MB_ICONERROR);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Run();
|
||||||
|
}
|
||||||
245
docs/plan/3DGS-D3D12最小可行系统计划_2026-04-12.md
Normal file
245
docs/plan/3DGS-D3D12最小可行系统计划_2026-04-12.md
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
# 3DGS-D3D12 最小可行系统计划
|
||||||
|
|
||||||
|
日期:2026-04-12
|
||||||
|
|
||||||
|
## 1. 文档定位
|
||||||
|
|
||||||
|
这份计划用于指导 `D:\Xuanchi\Main\XCEngine\mvs\3DGS-D3D12` 的最小可行系统落地。
|
||||||
|
|
||||||
|
当前任务边界已经明确:
|
||||||
|
|
||||||
|
1. 只允许在 `mvs/3DGS-D3D12` 目录内开发与新增文件。
|
||||||
|
2. `engine` 代码只能引用,禁止修改。
|
||||||
|
3. 图形抽象层只能使用现有 `engine` 提供的 `RHI` 接口。
|
||||||
|
4. 本轮目标是先把 3DGS 的最小可行系统跑通,而不是把它正式并入引擎主线。
|
||||||
|
5. 本轮明确不引入 `chunk` 机制。
|
||||||
|
|
||||||
|
因此,这份计划不是“继续修补引擎内已有的 GaussianSplat 路径”,而是“在 MVS 目录内重新搭一条干净的、可验证的、无 chunk 的 3DGS-D3D12 最小链路”。
|
||||||
|
|
||||||
|
## 2. 参考实现与职责拆分
|
||||||
|
|
||||||
|
本轮只参考两条现有实现,各自承担不同职责:
|
||||||
|
|
||||||
|
### 2.1 `mvs/3DGS Unity Renderer`
|
||||||
|
|
||||||
|
用途:
|
||||||
|
|
||||||
|
1. 参考 `.ply` 的读取方式。
|
||||||
|
2. 参考如何从 PLY 属性中提取 3DGS 所需原始数据。
|
||||||
|
3. 参考资源准备阶段的数据布局与字段含义。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
1. 这里只借鉴导入与数据准备思路。
|
||||||
|
2. 不照搬 Unity Editor 资产工作流。
|
||||||
|
3. 不引入 chunk。
|
||||||
|
|
||||||
|
### 2.2 `mvs/3DGS-Unity`
|
||||||
|
|
||||||
|
用途:
|
||||||
|
|
||||||
|
1. 参考“干净的无 chunk 渲染路径”。
|
||||||
|
2. 参考 `prepare -> sort -> draw -> composite` 的最小闭环。
|
||||||
|
3. 参考 3DGS 在屏幕空间椭圆展开、混合与合成时的核心着色器语义。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
1. 这里重点参考渲染过程,而不是 Unity 的宿主框架。
|
||||||
|
2. 不把 Unity 的 `ScriptableRenderPass`、资产导入器、Inspector 等编辑器逻辑带入本轮实现。
|
||||||
|
|
||||||
|
## 3. 首轮目标
|
||||||
|
|
||||||
|
首轮只完成以下闭环:
|
||||||
|
|
||||||
|
1. 在 `mvs/3DGS-D3D12` 内部加载 `room.ply`。
|
||||||
|
2. 将 PLY 中的高斯数据转换为无 chunk 的运行时缓冲。
|
||||||
|
3. 通过现有 `RHI` 完成 D3D12 路径下的最小渲染。
|
||||||
|
4. 输出一张稳定可观察的结果图,证明房间场景已经被正确绘制。
|
||||||
|
|
||||||
|
首轮验收标准:
|
||||||
|
|
||||||
|
1. 程序能独立编译与运行。
|
||||||
|
2. 不依赖修改 `engine` 才能成立。
|
||||||
|
3. 渲染结果不再是纯黑、纯白或明显错误的撕裂图。
|
||||||
|
4. 渲染链路中不存在任何 chunk 数据结构、chunk 缓冲或 chunk 可见性阶段。
|
||||||
|
|
||||||
|
## 4. 非目标
|
||||||
|
|
||||||
|
本轮明确不做:
|
||||||
|
|
||||||
|
1. 不并入 editor。
|
||||||
|
2. 不接入引擎现有 Renderer 主线。
|
||||||
|
3. 不做 OpenGL / Vulkan 多后端对齐。
|
||||||
|
4. 不做正式资源缓存格式。
|
||||||
|
5. 不做 chunk、cluster、LOD、streaming 等优化层。
|
||||||
|
6. 不做 compute 之外的额外架构扩展。
|
||||||
|
7. 不为迎合当前 MVS 去修改 `engine` 的 `RHI`、Renderer、Resources。
|
||||||
|
|
||||||
|
## 5. 约束与执行原则
|
||||||
|
|
||||||
|
### 5.1 目录约束
|
||||||
|
|
||||||
|
本轮新增内容应尽量收敛在 `mvs/3DGS-D3D12` 内,例如:
|
||||||
|
|
||||||
|
1. `src/`:程序入口、渲染器、相机、数据上传。
|
||||||
|
2. `include/`:本地头文件。
|
||||||
|
3. `shaders/`:本地 HLSL 或中间 shader 资源。
|
||||||
|
4. `assets/`:本地测试资源或生成物描述。
|
||||||
|
5. `third_party/`:仅当 MVS 自己确实需要额外小型依赖时使用。
|
||||||
|
|
||||||
|
### 5.2 RHI 使用原则
|
||||||
|
|
||||||
|
1. 只调用现有 `engine` 的 `RHI` 公共接口。
|
||||||
|
2. 如果发现最小系统缺失某项能力,优先在 MVS 内通过更简单的组织方式规避。
|
||||||
|
3. 若确实被 `RHI` 能力边界阻塞,先记录问题并汇报,不允许直接改 `engine`。
|
||||||
|
|
||||||
|
### 5.3 数据路径原则
|
||||||
|
|
||||||
|
1. 整个系统只保留 positions、other、color、SH 等无 chunk 基础数据。
|
||||||
|
2. 不生成 chunk header。
|
||||||
|
3. 不上传 chunk buffer。
|
||||||
|
4. 不做 visible chunk 标记。
|
||||||
|
5. 不保留任何为了兼容旧 chunk 方案而加的临时分支。
|
||||||
|
|
||||||
|
## 6. 技术路线
|
||||||
|
|
||||||
|
### Phase 1:梳理 3DGS-D3D12 的骨架与构建入口
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1. 确认 `mvs/3DGS-D3D12` 当前是否只有 `room.ply`。
|
||||||
|
2. 建立最小的可执行工程骨架。
|
||||||
|
3. 打通对 `engine` 中 `RHI` 的引用与链接。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 规划 `CMakeLists.txt` 与目录结构。
|
||||||
|
2. 建立窗口、设备、交换链、命令提交、离屏或在屏渲染的最小入口。
|
||||||
|
3. 跑通一个“清屏可显示”的基础版本。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
1. `3DGS-D3D12` 可独立编译。
|
||||||
|
2. 程序能启动并输出基础画面。
|
||||||
|
|
||||||
|
### Phase 2:实现无 chunk 的 PLY 读取与运行时数据打包
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1. 参考 `mvs/3DGS Unity Renderer`,在 MVS 内部完成 PLY 读取。
|
||||||
|
2. 输出适合渲染阶段直接上传的高斯原始数组。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 解析 PLY header 与顶点属性。
|
||||||
|
2. 提取 position、rotation、scale、opacity、color/SH 等字段。
|
||||||
|
3. 明确字段的排列顺序、类型与归一化方式。
|
||||||
|
4. 生成 MVS 本地 `GaussianSplatSceneData`。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
1. `room.ply` 可被成功读取。
|
||||||
|
2. 点数量、字段长度、边界盒等基础统计合理。
|
||||||
|
3. 整条导入链中完全没有 chunk 概念。
|
||||||
|
|
||||||
|
### Phase 3:对齐无 chunk 的 GPU 数据布局与上传
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1. 把导入结果上传到 GPU。
|
||||||
|
2. 数据布局尽量贴近 `mvs/3DGS-Unity` 的渲染输入。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 设计 positions / other / color / SH 的 GPU buffer。
|
||||||
|
2. 建立与着色器绑定一致的 SRV/UAV 视图。
|
||||||
|
3. 为排序与绘制准备 index、distance、view-data 等工作缓冲。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
1. GPU 侧所有关键缓冲都能正确创建。
|
||||||
|
2. 每个绑定槽位与 shader 语义一一对应。
|
||||||
|
3. 不包含 chunk buffer / visible chunk buffer。
|
||||||
|
|
||||||
|
### Phase 4:先打通 prepare 与 sort
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1. 先验证“高斯数据被相机看到并被正确排序”。
|
||||||
|
2. 在 draw 之前把中间结果可视化或可检查化。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 参考 `mvs/3DGS-Unity` 的 prepare 语义,计算每个 splat 的 view-space 信息。
|
||||||
|
2. 生成排序距离。
|
||||||
|
3. 跑通最小排序路径。
|
||||||
|
4. 必要时增加调试输出,用于检查 order / distance / 计数是否异常。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
1. prepare 结果不是全零或明显错误。
|
||||||
|
2. sort 输出索引顺序稳定。
|
||||||
|
|
||||||
|
### Phase 5:对齐 draw 与 composite
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1. 参考 `mvs/3DGS-Unity` 的“干净无 chunk 渲染路径”把核心画面先画出来。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 对齐 Gaussian splat draw shader 的主要输入输出语义。
|
||||||
|
2. 对齐椭圆展开、覆盖范围与透明混合约定。
|
||||||
|
3. 建立 accumulation target。
|
||||||
|
4. 建立 composite pass,把 accumulation 结果合回最终颜色。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
1. 输出图中能看出 `room.ply` 对应的房间结构。
|
||||||
|
2. 不出现整屏纯黑、纯白、随机撕裂。
|
||||||
|
|
||||||
|
### Phase 6:收口与验证
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1. 固化最小系统结果,形成稳定基线。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 规范运行命令与资源路径。
|
||||||
|
2. 输出一张固定命名的结果图作为检查基线。
|
||||||
|
3. 清理调试残留与临时分支。
|
||||||
|
4. 补一份 MVS 内局部说明文档。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
1. 代码、资源、着色器都收敛在 `mvs/3DGS-D3D12`。
|
||||||
|
2. 运行方式清晰。
|
||||||
|
3. 基线截图稳定。
|
||||||
|
|
||||||
|
## 7. 关键风险
|
||||||
|
|
||||||
|
### 7.1 PLY 属性语义与当前样例不一致
|
||||||
|
|
||||||
|
如果 `room.ply` 的字段命名、顺序或编码方式与参考加载器假设不一致,最先要修的是导入器映射,不是渲染器。
|
||||||
|
|
||||||
|
### 7.2 RHI 能力与参考实现存在接口差
|
||||||
|
|
||||||
|
如果 Unity 参考依赖的某些资源绑定或 compute 流程不能直接一比一照搬,本轮优先在 `mvs/3DGS-D3D12` 内部重排执行方式,而不是去动 `engine`。
|
||||||
|
|
||||||
|
### 7.3 排序与混合契约不一致
|
||||||
|
|
||||||
|
3DGS 最容易出错的不是“有没有画出来”,而是排序方向、alpha 累积与 composite 契约是否一致。本轮必须把这三者当成同一个问题处理,禁止分开打补丁。
|
||||||
|
|
||||||
|
## 8. 本轮完成后的下一步
|
||||||
|
|
||||||
|
当 `mvs/3DGS-D3D12` 的无 chunk 最小系统跑通后,下一步才有意义讨论:
|
||||||
|
|
||||||
|
1. 是否把这条路径收编进引擎正式 Renderer。
|
||||||
|
2. 是否把 PLY 导入升级成正式资源导入器。
|
||||||
|
3. 是否在引擎层补 structured buffer / compute / renderer pass 抽象。
|
||||||
|
4. 是否接入 editor 与资产缓存。
|
||||||
|
|
||||||
|
在这之前,所有工作都应服务于一个目标:
|
||||||
|
|
||||||
|
先在 `mvs/3DGS-D3D12` 内证明“无 chunk 的 3DGS-D3D12 + 现有 RHI”这条路是通的。
|
||||||
227
docs/plan/MainLight方向光阴影修复计划_2026-04-13.md
Normal file
227
docs/plan/MainLight方向光阴影修复计划_2026-04-13.md
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# MainLight 方向光阴影修复计划
|
||||||
|
|
||||||
|
日期: `2026-04-13`
|
||||||
|
|
||||||
|
## 1. 文档定位
|
||||||
|
|
||||||
|
这份文档是基于当前仓库代码状态重新整理的版本,用来承接 MainLight 单张方向光阴影的后续修复工作。
|
||||||
|
|
||||||
|
需要明确:
|
||||||
|
|
||||||
|
- 这不是对之前已删除计划文件的逐字恢复。
|
||||||
|
- 这是一份按当前实现状态、最近两次提交和现有阴影链路重新整理的执行计划。
|
||||||
|
- 当前目标不是上级联阴影,而是先把单张 MainLight 阴影做到可用。
|
||||||
|
|
||||||
|
## 2. 当前问题定义
|
||||||
|
|
||||||
|
当前 MainLight 阴影已经具备从规划、投射到接收采样的完整闭环,但实际效果仍然不可用,核心问题有两个:
|
||||||
|
|
||||||
|
1. 自阴影过重,表面出现明显 acne。
|
||||||
|
2. 阴影边缘呈现大块锯齿,采样质量不足。
|
||||||
|
|
||||||
|
当前阶段暂不解决:
|
||||||
|
|
||||||
|
- 级联阴影
|
||||||
|
- 点光 / 聚光阴影
|
||||||
|
- 阴影时域稳定与降噪的完整方案
|
||||||
|
- 多光源阴影编排
|
||||||
|
|
||||||
|
## 3. 当前阴影链路梳理
|
||||||
|
|
||||||
|
当前单张 MainLight 阴影主链如下:
|
||||||
|
|
||||||
|
`SceneRenderRequestPlanner`
|
||||||
|
-> `DirectionalShadowRenderPlan`
|
||||||
|
-> `CameraRenderer`
|
||||||
|
-> `ShadowCaster Pass`
|
||||||
|
-> `RenderSceneData::lighting.mainDirectionalShadow`
|
||||||
|
-> `BuiltinForwardPipeline`
|
||||||
|
-> `forward-lit.shader / Toon.shader`
|
||||||
|
|
||||||
|
关键代码位置:
|
||||||
|
|
||||||
|
- 规划层
|
||||||
|
- `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h`
|
||||||
|
- `engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp`
|
||||||
|
- 请求与运行时数据
|
||||||
|
- `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||||
|
- `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h`
|
||||||
|
- `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||||
|
- ShadowCaster 消费 bias
|
||||||
|
- `engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h`
|
||||||
|
- `engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp`
|
||||||
|
- `engine/assets/builtin/shaders/shadow-caster.shader`
|
||||||
|
- 接收端采样
|
||||||
|
- `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`
|
||||||
|
- `engine/assets/builtin/shaders/forward-lit.shader`
|
||||||
|
- `project/Assets/Shaders/Toon.shader`
|
||||||
|
|
||||||
|
## 4. 已完成阶段
|
||||||
|
|
||||||
|
### Phase 0:阴影参数契约正式化
|
||||||
|
|
||||||
|
已完成,目标是先把原来散落在不同位置的 shadow 参数收成正式数据契约。
|
||||||
|
|
||||||
|
对应提交:
|
||||||
|
|
||||||
|
- `2ee74e7` `rendering: formalize main light shadow params`
|
||||||
|
|
||||||
|
完成结果:
|
||||||
|
|
||||||
|
- MainLight shadow 的 map metrics / sampling 数据有了明确结构。
|
||||||
|
- 规划层到运行时数据链路不再依赖匿名 float 参数拼装。
|
||||||
|
|
||||||
|
### Phase 1:bias 设置正式化并接入 ShadowCaster
|
||||||
|
|
||||||
|
已完成,目标是把 caster bias 和 receiver bias 的职责拆清楚,并让 ShadowCaster 真正消费运行时设置。
|
||||||
|
|
||||||
|
对应提交:
|
||||||
|
|
||||||
|
- `1d6f2e2` `rendering: formalize main light shadow bias settings`
|
||||||
|
|
||||||
|
完成结果:
|
||||||
|
|
||||||
|
- 新增 `DirectionalShadowSamplingSettings`
|
||||||
|
- 新增 `DirectionalShadowCasterBiasSettings`
|
||||||
|
- planner 能输出 sampling / caster bias 默认值
|
||||||
|
- `ShadowCaster` pass 不再依赖 shader 内部写死的 `Offset`
|
||||||
|
- forward / toon 接收端继续消费统一的 shadow sampling contract
|
||||||
|
|
||||||
|
## 5. 根因判断
|
||||||
|
|
||||||
|
### 5.1 自阴影严重的根因
|
||||||
|
|
||||||
|
当前 acne 的根因不是单一问题,而是以下几项叠加:
|
||||||
|
|
||||||
|
1. caster bias 与 receiver bias 原先没有清晰契约,调参入口混乱。
|
||||||
|
2. receiver 端 normal bias 和 depth bias 默认值没有经过系统标定。
|
||||||
|
3. caster 端深度偏移之前由 shader 写死,无法和场景尺度、shadow map texel 尺度统一调整。
|
||||||
|
|
||||||
|
### 5.2 阴影锯齿严重的根因
|
||||||
|
|
||||||
|
当前接收端虽然已经做了一个固定 3x3 手写采样,但效果仍然偏糙,原因主要是:
|
||||||
|
|
||||||
|
1. 当前仍然是单张 shadow map,分辨率预算有限。
|
||||||
|
2. 阴影采样核是最基础的固定 3x3 box,没有更平滑的权重设计。
|
||||||
|
3. `BuiltinForwardPipeline` 里的 shadow sampler 目前仍是 point sampler。
|
||||||
|
4. 当前还没有专门面向“单张 MainLight 阴影可用性”的 filter 参数设计。
|
||||||
|
|
||||||
|
### 5.3 当前不应优先处理的方向
|
||||||
|
|
||||||
|
以下方向现在都不是第一优先级:
|
||||||
|
|
||||||
|
- 直接上级联阴影
|
||||||
|
- 直接引入更复杂的多光源阴影系统
|
||||||
|
- 先大改 planner 架构
|
||||||
|
|
||||||
|
原因很简单:如果当前单张阴影的 bias 和 filter 基线都没有站稳,上级方案只会把问题放大。
|
||||||
|
|
||||||
|
## 6. 下一阶段执行顺序
|
||||||
|
|
||||||
|
## Phase 2:建立可用的 bias 基线
|
||||||
|
|
||||||
|
这是下一步最高优先级。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 明显压住自阴影 acne
|
||||||
|
- 不引入明显 peter-panning
|
||||||
|
- 让不同材质和几何体至少达到“能看”的单张阴影结果
|
||||||
|
|
||||||
|
本阶段只调这四个核心参数:
|
||||||
|
|
||||||
|
- `DirectionalShadowCasterBiasSettings.depthBiasFactor`
|
||||||
|
- `DirectionalShadowCasterBiasSettings.depthBiasUnits`
|
||||||
|
- `DirectionalShadowSamplingSettings.receiverDepthBias`
|
||||||
|
- `DirectionalShadowSamplingSettings.normalBiasScale`
|
||||||
|
|
||||||
|
执行要点:
|
||||||
|
|
||||||
|
1. 先以默认值为基线做小步调参,不再混用 shader 内固定 offset。
|
||||||
|
2. 先看 caster bias 是否足以压掉大面积 acne,再看 receiver bias 是否还需要补偿。
|
||||||
|
3. `normalBiasScale` 只用于削减掠射角表面 acne,不能把它当成主修复手段。
|
||||||
|
4. 调参顺序优先保证“角色和常见静态模型表面不脏”,再控制阴影悬浮。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 球、立方体、角色模型等常见几何体上没有大片自阴影脏斑。
|
||||||
|
- 接触阴影没有整体漂浮一截。
|
||||||
|
- forward-lit 与 toon 两条接收路径结果一致性不回退。
|
||||||
|
|
||||||
|
## Phase 3:升级单张阴影采样质量
|
||||||
|
|
||||||
|
这个阶段才开始处理“大块锯齿”。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 让当前单张 MainLight shadow 的边缘从“块状锯齿”变成“有限预算下可接受的软化边缘”
|
||||||
|
|
||||||
|
优先处理项:
|
||||||
|
|
||||||
|
1. 升级当前固定 3x3 box 采样,不再停留在最基础平均核。
|
||||||
|
2. 明确是否继续走跨后端一致的手写 PCF,还是为 comparison sampler 单独补后端支持。
|
||||||
|
3. 如果继续保持跨后端一致性优先,则先做更好的手写 PCF 核,再评估 comparison sampler。
|
||||||
|
|
||||||
|
本阶段建议的最小落地方案:
|
||||||
|
|
||||||
|
- 先保留单张 shadow map
|
||||||
|
- 先保留当前主链结构
|
||||||
|
- 把 receiver 端阴影采样从固定 3x3 平均核升级为更稳定的 PCF 核
|
||||||
|
- 如有必要,再补 `filterRadiusInTexels` 一类的正式参数
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 阴影边缘不再呈现明显的 1-bit 台阶块状感。
|
||||||
|
- 角色和场景静态物体阴影边缘的可读性明显提升。
|
||||||
|
- 不因为滤波升级导致阴影整体发灰或漏光严重。
|
||||||
|
|
||||||
|
## Phase 4:单张方向光阴影稳定性收口
|
||||||
|
|
||||||
|
在 bias 和 filter 达到可用以后,再收口稳定性问题。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 降低相机轻微移动时的 shadow crawl / shimmering
|
||||||
|
|
||||||
|
重点项:
|
||||||
|
|
||||||
|
1. 审查当前 shadow camera 拟合结果是否需要 texel snapping。
|
||||||
|
2. 审查 ortho bounds 与 focus point 的稳定性。
|
||||||
|
3. 重新评估 `boundsPadding` / `minDepthPadding` / `minDepthRange` 的默认值是否过保守或过激进。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
这一阶段仍然是“单张 MainLight 阴影修好”,不是进入级联阴影。
|
||||||
|
|
||||||
|
## Phase 5:为后续级联阴影预留演进点
|
||||||
|
|
||||||
|
只有前面几个阶段都稳定后,才进入这一阶段。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 让当前单张 MainLight 阴影实现不阻塞未来 CSM 演进
|
||||||
|
|
||||||
|
需要预留但暂不展开的点:
|
||||||
|
|
||||||
|
- split 数据结构
|
||||||
|
- 每级 shadow map / sampler / matrix contract
|
||||||
|
- 接收端 cascade 选择逻辑
|
||||||
|
- 级联间过渡和稳定化
|
||||||
|
|
||||||
|
## 7. 推荐的提交切分
|
||||||
|
|
||||||
|
后续建议按下面的提交粒度推进:
|
||||||
|
|
||||||
|
1. `rendering: tune main light shadow bias defaults`
|
||||||
|
2. `rendering: improve main light shadow receiver filtering`
|
||||||
|
3. `rendering: stabilize single-map directional shadow fitting`
|
||||||
|
|
||||||
|
## 8. 当前结论
|
||||||
|
|
||||||
|
当前正确的下一步不是“直接做级联阴影”,而是:
|
||||||
|
|
||||||
|
1. 先把 bias 默认值标定到可用区间。
|
||||||
|
2. 再把当前接收端采样升级成真正可用的单张 shadow filter。
|
||||||
|
3. 最后再处理单张阴影稳定性。
|
||||||
|
|
||||||
|
只有这三步完成,当前 MainLight 阴影才算真正脱离“占位实现”。
|
||||||
180
docs/plan/NewEditor_宿主重构计划_2026-04-13.md
Normal file
180
docs/plan/NewEditor_宿主重构计划_2026-04-13.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# NewEditor 宿主重构计划
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把 `new_editor` 当前的窗口宿主从“功能可运行的过渡方案”收敛成可长期演进的 Editor 宿主架构,核心目标如下:
|
||||||
|
|
||||||
|
1. 主显示链最终统一到纯 D3D12。
|
||||||
|
2. 窗口线程只负责消息和状态,不承担 GPU 重活。
|
||||||
|
3. live resize 必须真实更新,但不能再走同步阻塞窗口线程的路径。
|
||||||
|
4. Editor shell 和 Scene/Game viewport 统一纳入宿主合成层。
|
||||||
|
5. 当前 `D3D11On12 + D2D` 只允许作为过渡路径,不能继续加深耦合。
|
||||||
|
|
||||||
|
## 当前问题
|
||||||
|
|
||||||
|
### 1. 宿主职责混在 `Application`
|
||||||
|
|
||||||
|
当前 `Application` 同时承担:
|
||||||
|
|
||||||
|
- Win32 消息调度
|
||||||
|
- 宿主运行时状态
|
||||||
|
- resize / dpi / deferred render 调度
|
||||||
|
- editor 状态更新
|
||||||
|
- present 前后的宿主控制
|
||||||
|
|
||||||
|
这会导致宿主问题难以单独定位和演进。
|
||||||
|
|
||||||
|
### 2. 主显示链过厚
|
||||||
|
|
||||||
|
当前主路径仍然依赖:
|
||||||
|
|
||||||
|
- D3D12 viewport 渲染
|
||||||
|
- swapchain backbuffer
|
||||||
|
- D3D11On12 wrapped resource
|
||||||
|
- D2D 绘制 shell
|
||||||
|
- present
|
||||||
|
|
||||||
|
这条链在 live resize、frame pacing、backbuffer 生命周期上都偏重。
|
||||||
|
|
||||||
|
### 3. resize 热路径仍然偏保守
|
||||||
|
|
||||||
|
虽然已经把 `WM_SIZE` 从直接同步 resize 调整为 deferred render 触发,但 resize 热路径里仍存在:
|
||||||
|
|
||||||
|
- backbuffer interop target 重建
|
||||||
|
- swapchain resize
|
||||||
|
- 资源生命周期收束
|
||||||
|
|
||||||
|
这还不是最终架构。
|
||||||
|
|
||||||
|
## 重构阶段
|
||||||
|
|
||||||
|
## 阶段 1:宿主分层
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
先把结构理顺,停止继续把宿主逻辑堆进 `Application`。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 拆出宿主运行时状态对象。
|
||||||
|
2. 拆出窗口消息调度与 deferred render 调度。
|
||||||
|
3. 让 `Application` 只保留 editor 业务更新与高层协作职责。
|
||||||
|
4. 为后续 HostRenderer / HostCompositor 留清晰边界。
|
||||||
|
|
||||||
|
### 完成标准
|
||||||
|
|
||||||
|
1. resize / dpi / deferred render / interactive resize 状态不再散落在 `Application` 成员里。
|
||||||
|
2. `Application` 的宿主状态字段明显减少。
|
||||||
|
3. 现有功能与布局不回退。
|
||||||
|
|
||||||
|
## 阶段 2:建立 HostRenderer / HostCompositor 边界
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
把宿主渲染拆成明确两层:
|
||||||
|
|
||||||
|
1. `HostRenderer`
|
||||||
|
责任:device / queue / swapchain / backbuffer / fence / present
|
||||||
|
2. `HostCompositor`
|
||||||
|
责任:把 shell draw data、viewport texture、icon/text 统一合成到 backbuffer
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 停止让 `NativeRenderer` 既像窗口绘制器又像过渡 compositor。
|
||||||
|
2. 把“主窗口显示”和“UI 绘制命令解释”职责显式拆开。
|
||||||
|
3. 为纯 D3D12 compositor 做接口准备。
|
||||||
|
|
||||||
|
### 完成标准
|
||||||
|
|
||||||
|
1. `HostRenderer` 不依赖 D2D 语义。
|
||||||
|
2. `HostCompositor` 成为唯一的宿主 UI 合成入口。
|
||||||
|
3. 现有 `NativeRenderer` 明确退化为过渡层或 fallback。
|
||||||
|
|
||||||
|
## 阶段 3:主显示链切换到纯 D3D12
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
去掉 `D3D11On12 + D2D` 在主显示链中的核心地位。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. shell 矩形、线条、图像、文字统一进入 D3D12 UI compositor。
|
||||||
|
2. Scene/Game viewport 作为普通 SRV 输入参与同一条 compositor pass。
|
||||||
|
3. backbuffer 只通过 D3D12 呈现。
|
||||||
|
|
||||||
|
### 完成标准
|
||||||
|
|
||||||
|
1. 主窗口显示不再依赖 D2D。
|
||||||
|
2. resize 时不再重建 D3D11On12 backbuffer interop target。
|
||||||
|
3. `new_editor` 主显示链可在没有 D3D11On12 的条件下工作。
|
||||||
|
|
||||||
|
## 阶段 4:重写 resize 状态机
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
做到真实 live resize,但不阻塞窗口线程。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. `WM_SIZE` 只更新最新目标尺寸。
|
||||||
|
2. render tick 只消费最新尺寸,不处理过期尺寸。
|
||||||
|
3. resize 不允许在消息处理里做 GPU 等待。
|
||||||
|
4. resize / present / viewport surface 生命周期统一到宿主状态机。
|
||||||
|
|
||||||
|
### 完成标准
|
||||||
|
|
||||||
|
1. 拖动窗口边界时不黑屏。
|
||||||
|
2. 不出现黑白垃圾区。
|
||||||
|
3. 窗口拖动体感明显优于当前实现。
|
||||||
|
|
||||||
|
## 阶段 5:去掉 resize 路径里的全队列等待
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
去掉当前最重的同步点。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 改成 per-backbuffer / per-frame 生命周期管理。
|
||||||
|
2. 只等待必须退休的资源代际。
|
||||||
|
3. 禁止 resize 路径里的整队列 idle 等待。
|
||||||
|
|
||||||
|
### 完成标准
|
||||||
|
|
||||||
|
1. resize 期间 CPU/GPU 同步明显减少。
|
||||||
|
2. live resize 手感继续改善。
|
||||||
|
|
||||||
|
## 阶段 6:viewport 生命周期收口
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
让 Scene/Game viewport 与外层宿主真正解耦。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. viewport render target 长期存活,不跟着宿主链大拆大建。
|
||||||
|
2. 宿主 compositor 只采样 viewport 结果,不干预其内部资源生命周期。
|
||||||
|
3. Scene/Game 在 interactive resize 期间保持稳定。
|
||||||
|
|
||||||
|
### 完成标准
|
||||||
|
|
||||||
|
1. viewport 不再因宿主 resize 逻辑出现黑屏或闪烁。
|
||||||
|
2. 宿主和 viewport 各自职责明确。
|
||||||
|
|
||||||
|
## 执行顺序
|
||||||
|
|
||||||
|
1. 先完成阶段 1。
|
||||||
|
2. 然后搭好阶段 2 的 HostRenderer / HostCompositor 边界。
|
||||||
|
3. 再推进阶段 3,切主显示链到纯 D3D12。
|
||||||
|
4. 最后做阶段 4、5、6 的性能与生命周期收口。
|
||||||
|
|
||||||
|
## 当前落点
|
||||||
|
|
||||||
|
当前阶段 1 已完成,阶段 2 已开始收口:
|
||||||
|
|
||||||
|
1. 已新增 `HostRuntimeState`,把宿主 DPI / interactive resize / pending resize / deferred render 状态从 `Application` 中抽离。
|
||||||
|
2. 已新增 `WindowMessageDispatcher`,把 `WndProc` 中的宿主消息调度与 deferred render 调度拆到 `Host` 层。
|
||||||
|
3. 已把 `D3D12WindowRenderLoop` 从悬空 helper 升级为主窗口帧编排入口,开始统一 `BeginFrame / viewport render / UI present / fallback / resize interop` 这条链。
|
||||||
|
4. 已把 viewport render target 资源工厂从 `ProductViewportRenderTargets.h` 收成独立 manager,`ProductViewportHostService` 只保留请求/返回 frame 的业务外壳。
|
||||||
|
5. 已把 shader resource descriptor 分配职责从 `D3D12WindowRenderer` 抽到独立的 `D3D12ShaderResourceDescriptorAllocator`,减少窗口呈现器的非 swapchain 职责。
|
||||||
|
6. 下一步进入阶段 2 主体:继续拆 `NativeRenderer` 中的窗口互操作 / present 组合路径,把 D2D UI 光栅化与 D3D11On12 窗口合成进一步分离。
|
||||||
283
docs/plan/NewEditor_方案1无边框宿主采用计划_2026-04-14.md
Normal file
283
docs/plan/NewEditor_方案1无边框宿主采用计划_2026-04-14.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# NewEditor 方案1无边框宿主采用计划
|
||||||
|
|
||||||
|
日期: `2026-04-14`
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
|
||||||
|
把 `new_editor` 从当前的原生 `WS_OVERLAPPEDWINDOW + DWM + HWND swapchain` 宿主模式,重构为:
|
||||||
|
|
||||||
|
- 无边框顶层窗口
|
||||||
|
- 自绘标题栏与边框
|
||||||
|
- 应用自己接管 move / resize / maximize / restore / hit-test
|
||||||
|
- 宿主渲染链与窗口尺寸变化由同一套状态机驱动
|
||||||
|
|
||||||
|
核心目的只有一个:
|
||||||
|
|
||||||
|
尽可能消除当前原生窗口 live resize 期间那种“旧帧被系统先拉伸,再等新帧补上”的视觉变形。
|
||||||
|
|
||||||
|
## 2. 为什么必须走这条路
|
||||||
|
|
||||||
|
当前路径的问题不是 XCUI 布局树本身,而是宿主显示链顺序决定的:
|
||||||
|
|
||||||
|
1. Windows 先改变原生窗口外框尺寸。
|
||||||
|
2. DWM 立即需要一张图去填新窗口区域。
|
||||||
|
3. `new_editor` 再收到 `WM_SIZE`,之后才开始:
|
||||||
|
- `ResizeBuffers`
|
||||||
|
- backbuffer / interop target 处理
|
||||||
|
- UI + viewport 合成
|
||||||
|
- `Present`
|
||||||
|
4. 这中间只要慢一个 compositor tick,就会看到旧尺寸帧被拉伸。
|
||||||
|
|
||||||
|
只要继续使用原生 non-client resize,这个问题就只能减轻,不能彻底归零。
|
||||||
|
|
||||||
|
## 3. 方案1的总思路
|
||||||
|
|
||||||
|
不要再让系统驱动 resize 交互。
|
||||||
|
|
||||||
|
改为:
|
||||||
|
|
||||||
|
1. 窗口本身使用无边框样式。
|
||||||
|
2. 顶部标题栏、拖动区、8 个 resize grip 全部放到 client area。
|
||||||
|
3. 鼠标拖动边界时,不进入系统的 `WM_ENTERSIZEMOVE` 模态循环。
|
||||||
|
4. 应用自己维护一套 `WindowFrameController`:
|
||||||
|
- 记录拖动起点
|
||||||
|
- 计算目标外框矩形
|
||||||
|
- 先驱动宿主渲染链切到目标尺寸
|
||||||
|
- 再提交窗口矩形变化与新帧 present
|
||||||
|
|
||||||
|
这样窗口尺寸变化、swapchain 尺寸变化、UI 布局变化、present 节奏都由应用自己掌控,而不是被 Windows 原生外框拆成两段。
|
||||||
|
|
||||||
|
## 4. 重构边界
|
||||||
|
|
||||||
|
这次重构只动 `new_editor/app/Host` 宿主层,不把业务逻辑塞进去。
|
||||||
|
|
||||||
|
### 4.1 保留不动的层
|
||||||
|
|
||||||
|
- `XCEditor` 基础 UI 组件层
|
||||||
|
- `new_editor/app/Workspace`
|
||||||
|
- `new_editor/app/Panels`
|
||||||
|
- `Viewport` 业务层
|
||||||
|
|
||||||
|
### 4.2 主要改造层
|
||||||
|
|
||||||
|
- [Application.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Application.cpp)
|
||||||
|
- [Application.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Application.h)
|
||||||
|
- [WindowMessageDispatcher.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/WindowMessageDispatcher.cpp)
|
||||||
|
- [WindowMessageDispatcher.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/WindowMessageDispatcher.h)
|
||||||
|
- [HostRuntimeState.h](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/HostRuntimeState.h)
|
||||||
|
- [D3D12WindowRenderLoop.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowRenderLoop.cpp)
|
||||||
|
- [D3D12WindowRenderer.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowRenderer.cpp)
|
||||||
|
- [D3D12WindowSwapChainPresenter.cpp](D:/Xuanchi/Main/XCEngine/new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp)
|
||||||
|
|
||||||
|
### 4.3 新增宿主对象
|
||||||
|
|
||||||
|
建议新增以下文件:
|
||||||
|
|
||||||
|
- `new_editor/app/Host/WindowFrameMetrics.h`
|
||||||
|
- `new_editor/app/Host/WindowFrameController.h`
|
||||||
|
- `new_editor/app/Host/WindowFrameController.cpp`
|
||||||
|
- `new_editor/app/Host/WindowFrameInteractionState.h`
|
||||||
|
- `new_editor/app/Host/BorderlessWindowStyle.h`
|
||||||
|
|
||||||
|
职责划分:
|
||||||
|
|
||||||
|
- `WindowFrameMetrics`
|
||||||
|
- 统一管理标题栏高度、resize 边缘厚度、阴影扩展、caption button 区域
|
||||||
|
- `WindowFrameInteractionState`
|
||||||
|
- 记录当前是否在 move / resize
|
||||||
|
- 记录激活边、起始窗口矩形、起始鼠标屏幕坐标
|
||||||
|
- `WindowFrameController`
|
||||||
|
- 处理 hit-test、开始拖动、更新拖动、结束拖动
|
||||||
|
- 计算目标窗口矩形
|
||||||
|
- 输出“本帧宿主需要切到什么尺寸”
|
||||||
|
- `BorderlessWindowStyle`
|
||||||
|
- 集中处理 `WS_POPUP / WS_THICKFRAME / WS_CAPTION` 等样式组合与 DWM 扩展
|
||||||
|
|
||||||
|
## 5. 分阶段执行
|
||||||
|
|
||||||
|
## 阶段 A:把窗口宿主从原生外框切到无边框
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
先完成“视觉上还是一个正常窗口,但 non-client 已经不再由系统绘制”。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 创建窗口时移除 `WS_OVERLAPPEDWINDOW`,改为适合无边框的样式组合。
|
||||||
|
2. 顶部工具栏正式承担标题栏职责。
|
||||||
|
3. 处理:
|
||||||
|
- 最小化
|
||||||
|
- 最大化
|
||||||
|
- 还原
|
||||||
|
- 关闭
|
||||||
|
4. 保留 Windows 阴影、任务栏行为、Alt+Tab 行为。
|
||||||
|
|
||||||
|
### 验收
|
||||||
|
|
||||||
|
1. 窗口外框由 XCUI 自己绘制。
|
||||||
|
2. 顶部区域可拖动移动窗口。
|
||||||
|
3. 基本窗口控制按钮可用。
|
||||||
|
|
||||||
|
## 阶段 B:接管 hit-test 与 8 向 resize 手势
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
不再依赖系统 non-client resize。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 在 client area 内定义:
|
||||||
|
- 左 / 右 / 上 / 下
|
||||||
|
- 左上 / 右上 / 左下 / 右下
|
||||||
|
的 resize 热区。
|
||||||
|
2. 鼠标 hover 时稳定切换对应 cursor。
|
||||||
|
3. 鼠标按下后进入 `WindowFrameInteractionState`。
|
||||||
|
4. 鼠标移动时由 `WindowFrameController` 计算目标外框矩形。
|
||||||
|
|
||||||
|
### 验收
|
||||||
|
|
||||||
|
1. 8 个方向 resize 都能工作。
|
||||||
|
2. cursor 不闪烁。
|
||||||
|
3. 不再进入 `WM_ENTERSIZEMOVE / WM_EXITSIZEMOVE` 原生模态链。
|
||||||
|
|
||||||
|
## 阶段 C:把窗口尺寸变化改成“应用驱动”
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
让窗口矩形变化和新尺寸帧提交进入同一条应用控制链。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 取消当前“完全依赖 `WM_SIZE` 才处理 resize”的模式。
|
||||||
|
2. 鼠标拖动过程中,先由 `WindowFrameController` 产出目标 client size。
|
||||||
|
3. 宿主渲染层按目标 size:
|
||||||
|
- resize swapchain
|
||||||
|
- rebuild backbuffer view
|
||||||
|
- 重新布局 UI
|
||||||
|
- render + present
|
||||||
|
4. 然后再提交窗口矩形更新。
|
||||||
|
|
||||||
|
这里的关键不是“永远先后顺序绝对固定”,而是宿主要从“被动响应 Windows resize”改成“主动推进目标尺寸帧”。
|
||||||
|
|
||||||
|
### 验收
|
||||||
|
|
||||||
|
1. live resize 期间不再有明显的整窗拉伸。
|
||||||
|
2. 内部 UI 不再跟随旧帧一起被整体放大缩小。
|
||||||
|
3. resize 时不黑屏、不闪退。
|
||||||
|
|
||||||
|
## 阶段 D:把 Scene / Game viewport 也纳入同一节奏
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
避免窗口外层尺寸变化和 viewport target 更新脱节。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 宿主 resize 状态机输出统一尺寸变更事件。
|
||||||
|
2. `ProductViewportHostService` 消费同一目标尺寸。
|
||||||
|
3. viewport render target 与 host swapchain 的 resize 节奏统一。
|
||||||
|
|
||||||
|
### 验收
|
||||||
|
|
||||||
|
1. 调外层窗口尺寸时,Scene / Game 内部画布不再滞后。
|
||||||
|
2. 不再出现外层变了、内部蓝底画布慢一拍才追上的情况。
|
||||||
|
|
||||||
|
## 阶段 E:补齐无边框窗口的系统行为
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
把“能用”补到“能长期替代正式编辑器宿主”。
|
||||||
|
|
||||||
|
### 任务
|
||||||
|
|
||||||
|
1. 双击标题栏最大化 / 还原。
|
||||||
|
2. 拖到屏幕顶部最大化。
|
||||||
|
3. 屏幕边缘吸附。
|
||||||
|
4. 多显示器 DPI / work area 正确处理。
|
||||||
|
5. 最小窗口尺寸约束。
|
||||||
|
6. 系统菜单与快捷键兼容。
|
||||||
|
|
||||||
|
### 验收
|
||||||
|
|
||||||
|
1. 宿主交互接近常规桌面应用。
|
||||||
|
2. 不因无边框而丢失基本桌面体验。
|
||||||
|
|
||||||
|
## 6. 架构原则
|
||||||
|
|
||||||
|
### 6.1 不允许把窗口交互逻辑混回 `Application`
|
||||||
|
|
||||||
|
`Application` 只能做:
|
||||||
|
|
||||||
|
- 应用生命周期
|
||||||
|
- 编辑器业务装配
|
||||||
|
- 驱动一帧更新与渲染
|
||||||
|
|
||||||
|
窗口移动、边框 hit-test、resize 手势状态机都必须沉淀到 `Host`。
|
||||||
|
|
||||||
|
### 6.2 不允许为了“先跑通”堆事件补丁
|
||||||
|
|
||||||
|
禁止继续沿这些方向堆补丁:
|
||||||
|
|
||||||
|
- `WM_SIZE` 后疯狂补 render
|
||||||
|
- `ValidateRect` / `InvalidateRect` 来回试
|
||||||
|
- `DwmFlush` 之类的消息尾部补丁
|
||||||
|
- 在 resize 过程中乱加全局 GPU idle
|
||||||
|
|
||||||
|
这些都只能缓解,不能把宿主控制权拿回来。
|
||||||
|
|
||||||
|
### 6.3 宿主渲染链必须单向清晰
|
||||||
|
|
||||||
|
目标链路必须收敛成:
|
||||||
|
|
||||||
|
`WindowFrameController -> Host resize state -> swapchain/backbuffer resize -> UI + viewport compose -> present`
|
||||||
|
|
||||||
|
而不是:
|
||||||
|
|
||||||
|
`Windows non-client -> WM_SIZE -> 若干临时补丁 -> 侥幸赶上 compositor`
|
||||||
|
|
||||||
|
## 7. 风险点
|
||||||
|
|
||||||
|
### 7.1 这不是小修
|
||||||
|
|
||||||
|
这是宿主交互模型切换,不是单点 bugfix。
|
||||||
|
|
||||||
|
### 7.2 需要额外补齐桌面窗口语义
|
||||||
|
|
||||||
|
无边框之后,很多系统行为都要自己接:
|
||||||
|
|
||||||
|
- caption drag
|
||||||
|
- 双击最大化
|
||||||
|
- hit-test
|
||||||
|
- snap
|
||||||
|
- monitor work area
|
||||||
|
- DPI 变更
|
||||||
|
|
||||||
|
### 7.3 需要严格做阶段验收
|
||||||
|
|
||||||
|
每一阶段都必须编译并人工验证,不然很容易把宿主架构搞乱。
|
||||||
|
|
||||||
|
## 8. 推荐执行顺序
|
||||||
|
|
||||||
|
1. 阶段 A:无边框窗口切换
|
||||||
|
2. 阶段 B:hit-test / resize 手势
|
||||||
|
3. 阶段 C:应用驱动 resize 主链
|
||||||
|
4. 阶段 D:viewport resize 节奏统一
|
||||||
|
5. 阶段 E:补系统级桌面行为
|
||||||
|
|
||||||
|
## 9. 收口标准
|
||||||
|
|
||||||
|
满足以下条件,才算方案1真正落地:
|
||||||
|
|
||||||
|
1. `new_editor` 已彻底脱离原生 non-client resize。
|
||||||
|
2. 窗口拖边 resize 时,整窗拉伸变形基本消失。
|
||||||
|
3. Scene / Game viewport 尺寸变化与外层窗口同步。
|
||||||
|
4. resize 过程中无黑屏、无崩溃、无 cursor 闪烁。
|
||||||
|
5. 顶部标题栏、最大化、吸附、DPI 行为达到正式编辑器可用水平。
|
||||||
|
|
||||||
|
## 10. 结论
|
||||||
|
|
||||||
|
如果目标是“尽可能彻底消除 resize 变形”,就不该继续在当前原生外框链路上修修补补。
|
||||||
|
|
||||||
|
正确主线是:
|
||||||
|
|
||||||
|
把 `new_editor` 宿主改成无边框、自管标题栏、自管 resize、自管 present 节奏的 editor shell。
|
||||||
310
docs/plan/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md
Normal file
310
docs/plan/Renderer_C++层第一阶段收口计划_2026-04-13_晚间.md
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
# Renderer C++层第一阶段收口计划
|
||||||
|
|
||||||
|
日期: `2026-04-13`
|
||||||
|
|
||||||
|
## 1. 文档定位
|
||||||
|
|
||||||
|
这份文档是 `Render Graph` 之前的最后一轮 `native render kernel v1` 收口计划。
|
||||||
|
|
||||||
|
目标不是继续往当前 `Rendering` 层里塞新功能,而是先把下面几件事彻底做清楚:
|
||||||
|
|
||||||
|
- `request -> frame plan -> execution` 三层语义分开。
|
||||||
|
- `RenderSceneExtractor -> CullingResults -> RendererList -> DrawSettings` 这条数据组织链正式化。
|
||||||
|
- `BuiltinForwardPipeline` 从“大杂烩渲染器”收成“协调器 + 子能力”。
|
||||||
|
- `Editor` 注入点、阴影、主场景、后处理之间的边界说清楚。
|
||||||
|
- 给下一步 `C++ Render Graph` 留出稳定输入,而不是把当前混乱直接 graph 化。
|
||||||
|
|
||||||
|
结论先写在前面:
|
||||||
|
|
||||||
|
- `Render Graph` 应该做在 `C++ native` 层。
|
||||||
|
- 现在还不应该直接开做 `Render Graph` 或 `Deferred Renderer`。
|
||||||
|
- 当前更优先的是把现有渲染层整理干净,再往 SRP/URP 方向演进。
|
||||||
|
|
||||||
|
## 2. 当前阶段已经完成的收口
|
||||||
|
|
||||||
|
截至 `2026-04-13` 晚间,已经落地的内容:
|
||||||
|
|
||||||
|
- `CameraRenderRequest -> CameraFramePlan` 已经分层,`CameraRenderer` 开始正式消费 plan。
|
||||||
|
- `CullingResults / RendererList` contract 已经进入 `RenderSceneData`。
|
||||||
|
- `Forward / Depth / ObjectId` 已经优先走 `RendererList`,旧 `visibleItems` 只保留过渡 fallback。
|
||||||
|
- `RendererListUtils` 已经把可见项遍历规则收口成公共工具。
|
||||||
|
- `Gaussian / Volumetric` 已经从 `BuiltinForwardPipeline` 中抽成统一 `SceneRenderFeaturePass`。
|
||||||
|
- `FrameExecutionContext / ScenePhase / DrawSettings` 已经引入。
|
||||||
|
- `RenderPipeline` 已经支持新的 `FrameExecutionContext` 入口,旧三参数入口保留为兼容适配层。
|
||||||
|
- `BuiltinForwardPipeline` 已经开始按 scene phase 组织主场景执行。
|
||||||
|
- `CameraRenderer` 主场景阶段已经走新的 execution context,而不是继续只传三参数。
|
||||||
|
|
||||||
|
本轮验证结果:
|
||||||
|
|
||||||
|
- `XCEditor` 已重新编译通过。
|
||||||
|
- `rendering_unit_tests` 已重新编译通过。
|
||||||
|
- `editor_tests` 已重新编译通过。
|
||||||
|
- `editor_tests --gtest_filter=*ViewportRenderFlow*:*SceneViewportRenderPassBundle* --gtest_brief=1` 通过 `14/14`。
|
||||||
|
- `rendering_unit_tests --gtest_filter=*BuiltinForwardPipeline*:*RenderPass*:*CameraSceneRenderer*:*Shadow*:*ObjectId* --gtest_brief=1` 通过 `67/68`。
|
||||||
|
- 唯一失败仍然是既有老问题:
|
||||||
|
`BuiltinForwardPipeline_Test.OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions`
|
||||||
|
|
||||||
|
## 3. 为什么现在还不能直接上 Render Graph
|
||||||
|
|
||||||
|
现在直接上 `Render Graph`,本质上是在 graph 里继续承接当前的隐式耦合,问题只会换个位置存在。
|
||||||
|
|
||||||
|
当前还没完全收口的核心问题不是“没有 graph”,而是下面这些边界还不够正式:
|
||||||
|
|
||||||
|
- `CameraRenderer` 和各类 scene pass 之间,谁负责组织阶段,谁负责执行,还没有完全模块化。
|
||||||
|
- `Shadow` 相关逻辑仍然分散在 planner、camera execution、pipeline、surface cache 几处。
|
||||||
|
- `BuiltinForwardPipeline` 虽然已经开始瘦身,但还没有彻底收成一个明确的 coordinator。
|
||||||
|
- `Editor` 的扩展点虽然已可用,但 contract 还偏“约定式”,还不够像以后给 `C# SRP` 暴露的正式接口。
|
||||||
|
- 目录结构仍然在暴露旧历史:一些“子系统”目前还是文件堆,不是真正的模块。
|
||||||
|
|
||||||
|
如果这些问题不先解决,`Render Graph` 只会变成一个新的承压层,后面再拆更贵。
|
||||||
|
|
||||||
|
## 4. Render Graph 和未来 SRP/URP 的边界
|
||||||
|
|
||||||
|
未来的推荐结构仍然是这条线:
|
||||||
|
|
||||||
|
`RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline`
|
||||||
|
|
||||||
|
其中边界应当明确成这样:
|
||||||
|
|
||||||
|
- `C++ native` 保留:
|
||||||
|
- `Render Graph`
|
||||||
|
- `CullingResults / RendererList`
|
||||||
|
- draw / dispatch context
|
||||||
|
- GPU resource cache
|
||||||
|
- shadow map / atlas 执行能力
|
||||||
|
- Gaussian 排序与 working set
|
||||||
|
- Volume 资源上传与底层执行
|
||||||
|
- `URP-like` 包层保留:
|
||||||
|
- feature 编排
|
||||||
|
- pass 注入顺序
|
||||||
|
- renderer feature 配置
|
||||||
|
- 用户级 pipeline 组合
|
||||||
|
- 未来的 `C#` 自定义渲染管线 API
|
||||||
|
|
||||||
|
这意味着:
|
||||||
|
|
||||||
|
- 阴影、Gaussian、Volumetric 不是整块搬去包层。
|
||||||
|
- 包层更像 Unity 的 `URP renderer` 和 `renderer feature`。
|
||||||
|
- native 层更像 Unity 没公开给用户、但真正负责执行和图资源生命周期的内核。
|
||||||
|
|
||||||
|
## 5. 第一阶段还差哪些工作才能收口
|
||||||
|
|
||||||
|
### 5.1 Shadow 子系统正式化
|
||||||
|
|
||||||
|
这是当前最值得先做的一块。
|
||||||
|
|
||||||
|
现状问题:
|
||||||
|
|
||||||
|
- 阴影规划在 `Planning`。
|
||||||
|
- 阴影 surface/cache 在 `Execution/Caches`。
|
||||||
|
- 阴影执行又是 standalone pass。
|
||||||
|
- 主场景采样阴影的状态切换仍在 `BuiltinForwardPipeline`。
|
||||||
|
|
||||||
|
这说明阴影现在“能跑”,但还不是一个正式子系统。
|
||||||
|
|
||||||
|
收口目标:
|
||||||
|
|
||||||
|
- 明确 `shadow planning / shadow resources / shadow execution / shadow sampling contract` 四层边界。
|
||||||
|
- 把方向光阴影相关共享数据收成一组正式类型,而不是散落在多个调用点上。
|
||||||
|
- 为以后扩展 `cascades / shadow atlas / additional light shadows / baked shadowmask` 留出位置。
|
||||||
|
|
||||||
|
### 5.2 Editor 注入点正式化
|
||||||
|
|
||||||
|
现状问题:
|
||||||
|
|
||||||
|
- 当前有 `pre scene / post scene / overlay / object id / outline / grid` 多种 editor 路径。
|
||||||
|
- 这些能力已经能工作,但还没有统一成“正式 stage 注入 contract”。
|
||||||
|
|
||||||
|
收口目标:
|
||||||
|
|
||||||
|
- 明确哪些注入点属于 runtime 正式阶段,哪些属于 editor-only extension。
|
||||||
|
- 让 `CameraFramePlan` 对这些阶段的描述更加稳定。
|
||||||
|
- 给以后 `C# renderer feature` 的注入点设计提供 native 对照物。
|
||||||
|
|
||||||
|
### 5.3 BuiltinForwardPipeline 继续瘦身
|
||||||
|
|
||||||
|
本轮已经完成第一刀,但还没收完。
|
||||||
|
|
||||||
|
还需要继续做的事情:
|
||||||
|
|
||||||
|
- 把 forward scene phase 执行和底层 draw/resource 逻辑彻底分开。
|
||||||
|
- 把 forward renderer 自己的内部资源、skybox、feature 协调代码进一步收拢。
|
||||||
|
- 保证 `BuiltinForwardPipeline` 自己只表达“阶段顺序和协调”,不再继续膨胀。
|
||||||
|
|
||||||
|
### 5.4 旧 fallback 的裁边
|
||||||
|
|
||||||
|
当前有一些过渡兼容层是故意保留的:
|
||||||
|
|
||||||
|
- `RenderPipeline::Render(context, surface, sceneData)` 旧入口
|
||||||
|
- `visibleItems` fallback
|
||||||
|
- `BuildRenderRequests` 旧习惯入口
|
||||||
|
|
||||||
|
这些现在不该硬删,因为会影响现有 tests 和 editor 链路。
|
||||||
|
|
||||||
|
但第一阶段收口前,至少要做到:
|
||||||
|
|
||||||
|
- 新主链默认只走正式 contract。
|
||||||
|
- 旧入口只作为 adapter,不再新增依赖。
|
||||||
|
- 每个 fallback 都有明确退出条件。
|
||||||
|
|
||||||
|
### 5.5 测试和文档补齐
|
||||||
|
|
||||||
|
当前已有回归还不够系统。
|
||||||
|
|
||||||
|
还需要补的重点测试:
|
||||||
|
|
||||||
|
- `FrameExecutionContext` 是否完整透传 source surface / source color view / state。
|
||||||
|
- `BuiltinForwardPipeline` scene phase 顺序是否稳定。
|
||||||
|
- feature pass 的 `Prepare -> Execute` 顺序是否稳定。
|
||||||
|
- editor 注入点在有后处理和无后处理两种情况下是否仍能保持正确 source/destination surface。
|
||||||
|
- shadow planning 和 main scene shadow sampling 之间的契约测试。
|
||||||
|
|
||||||
|
## 6. 下一步最优先该做什么
|
||||||
|
|
||||||
|
下一步最优先建议做:
|
||||||
|
|
||||||
|
`Shadow 子系统收口`
|
||||||
|
|
||||||
|
原因很直接:
|
||||||
|
|
||||||
|
- 它横跨 `Planning / Execution / Pipeline / Resources / Sampling`。
|
||||||
|
- 它是以后 `Deferred / Light Baking / ShadowMask / Additional Light Shadows` 的共同基础。
|
||||||
|
- 它也是未来 `Render Graph` 最敏感的一类资源依赖链。
|
||||||
|
- 如果阴影先不收,后面 graph 化时 barrier、resource lifetime、pass dependency 都会更乱。
|
||||||
|
|
||||||
|
建议顺序:
|
||||||
|
|
||||||
|
1. 先把方向光阴影的 plan/data/resource/execute contract 固化。
|
||||||
|
2. 再把 editor 注入 contract 固化。
|
||||||
|
3. 然后做第一轮目录收拢。
|
||||||
|
4. 最后判断这一阶段是否可以正式收口,进入 `Render Graph` 设计。
|
||||||
|
|
||||||
|
## 7. 当前 Rendering 目录结构存在的问题
|
||||||
|
|
||||||
|
### 7.1 `FrameData` 目录太杂
|
||||||
|
|
||||||
|
现在 `FrameData` 里同时混着:
|
||||||
|
|
||||||
|
- camera/environment 数据
|
||||||
|
- scene snapshot
|
||||||
|
- visible item
|
||||||
|
- culling results
|
||||||
|
- renderer list utils
|
||||||
|
|
||||||
|
这说明它不是一个明确模块,而是“跟帧有关的先放这”。
|
||||||
|
|
||||||
|
建议方向:
|
||||||
|
|
||||||
|
- `FrameData` 保留纯只读帧快照类型。
|
||||||
|
- 把 `CullingResults / RendererList / Filtering / Sorting / DrawSettings` 逐步收进更明确的 `Execution` 或 `Culling` 子域。
|
||||||
|
- `RendererListUtils` 这种执行辅助逻辑,不适合长期留在 `FrameData`。
|
||||||
|
|
||||||
|
### 7.2 `Passes` 目录混合了三类东西
|
||||||
|
|
||||||
|
现在 `Passes` 里同时有:
|
||||||
|
|
||||||
|
- 主场景 pass
|
||||||
|
- fullscreen/post-process pass
|
||||||
|
- editor pass
|
||||||
|
|
||||||
|
这会让“runtime 正式渲染能力”和“editor 辅助渲染能力”继续混在一起。
|
||||||
|
|
||||||
|
建议方向:
|
||||||
|
|
||||||
|
- `Passes/Scene`
|
||||||
|
- `Passes/Fullscreen`
|
||||||
|
- `Passes/Editor`
|
||||||
|
- `Passes/Shadow`
|
||||||
|
|
||||||
|
不一定要一次性全搬,但后面新增文件不应继续平铺。
|
||||||
|
|
||||||
|
### 7.3 `BuiltinForwardPipeline` 还没形成真正子模块
|
||||||
|
|
||||||
|
当前 forward 相关代码已经分散在:
|
||||||
|
|
||||||
|
- `BuiltinForwardPipeline.h/.cpp`
|
||||||
|
- `BuiltinForwardPipelineSkybox.cpp`
|
||||||
|
- `Internal/BuiltinForwardPipelineResources.cpp`
|
||||||
|
|
||||||
|
这已经具备“应该单独成目录”的条件。
|
||||||
|
|
||||||
|
建议方向:
|
||||||
|
|
||||||
|
- `Rendering/Pipelines/BuiltinForward/`
|
||||||
|
- `BuiltinForwardPipeline.h`
|
||||||
|
- `BuiltinForwardPipeline.cpp`
|
||||||
|
- `BuiltinForwardPipelineScene.cpp`
|
||||||
|
- `BuiltinForwardPipelineResources.cpp`
|
||||||
|
- `BuiltinForwardPipelineSkybox.cpp`
|
||||||
|
|
||||||
|
这样后面 forward / deferred / universal renderer 才能并列演进。
|
||||||
|
|
||||||
|
### 7.4 `Shadow` 目录还不算真正的 shadow 子系统
|
||||||
|
|
||||||
|
目前虽然已经有 `Rendering/Shadow`,但从全链路看,阴影的责任仍然散在其他目录。
|
||||||
|
|
||||||
|
建议方向:
|
||||||
|
|
||||||
|
- `Shadow` 目录最终至少容纳:
|
||||||
|
- planning types
|
||||||
|
- shared frame data
|
||||||
|
- runtime resources / caches
|
||||||
|
- shadow executor or shadow renderer
|
||||||
|
|
||||||
|
### 7.5 `Internal` 边界不稳定
|
||||||
|
|
||||||
|
现在 `src/Rendering/Internal` 更像历史遗留缓冲区。
|
||||||
|
|
||||||
|
长期风险:
|
||||||
|
|
||||||
|
- 真正属于 forward 的内部实现放在全局 `Internal`
|
||||||
|
- 真正属于 volume / splat / shadow 的内部实现也可能继续堆进去
|
||||||
|
|
||||||
|
建议方向:
|
||||||
|
|
||||||
|
- 全局 `Internal` 只保留跨模块公共 helper。
|
||||||
|
- 各子系统自己的内部文件放回各自目录。
|
||||||
|
|
||||||
|
## 8. 建议的第一轮目录收拢动作
|
||||||
|
|
||||||
|
这一轮只做低风险收拢,不做大搬家。
|
||||||
|
|
||||||
|
建议动作:
|
||||||
|
|
||||||
|
- 把 `BuiltinForwardPipeline*` 相关实现收成 `Pipelines/BuiltinForward/` 子目录。
|
||||||
|
- 给 `Passes` 至少先分出 `Editor` 和 `Fullscreen` 两个子目录。
|
||||||
|
- 给 `Shadow` 补一组正式 shared types,再决定是否移动更多实现文件。
|
||||||
|
- 暂时不大动 `include` 层 public 路径,先保证编译和引用稳定。
|
||||||
|
|
||||||
|
原则:
|
||||||
|
|
||||||
|
- 先按职责拆,再按目录搬。
|
||||||
|
- 不为了“好看”做纯目录手术。
|
||||||
|
- 每次收拢都必须带编译和回归。
|
||||||
|
|
||||||
|
## 9. 第一阶段收口完成的判定标准
|
||||||
|
|
||||||
|
满足下面这些条件,就可以认为 `Render Graph` 之前这一阶段基本收口:
|
||||||
|
|
||||||
|
- `CameraFramePlan` 成为主执行入口,旧 request 入口只保留兼容壳。
|
||||||
|
- `FrameExecutionContext / ScenePhase / DrawSettings` 成为主场景执行正式 contract。
|
||||||
|
- `CullingResults / RendererList` 成为主要 draw organization contract。
|
||||||
|
- `BuiltinForwardPipeline` 被收成 coordinator,而不是继续长成总垃圾堆。
|
||||||
|
- `Shadow` 成为清晰子系统,而不是散点逻辑。
|
||||||
|
- `Editor` 注入点形成稳定 contract。
|
||||||
|
- 目录结构不再继续鼓励“新逻辑直接往老文件里塞”。
|
||||||
|
- 相关 tests 和编译链稳定。
|
||||||
|
|
||||||
|
到这一步,再开始 `C++ Render Graph`,代价才是可控的。
|
||||||
|
|
||||||
|
## 10. 当前建议
|
||||||
|
|
||||||
|
当前最佳路线不变:
|
||||||
|
|
||||||
|
- 短期:继续按 Unity 的 `SRP + URP` 方向做 native 基座。
|
||||||
|
- 中期:先在 `C++` 做出稳定的 `Render Graph + RendererList + Feature Injection` 内核。
|
||||||
|
- 中长期:再暴露 `C#` 自定义渲染管线接口,做你自己的 `URP-like` 包层。
|
||||||
|
|
||||||
|
眼下最正确的动作不是“赶紧补 deferred”,也不是“先把所有特效搬去包层”,而是:
|
||||||
|
|
||||||
|
`先把当前 native rendering layer 整理成一个真正可扩展的内核。`
|
||||||
462
docs/plan/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md
Normal file
462
docs/plan/Renderer_C++层第一阶段重构计划_RenderGraph前_2026-04-13.md
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
# Renderer C++层第一阶段重构计划(Render Graph 前)
|
||||||
|
|
||||||
|
日期:`2026-04-13`
|
||||||
|
|
||||||
|
## 1. 文档定位
|
||||||
|
|
||||||
|
这份计划只处理 `Render Graph` 之前的那一步,也就是把当前 `Rendering` 原生主链先整理成一个可长期演进的正式架构。
|
||||||
|
|
||||||
|
这一步的目标不是新增大功能,而是先把下面这些边界收口:
|
||||||
|
|
||||||
|
1. `SceneRenderer / CameraRenderer / SceneRenderRequestPlanner` 的职责边界
|
||||||
|
2. `RenderSceneExtractor / RenderSceneData / VisibleRenderItem` 的数据边界
|
||||||
|
3. `RenderPipeline / RenderPass / BuiltinForwardPipeline` 的执行边界
|
||||||
|
4. Runtime 主链与 Editor 注入点的边界
|
||||||
|
|
||||||
|
这一步做完之后,下一阶段才适合引入真正的 `Render Graph`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 关于 Render Graph 的结论
|
||||||
|
|
||||||
|
结论很明确:
|
||||||
|
|
||||||
|
1. `Render Graph` 应该先做在 `C++ native` 层
|
||||||
|
2. 它不应该直接作为第一阶段的一部分
|
||||||
|
3. 第一阶段的任务,是先把当前 native renderer 收口到足够清晰,避免后面只是把现有混乱机械搬进 graph
|
||||||
|
|
||||||
|
后面的目标结构应该是:
|
||||||
|
|
||||||
|
`RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
1. `Render Graph` 属于 `Native Render Kernel`
|
||||||
|
2. `C#` 层以后可以编排 pass,但图资源生命周期、资源状态、barrier、跨 pass 读写依赖仍应先由 native graph 掌控
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 当前代码里的真实主链
|
||||||
|
|
||||||
|
当前项目并不是没有架构,而是已经形成了一条手工编排的 mini-SRP 主链:
|
||||||
|
|
||||||
|
1. `SceneRenderRequestPlanner`
|
||||||
|
- 收集 camera
|
||||||
|
- 生成 `CameraRenderRequest`
|
||||||
|
- 负责方向光阴影规划参数
|
||||||
|
2. `SceneRenderer`
|
||||||
|
- 调 planner
|
||||||
|
- 解析 final color policy
|
||||||
|
- 为 post-process / final-output 附加 fullscreen stage request
|
||||||
|
3. `CameraRenderer`
|
||||||
|
- 做 shadow request resolve
|
||||||
|
- 提取 `RenderSceneData`
|
||||||
|
- 按固定 stage 顺序执行
|
||||||
|
4. `BuiltinForwardPipeline`
|
||||||
|
- 负责主场景绘制
|
||||||
|
- 同时背着 shader 绑定、PSO 缓存、材质资源解析、skybox、splat、volumetric 等大量职责
|
||||||
|
|
||||||
|
对应的当前入口主要是:
|
||||||
|
|
||||||
|
1. `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h`
|
||||||
|
2. `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h`
|
||||||
|
3. `engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h`
|
||||||
|
4. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||||
|
5. `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h`
|
||||||
|
6. `engine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h`
|
||||||
|
7. `engine/include/XCEngine/Rendering/RenderPipeline.h`
|
||||||
|
8. `engine/include/XCEngine/Rendering/RenderPipelineAsset.h`
|
||||||
|
9. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
||||||
|
10. `engine/src/Rendering/Execution/SceneRenderer.cpp`
|
||||||
|
11. `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||||
|
12. `engine/src/Rendering/Extraction/RenderSceneExtractor.cpp`
|
||||||
|
13. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`
|
||||||
|
14. `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 当前真正的问题
|
||||||
|
|
||||||
|
当前主要不是“功能太少”,而是“职责层次还不够正式”。
|
||||||
|
|
||||||
|
### 4.1 request、plan、execution 混在一起
|
||||||
|
|
||||||
|
`CameraRenderRequest` 现在既像外部请求,又像内部执行计划,还夹带了一部分 runtime surface / fullscreen chain 组织信息。
|
||||||
|
`SceneRenderer` 也同时承担了 request build、final color resolve、fullscreen intermediate surface ownership 这几类事情。
|
||||||
|
|
||||||
|
这样的问题是:
|
||||||
|
|
||||||
|
1. 后面很难区分“用户想渲染什么”和“引擎决定怎么执行”
|
||||||
|
2. Editor 也会继续直接依赖底层 stage 细节
|
||||||
|
3. Render Graph 以后很难接到一个稳定的 frame plan 输入
|
||||||
|
|
||||||
|
### 4.2 extraction 数据还偏早期
|
||||||
|
|
||||||
|
`RenderSceneExtractor` 现在能工作,但产物仍然偏“把可见对象收集成数组”。
|
||||||
|
它还没有正式长成后续 SRP / Render Graph 真正需要的那种中间层:
|
||||||
|
|
||||||
|
1. `CullingResults`
|
||||||
|
2. `RendererList`
|
||||||
|
3. `FilteringSettings`
|
||||||
|
4. `SortingSettings`
|
||||||
|
5. `DrawSettings`
|
||||||
|
|
||||||
|
如果这层不先正式化,后面 forward、deferred、shadow、object-id、editor overlay 都会继续各自拿 `visibleItems` 做自己的隐式解释。
|
||||||
|
|
||||||
|
### 4.3 BuiltinForwardPipeline 过重
|
||||||
|
|
||||||
|
当前 `BuiltinForwardPipeline` 不只是一个 renderer,它已经同时包含:
|
||||||
|
|
||||||
|
1. 主场景 pass 编排
|
||||||
|
2. 材质资源布局解析
|
||||||
|
3. descriptor set 组织
|
||||||
|
4. pipeline state cache
|
||||||
|
5. lighting / shadow 绑定
|
||||||
|
6. skybox 逻辑
|
||||||
|
7. gaussian splat 特殊路径
|
||||||
|
8. volumetric 特殊路径
|
||||||
|
|
||||||
|
这会带来两个后果:
|
||||||
|
|
||||||
|
1. 后面任何主场景新能力都会继续往这个类里堆
|
||||||
|
2. 未来要抽 `Universal Renderer` 或切出 `Deferred` 时,拆分成本会很高
|
||||||
|
|
||||||
|
### 4.4 runtime 主线和 editor 扩展点还没正式隔离
|
||||||
|
|
||||||
|
现在 Editor 已经深度依赖 request stage 注入点,这个方向本身没错,但 contract 还不够正式。
|
||||||
|
如果不先收口,后面 graph 化时 editor 特殊路径会变成额外复杂度来源。
|
||||||
|
|
||||||
|
### 4.5 pipeline contract 还不够像未来的 SRP 底座
|
||||||
|
|
||||||
|
当前 `RenderPipeline` 还是:
|
||||||
|
|
||||||
|
`Render(context, surface, sceneData)`
|
||||||
|
|
||||||
|
这个接口对当前 builtin forward 足够,但对未来这些东西不够:
|
||||||
|
|
||||||
|
1. renderer list
|
||||||
|
2. graph resource declaration
|
||||||
|
3. per-frame context
|
||||||
|
4. pass feature 注入
|
||||||
|
5. 多渲染路径共存
|
||||||
|
|
||||||
|
第一阶段不要求一步到位,但至少要把它朝这个方向整理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 本阶段明确不做的事
|
||||||
|
|
||||||
|
为了保证收口不失控,这一阶段明确不做下面这些:
|
||||||
|
|
||||||
|
1. 不引入真正运行时 `Render Graph`
|
||||||
|
2. 不做完整 `Deferred Renderer`
|
||||||
|
3. 不做 `C# SRP` 暴露
|
||||||
|
4. 不重写 RHI
|
||||||
|
5. 不新增大型渲染特性来打断架构收口
|
||||||
|
|
||||||
|
本阶段只做一件事:
|
||||||
|
|
||||||
|
`把当前 native rendering layer 整理成 Render Graph 之前的正式形态`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 第一阶段完成后应该达到什么状态
|
||||||
|
|
||||||
|
本阶段完成后,native renderer 至少应满足下面这些条件:
|
||||||
|
|
||||||
|
1. 外部 request、内部 frame plan、实际 execution 三层语义分开
|
||||||
|
2. extraction / culling / draw organization 有正式中间层,不再只靠裸 `visibleItems`
|
||||||
|
3. `BuiltinForwardPipeline` 明显瘦身,不再继续当总垃圾桶
|
||||||
|
4. runtime pass 与 editor pass 有明确注入 contract
|
||||||
|
5. 后续 `Render Graph` 能接一个稳定的 `Frame Plan + Renderer Lists + Resource Needs` 输入
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 第一阶段的重构原则
|
||||||
|
|
||||||
|
### 7.1 先 formalize,再 graphize
|
||||||
|
|
||||||
|
先把 contract 和边界做清楚,再做 graph。
|
||||||
|
不要反过来。
|
||||||
|
|
||||||
|
### 7.2 不破坏现有主链闭环
|
||||||
|
|
||||||
|
当前 `Scene / Game / Editor Viewport` 已经能跑通,第一阶段不能为了“好看”把现有可运行链条打散。
|
||||||
|
|
||||||
|
### 7.3 新中间层优先 native internal
|
||||||
|
|
||||||
|
这一阶段新补的 `Frame Plan`、`CullingResults`、`RendererList`、`DrawSettings` 都先是 C++ 内部契约,不急着暴露给 C#。
|
||||||
|
|
||||||
|
### 7.4 先解耦职责,再决定目录长相
|
||||||
|
|
||||||
|
不要先做大范围目录搬家。
|
||||||
|
先把职责拆开,目录只是最后的外显结果。
|
||||||
|
|
||||||
|
### 7.5 editor contract 正式化,但不去特殊化主链
|
||||||
|
|
||||||
|
Editor 的 object-id、outline、grid、overlay 依然要保留,但它们应该依附在正式 contract 上,而不是继续共享 runtime 的隐式细节。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 工作包拆分
|
||||||
|
|
||||||
|
## 工作包 A:request / frame plan 分层
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
把“用户想渲染什么”和“引擎这帧怎么执行”拆开。
|
||||||
|
|
||||||
|
建议结果:
|
||||||
|
|
||||||
|
1. 保留 `CameraRenderRequest` 作为外部请求描述
|
||||||
|
2. 新增内部 `CameraFramePlan` 或等价类型,承载:
|
||||||
|
- 实际启用的 stage
|
||||||
|
- fullscreen chain
|
||||||
|
- resolved target/surface
|
||||||
|
- 阴影计划结果
|
||||||
|
- editor 注入点
|
||||||
|
3. `SceneRenderer` 只负责:
|
||||||
|
- 请求收集
|
||||||
|
- 请求排序
|
||||||
|
- request -> frame plan 转换
|
||||||
|
4. `CameraRenderer` 只负责:
|
||||||
|
- frame plan 执行
|
||||||
|
- 调 scene extraction
|
||||||
|
- 调 pipeline / standalone pass
|
||||||
|
|
||||||
|
优先涉及文件:
|
||||||
|
|
||||||
|
1. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||||
|
2. `engine/include/XCEngine/Rendering/Execution/SceneRenderer.h`
|
||||||
|
3. `engine/src/Rendering/Execution/SceneRenderer.cpp`
|
||||||
|
4. `engine/include/XCEngine/Rendering/Execution/CameraRenderer.h`
|
||||||
|
5. `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||||
|
|
||||||
|
完成标准:
|
||||||
|
|
||||||
|
1. request 不再混入过多 runtime-owned intermediate state
|
||||||
|
2. frame plan 成为 camera 执行的正式输入
|
||||||
|
3. SceneRenderer 和 CameraRenderer 的边界可以一句话说清
|
||||||
|
|
||||||
|
## 工作包 B:scene extraction -> culling results / renderer list
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
把当前 `RenderSceneExtractor` 的数组式输出,推进为后续 SRP/RenderGraph 可用的原生中间层。
|
||||||
|
|
||||||
|
建议结果:
|
||||||
|
|
||||||
|
1. 在 native 层引入等价于以下概念的类型:
|
||||||
|
- `CullingResults`
|
||||||
|
- `RendererListDesc`
|
||||||
|
- `RendererList`
|
||||||
|
- `FilteringSettings`
|
||||||
|
- `SortingSettings`
|
||||||
|
- `DrawSettings`
|
||||||
|
2. `RenderSceneData` 不再承担过多“什么都往里塞”的职责
|
||||||
|
3. `VisibleRenderItem` 继续保留为底层记录,但不再直接成为所有 pass 的唯一公共输入
|
||||||
|
4. shadow、main scene、object-id 等路径逐步改为消费 `RendererList`
|
||||||
|
|
||||||
|
优先涉及文件:
|
||||||
|
|
||||||
|
1. `engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h`
|
||||||
|
2. `engine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h`
|
||||||
|
3. `engine/src/Rendering/Extraction/RenderSceneExtractor.cpp`
|
||||||
|
4. `engine/include/XCEngine/Rendering/Extraction/`
|
||||||
|
5. `engine/include/XCEngine/Rendering/Builtin/`
|
||||||
|
|
||||||
|
完成标准:
|
||||||
|
|
||||||
|
1. opaque、transparent、shadow、object-id 至少能共用同一套 draw organization contract
|
||||||
|
2. 后面新增 deferred 时,不需要再发明第二套“可见对象数组解释逻辑”
|
||||||
|
|
||||||
|
## 工作包 C:pipeline contract 收口
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
把当前 `RenderPipeline / RenderPass / RenderPipelineAsset` 收口到更像未来 SRP native kernel 的形态。
|
||||||
|
|
||||||
|
建议结果:
|
||||||
|
|
||||||
|
1. 继续保留 `RenderPipelineAsset -> RenderPipeline` 这条总方向
|
||||||
|
2. 明确区分:
|
||||||
|
- pipeline asset:配置与默认策略
|
||||||
|
- pipeline runtime:本帧执行
|
||||||
|
- standalone pass:独立功能 pass
|
||||||
|
3. 给主场景 pipeline 引入更正式的执行输入,而不是继续只吃 `surface + sceneData`
|
||||||
|
4. 为下一阶段 graph 化预留清晰输入面:
|
||||||
|
- frame context
|
||||||
|
- renderer lists
|
||||||
|
- per-frame resources
|
||||||
|
|
||||||
|
优先涉及文件:
|
||||||
|
|
||||||
|
1. `engine/include/XCEngine/Rendering/RenderPipeline.h`
|
||||||
|
2. `engine/include/XCEngine/Rendering/RenderPipelineAsset.h`
|
||||||
|
3. `engine/include/XCEngine/Rendering/RenderPass.h`
|
||||||
|
4. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
||||||
|
|
||||||
|
完成标准:
|
||||||
|
|
||||||
|
1. 主 pipeline 输入面能明显映射到未来 `Universal Renderer`
|
||||||
|
2. standalone pass 与 main scene pipeline 的边界清楚
|
||||||
|
|
||||||
|
## 工作包 D:BuiltinForwardPipeline 瘦身
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
把 `BuiltinForwardPipeline` 从“大一统实现类”拆成可长期维护的 renderer 内部模块。
|
||||||
|
|
||||||
|
建议结果:
|
||||||
|
|
||||||
|
1. 按职责切成内部模块或 helper:
|
||||||
|
- main scene draw
|
||||||
|
- skybox draw
|
||||||
|
- lighting binding
|
||||||
|
- shadow binding
|
||||||
|
- material binding
|
||||||
|
- pipeline state cache
|
||||||
|
- per-frame resource cache
|
||||||
|
2. `GaussianSplat` 和 `Volumetric` 继续保留,但从“主 pipeline 杂糅逻辑”变成清晰的 feature-style 子模块
|
||||||
|
3. `BuiltinForwardPipelineResources.cpp` 里和运行时编排无关的资源/绑定组织进一步下沉
|
||||||
|
|
||||||
|
优先涉及文件:
|
||||||
|
|
||||||
|
1. `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
||||||
|
2. `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp`
|
||||||
|
3. `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp`
|
||||||
|
4. `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h`
|
||||||
|
|
||||||
|
完成标准:
|
||||||
|
|
||||||
|
1. `BuiltinForwardPipeline` 主类显著变薄
|
||||||
|
2. 新增一种主场景能力时,不需要继续把所有逻辑堆回同一个 cpp
|
||||||
|
|
||||||
|
## 工作包 E:editor 注入点正式化
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
保留当前 editor 渲染能力,但把它从“依赖阶段细节”变成“依赖正式 extension contract”。
|
||||||
|
|
||||||
|
建议结果:
|
||||||
|
|
||||||
|
1. 继续保留:
|
||||||
|
- object-id
|
||||||
|
- outline
|
||||||
|
- grid
|
||||||
|
- overlay
|
||||||
|
2. 把这些能力明确分成:
|
||||||
|
- runtime stage
|
||||||
|
- editor-only extension stage
|
||||||
|
3. 收口 `RenderPassSequence` 在 request 上的使用边界,避免后续 graph 化时出现“谁都能往 request 里塞东西”的状态
|
||||||
|
|
||||||
|
优先涉及文件:
|
||||||
|
|
||||||
|
1. `engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h`
|
||||||
|
2. `editor/src/Viewport/SceneViewportRenderPlan.h`
|
||||||
|
3. `editor/src/Viewport/`
|
||||||
|
4. `engine/src/Rendering/Execution/CameraRenderer.cpp`
|
||||||
|
|
||||||
|
完成标准:
|
||||||
|
|
||||||
|
1. Editor 仍能完整接入当前主链
|
||||||
|
2. 但 editor 不再直接绑死 runtime 内部实现细节
|
||||||
|
|
||||||
|
## 工作包 F:测试与文档同步
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
把第一阶段重构做成可回归、可交接的正式收口,而不是一次只靠手工验证的大重排。
|
||||||
|
|
||||||
|
建议结果:
|
||||||
|
|
||||||
|
1. 补 `tests/Rendering/unit`
|
||||||
|
2. 保底回归现有 `tests/Rendering/integration`
|
||||||
|
3. 必要时补 editor viewport 级验证
|
||||||
|
4. 更新 API 文档与后续计划入口
|
||||||
|
|
||||||
|
完成标准:
|
||||||
|
|
||||||
|
1. request / extractor / pipeline 边界都有对应测试覆盖
|
||||||
|
2. 当前 forward、shadow、object-id、editor scene view 不回退
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 执行顺序
|
||||||
|
|
||||||
|
必须按下面的顺序推进:
|
||||||
|
|
||||||
|
1. 先做工作包 A
|
||||||
|
2. 再做工作包 B
|
||||||
|
3. 再做工作包 C
|
||||||
|
4. 然后做工作包 D
|
||||||
|
5. 接着做工作包 E
|
||||||
|
6. 最后统一做工作包 F
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
1. 不先分清 request 和 frame plan,后面所有 contract 都会悬空
|
||||||
|
2. 不先把 extraction 推进到 `renderer list` 层,pipeline 收口只是表面整理
|
||||||
|
3. 不先把 pipeline contract 稳住,`BuiltinForwardPipeline` 的瘦身会缺少稳定目标
|
||||||
|
4. 不先把 native 主线收紧,editor contract 很容易继续沿着旧细节扩张
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 建议新增或调整的 native 类型
|
||||||
|
|
||||||
|
第一阶段不要求名字完全按这个来,但建议至少出现这些等价层:
|
||||||
|
|
||||||
|
1. `CameraFramePlan`
|
||||||
|
2. `FrameExecutionContext`
|
||||||
|
3. `CullingResults`
|
||||||
|
4. `RendererListDesc`
|
||||||
|
5. `RendererList`
|
||||||
|
6. `DrawSettings`
|
||||||
|
7. `FilteringSettings`
|
||||||
|
8. `SortingSettings`
|
||||||
|
9. `EditorRenderExtensions` 或等价 editor 注入描述
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
1. 这些类型先是 `C++ internal contract`
|
||||||
|
2. 这一阶段不急着桥接到 `managed`
|
||||||
|
3. 下一阶段 `Render Graph` 会直接消费其中一部分
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 这一阶段完成的验收标准
|
||||||
|
|
||||||
|
当以下条件同时成立时,这一阶段才算完成:
|
||||||
|
|
||||||
|
1. `SceneRenderer / CameraRenderer / Planner` 三者职责边界稳定
|
||||||
|
2. native 渲染主链内部已经形成 request、frame plan、execution 三层
|
||||||
|
3. `RenderSceneExtractor` 不再只是输出裸数组,而是能支持正式的 draw organization
|
||||||
|
4. `BuiltinForwardPipeline` 不再承担明显超载职责
|
||||||
|
5. editor 的注入点已经正规化,且没有破坏当前 Scene View / Game View 能力
|
||||||
|
6. 现有 forward、shadow、post-process、final-output、object-id、overlay 主路径回归通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 下一阶段如何接 Render Graph
|
||||||
|
|
||||||
|
等本阶段完成后,下一阶段才进入真正的 `Render Graph`。
|
||||||
|
|
||||||
|
那时的接法应该是:
|
||||||
|
|
||||||
|
1. `CameraFramePlan` 提供本帧逻辑阶段与 feature 需求
|
||||||
|
2. `CullingResults / RendererList` 提供 draw 输入
|
||||||
|
3. `FrameExecutionContext` 提供本帧统一资源上下文
|
||||||
|
4. `Render Graph` 负责:
|
||||||
|
- pass declaration
|
||||||
|
- resource creation/import
|
||||||
|
- read/write dependency
|
||||||
|
- barrier / lifetime
|
||||||
|
- transient resource reuse
|
||||||
|
|
||||||
|
也就是说,`Render Graph` 不是来替代第一阶段,而是建立在第一阶段之上。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 一句话结论
|
||||||
|
|
||||||
|
当前最佳路线不是立刻补 `Deferred`,也不是立刻补 `Render Graph`,而是先把你现有这条 native rendering 主链整理成真正的 `Render Kernel v1`;这一步做实了,后面的 `Render Graph`、`Universal Renderer`、`C# SRP` 才会接得稳。
|
||||||
@@ -32,12 +32,16 @@ if(NOT TARGET XCEngine)
|
|||||||
option(XCENGINE_ENABLE_MONO_SCRIPTING "Build the Mono-based C# scripting runtime" ON)
|
option(XCENGINE_ENABLE_MONO_SCRIPTING "Build the Mono-based C# scripting runtime" ON)
|
||||||
|
|
||||||
if(NOT DEFINED XCENGINE_MONO_ROOT_DIR OR XCENGINE_MONO_ROOT_DIR STREQUAL "")
|
if(NOT DEFINED XCENGINE_MONO_ROOT_DIR OR XCENGINE_MONO_ROOT_DIR STREQUAL "")
|
||||||
|
if(EXISTS "${XCENGINE_ROOT_DIR}/engine/third_party/mono/binary/mscorlib.dll")
|
||||||
|
set(XCENGINE_MONO_ROOT_DIR_DEFAULT "${XCENGINE_ROOT_DIR}/engine/third_party/mono")
|
||||||
|
else()
|
||||||
file(GLOB XCENGINE_MONO_ROOT_DIR_CANDIDATES "${XCENGINE_ROOT_DIR}/*/Fermion/Fermion/external/mono")
|
file(GLOB XCENGINE_MONO_ROOT_DIR_CANDIDATES "${XCENGINE_ROOT_DIR}/*/Fermion/Fermion/external/mono")
|
||||||
if(XCENGINE_MONO_ROOT_DIR_CANDIDATES)
|
if(XCENGINE_MONO_ROOT_DIR_CANDIDATES)
|
||||||
list(GET XCENGINE_MONO_ROOT_DIR_CANDIDATES 0 XCENGINE_MONO_ROOT_DIR_DEFAULT)
|
list(GET XCENGINE_MONO_ROOT_DIR_CANDIDATES 0 XCENGINE_MONO_ROOT_DIR_DEFAULT)
|
||||||
else()
|
else()
|
||||||
set(XCENGINE_MONO_ROOT_DIR_DEFAULT "${XCENGINE_ROOT_DIR}/managed/mono")
|
set(XCENGINE_MONO_ROOT_DIR_DEFAULT "${XCENGINE_ROOT_DIR}/managed/mono")
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
set(
|
set(
|
||||||
XCENGINE_MONO_ROOT_DIR
|
XCENGINE_MONO_ROOT_DIR
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ std::filesystem::path FindBundledMonoRootDirectory(const std::filesystem::path&
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path projectBundledMonoRoot = repositoryRoot / "engine" / "third_party" / "mono";
|
||||||
|
if (std::filesystem::exists(projectBundledMonoRoot / "binary" / "mscorlib.dll", ec)) {
|
||||||
|
return projectBundledMonoRoot.lexically_normal();
|
||||||
|
}
|
||||||
|
|
||||||
for (std::filesystem::directory_iterator it(repositoryRoot, ec), end; it != end && !ec; it.increment(ec)) {
|
for (std::filesystem::directory_iterator it(repositoryRoot, ec), end; it != end && !ec; it.increment(ec)) {
|
||||||
if (ec || !it->is_directory(ec)) {
|
if (ec || !it->is_directory(ec)) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -101,18 +101,18 @@ inline SceneViewportRenderPlanBuildResult BuildSceneViewportRenderPlan(
|
|||||||
inline void ApplySceneViewportRenderPlan(
|
inline void ApplySceneViewportRenderPlan(
|
||||||
const ViewportRenderTargets& targets,
|
const ViewportRenderTargets& targets,
|
||||||
SceneViewportRenderPlan& plan,
|
SceneViewportRenderPlan& plan,
|
||||||
Rendering::CameraRenderRequest& request) {
|
Rendering::CameraFramePlan& framePlan) {
|
||||||
ApplySceneViewportRenderRequestSetup(
|
ApplySceneViewportRenderRequestSetup(
|
||||||
targets,
|
targets,
|
||||||
&plan.postScenePasses,
|
&plan.postScenePasses,
|
||||||
request);
|
framePlan);
|
||||||
|
|
||||||
if (plan.HasOverlayPasses()) {
|
if (plan.HasOverlayPasses()) {
|
||||||
request.overlayPasses = &plan.overlayPasses;
|
framePlan.overlayPasses = &plan.overlayPasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.hasClearColorOverride = plan.hasClearColorOverride;
|
framePlan.request.hasClearColorOverride = plan.hasClearColorOverride;
|
||||||
request.clearColorOverride = plan.clearColorOverride;
|
framePlan.request.clearColorOverride = plan.clearColorOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "ViewportHostRenderTargets.h"
|
#include "ViewportHostRenderTargets.h"
|
||||||
|
|
||||||
#include <XCEngine/Core/Math/Color.h>
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -142,31 +142,31 @@ inline SceneViewportSelectionOutlineStyle BuildSceneViewportSelectionOutlineStyl
|
|||||||
inline void ApplySceneViewportRenderRequestSetup(
|
inline void ApplySceneViewportRenderRequestSetup(
|
||||||
const ViewportRenderTargets& targets,
|
const ViewportRenderTargets& targets,
|
||||||
Rendering::RenderPassSequence* postPasses,
|
Rendering::RenderPassSequence* postPasses,
|
||||||
Rendering::CameraRenderRequest& request) {
|
Rendering::CameraFramePlan& plan) {
|
||||||
request.preScenePasses = nullptr;
|
plan.preScenePasses = nullptr;
|
||||||
request.postScenePasses = nullptr;
|
plan.postScenePasses = nullptr;
|
||||||
request.overlayPasses = nullptr;
|
plan.overlayPasses = nullptr;
|
||||||
request.objectId = {};
|
plan.request.objectId = {};
|
||||||
|
|
||||||
if (postPasses != nullptr && postPasses->GetPassCount() > 0) {
|
if (postPasses != nullptr && postPasses->GetPassCount() > 0) {
|
||||||
request.postScenePasses = postPasses;
|
plan.postScenePasses = postPasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targets.objectIdView == nullptr || targets.objectIdDepthView == nullptr) {
|
if (targets.objectIdView == nullptr || targets.objectIdDepthView == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.objectId.surface = BuildViewportObjectIdSurface(targets);
|
plan.request.objectId.surface = BuildViewportObjectIdSurface(targets);
|
||||||
request.objectId.surface.SetRenderArea(request.surface.GetRenderArea());
|
plan.request.objectId.surface.SetRenderArea(plan.request.surface.GetRenderArea());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void MarkSceneViewportRenderSuccess(
|
inline void MarkSceneViewportRenderSuccess(
|
||||||
ViewportRenderTargets& targets,
|
ViewportRenderTargets& targets,
|
||||||
const Rendering::CameraRenderRequest& request) {
|
const Rendering::CameraFramePlan& plan) {
|
||||||
targets.colorState = RHI::ResourceStates::PixelShaderResource;
|
targets.colorState = RHI::ResourceStates::PixelShaderResource;
|
||||||
targets.objectIdState = RHI::ResourceStates::PixelShaderResource;
|
targets.objectIdState = RHI::ResourceStates::PixelShaderResource;
|
||||||
targets.selectionMaskState = RHI::ResourceStates::PixelShaderResource;
|
targets.selectionMaskState = RHI::ResourceStates::PixelShaderResource;
|
||||||
targets.hasValidObjectIdFrame = request.objectId.IsRequested();
|
targets.hasValidObjectIdFrame = plan.request.objectId.IsRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void MarkGameViewportRenderSuccess(ViewportRenderTargets& targets) {
|
inline void MarkGameViewportRenderSuccess(ViewportRenderTargets& targets) {
|
||||||
|
|||||||
@@ -591,9 +591,9 @@ private:
|
|||||||
SceneViewportRenderState sceneState = {};
|
SceneViewportRenderState sceneState = {};
|
||||||
BuildSceneViewportRenderState(entry, context, sceneState);
|
BuildSceneViewportRenderState(entry, context, sceneState);
|
||||||
|
|
||||||
std::vector<Rendering::CameraRenderRequest> requests =
|
std::vector<Rendering::CameraFramePlan> plans =
|
||||||
m_sceneRenderer->BuildRenderRequests(*scene, m_sceneViewCamera.camera, renderContext, surface);
|
m_sceneRenderer->BuildFramePlans(*scene, m_sceneViewCamera.camera, renderContext, surface);
|
||||||
if (requests.empty()) {
|
if (plans.empty()) {
|
||||||
ApplyViewportRenderFailure(
|
ApplyViewportRenderFailure(
|
||||||
entry,
|
entry,
|
||||||
renderContext,
|
renderContext,
|
||||||
@@ -602,9 +602,9 @@ private:
|
|||||||
return false;
|
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(
|
ApplyViewportRenderFailure(
|
||||||
entry,
|
entry,
|
||||||
renderContext,
|
renderContext,
|
||||||
@@ -613,7 +613,7 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkSceneViewportRenderSuccess(entry.renderTargets, requests[0]);
|
MarkSceneViewportRenderSuccess(entry.renderTargets, plans[0]);
|
||||||
const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount();
|
const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount();
|
||||||
context.GetSceneManager().NotifySceneViewportFramePresented(pendingAsyncLoads);
|
context.GetSceneManager().NotifySceneViewportFramePresented(pendingAsyncLoads);
|
||||||
if (entry.statusText.empty()) {
|
if (entry.statusText.empty()) {
|
||||||
|
|||||||
@@ -478,11 +478,19 @@ add_library(XCEngine STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.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/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/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/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/RenderCameraData.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/RenderSceneData.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/VisibleGaussianSplatItem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleVolumeItem.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/RenderPipeline.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipelineAsset.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/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/RenderResourceCache.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.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/BuiltinDepthStylePassBase.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h
|
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.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/RenderSceneExtractor.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneUtility.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/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/SceneRenderRequestPlanner.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/Internal/DirectionalShadowPlanning.h
|
${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/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/Execution/SceneRenderer.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSkybox.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSkybox.cpp
|
||||||
@@ -715,7 +730,6 @@ if(MSVC)
|
|||||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release"
|
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release"
|
||||||
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel"
|
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel"
|
||||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo"
|
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo"
|
||||||
VS_GLOBAL_UseMultiToolTask "false"
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,21 @@ Shader "Builtin Forward Lit"
|
|||||||
float4 spotAnglesAndFlags;
|
float4 spotAnglesAndFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ShadowMapMetrics
|
||||||
|
{
|
||||||
|
float2 inverseMapSize;
|
||||||
|
float worldTexelSize;
|
||||||
|
float padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShadowSamplingData
|
||||||
|
{
|
||||||
|
float enabled;
|
||||||
|
float receiverDepthBias;
|
||||||
|
float normalBiasScale;
|
||||||
|
float shadowStrength;
|
||||||
|
};
|
||||||
|
|
||||||
cbuffer LightingConstants
|
cbuffer LightingConstants
|
||||||
{
|
{
|
||||||
float4 gMainLightDirectionAndIntensity;
|
float4 gMainLightDirectionAndIntensity;
|
||||||
@@ -42,8 +57,8 @@ Shader "Builtin Forward Lit"
|
|||||||
cbuffer ShadowReceiverConstants
|
cbuffer ShadowReceiverConstants
|
||||||
{
|
{
|
||||||
float4x4 gWorldToShadowMatrix;
|
float4x4 gWorldToShadowMatrix;
|
||||||
float4 gShadowBiasAndTexelSize;
|
ShadowMapMetrics gShadowMapMetrics;
|
||||||
float4 gShadowOptions;
|
ShadowSamplingData gShadowSampling;
|
||||||
};
|
};
|
||||||
|
|
||||||
Texture2D BaseColorTexture;
|
Texture2D BaseColorTexture;
|
||||||
@@ -78,17 +93,61 @@ Shader "Builtin Forward Lit"
|
|||||||
return output;
|
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)
|
float ComputeShadowAttenuation(float3 positionWS, float3 normalWS, float3 lightDirectionWS)
|
||||||
{
|
{
|
||||||
#ifndef XC_MAIN_LIGHT_SHADOWS
|
#ifndef XC_MAIN_LIGHT_SHADOWS
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
#else
|
#else
|
||||||
if (gShadowOptions.x < 0.5f) {
|
if (gShadowSampling.enabled < 0.5f) {
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float nDotL = saturate(dot(normalize(normalWS), normalize(lightDirectionWS)));
|
const float nDotL = saturate(dot(normalize(normalWS), normalize(lightDirectionWS)));
|
||||||
const float normalBiasWorld = gShadowOptions.y * gShadowOptions.z * (1.0f - nDotL);
|
const float normalBiasWorld =
|
||||||
|
gShadowMapMetrics.worldTexelSize * gShadowSampling.normalBiasScale * (1.0f - nDotL);
|
||||||
const float3 shadowPositionWS = positionWS + normalize(normalWS) * normalBiasWorld;
|
const float3 shadowPositionWS = positionWS + normalize(normalWS) * normalBiasWorld;
|
||||||
const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f));
|
const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f));
|
||||||
if (shadowClip.w <= 0.0f) {
|
if (shadowClip.w <= 0.0f) {
|
||||||
@@ -109,7 +168,7 @@ Shader "Builtin Forward Lit"
|
|||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowBiasAndTexelSize.x;
|
const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowSampling.receiverDepthBias;
|
||||||
#else
|
#else
|
||||||
if (shadowUv.x < 0.0f || shadowUv.x > 1.0f ||
|
if (shadowUv.x < 0.0f || shadowUv.x > 1.0f ||
|
||||||
shadowUv.y < 0.0f || shadowUv.y > 1.0f ||
|
shadowUv.y < 0.0f || shadowUv.y > 1.0f ||
|
||||||
@@ -117,29 +176,14 @@ Shader "Builtin Forward Lit"
|
|||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x;
|
const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias;
|
||||||
#endif
|
#endif
|
||||||
const float2 shadowTexelSize = gShadowBiasAndTexelSize.yz;
|
const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize;
|
||||||
float visibility = 0.0f;
|
const float visibility = ComputeShadowPcfVisibility(
|
||||||
[unroll]
|
shadowUv,
|
||||||
for (int offsetY = -1; offsetY <= 1; ++offsetY) {
|
receiverDepth,
|
||||||
[unroll]
|
shadowTexelSize);
|
||||||
for (int offsetX = -1; offsetX <= 1; ++offsetX) {
|
const float shadowStrength = saturate(gShadowSampling.shadowStrength);
|
||||||
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 shadowStrength = saturate(gShadowBiasAndTexelSize.w);
|
|
||||||
return lerp(1.0f - shadowStrength, 1.0f, visibility);
|
return lerp(1.0f - shadowStrength, 1.0f, visibility);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ Shader "Builtin Shadow Caster"
|
|||||||
Cull Back
|
Cull Back
|
||||||
ZWrite On
|
ZWrite On
|
||||||
ZTest LEqual
|
ZTest LEqual
|
||||||
Offset 1.0, 2
|
|
||||||
HLSLPROGRAM
|
HLSLPROGRAM
|
||||||
#pragma target 4.5
|
#pragma target 4.5
|
||||||
#pragma vertex MainVS
|
#pragma vertex MainVS
|
||||||
|
|||||||
@@ -25,12 +25,17 @@ public:
|
|||||||
|
|
||||||
uint32_t GetCurrentBackBufferIndex() const override;
|
uint32_t GetCurrentBackBufferIndex() const override;
|
||||||
RHITexture* GetCurrentBackBuffer() override;
|
RHITexture* GetCurrentBackBuffer() override;
|
||||||
|
D3D12Texture* TryGetBackBuffer(uint32_t index);
|
||||||
|
const D3D12Texture* TryGetBackBuffer(uint32_t index) const;
|
||||||
D3D12Texture& GetBackBuffer(uint32_t index);
|
D3D12Texture& GetBackBuffer(uint32_t index);
|
||||||
const D3D12Texture& GetBackBuffer(uint32_t index) const;
|
const D3D12Texture& GetBackBuffer(uint32_t index) const;
|
||||||
ID3D12CommandQueue* GetNativeCommandQueue() const { return m_commandQueue.Get(); }
|
ID3D12CommandQueue* GetNativeCommandQueue() const { return m_commandQueue.Get(); }
|
||||||
void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override;
|
void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override;
|
||||||
void Resize(uint32_t width, uint32_t height) override;
|
void Resize(uint32_t width, uint32_t height) override;
|
||||||
void* GetNativeHandle() 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:
|
private:
|
||||||
bool RefreshBackBuffers();
|
bool RefreshBackBuffers();
|
||||||
@@ -42,6 +47,7 @@ private:
|
|||||||
uint32_t m_height;
|
uint32_t m_height;
|
||||||
uint32_t m_bufferCount;
|
uint32_t m_bufferCount;
|
||||||
std::vector<D3D12Texture> m_backBuffers;
|
std::vector<D3D12Texture> m_backBuffers;
|
||||||
|
HRESULT m_lastResizeResult = S_OK;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace RHI
|
} // namespace RHI
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
|
|
||||||
@@ -14,6 +14,16 @@ namespace Rendering {
|
|||||||
struct DirectionalShadowRenderPlan;
|
struct DirectionalShadowRenderPlan;
|
||||||
struct RenderContext;
|
struct RenderContext;
|
||||||
|
|
||||||
|
struct DirectionalShadowSurfaceAllocation {
|
||||||
|
RenderSurface surface = {};
|
||||||
|
RHI::RHIResourceView* depthShaderView = nullptr;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return surface.GetDepthAttachment() != nullptr &&
|
||||||
|
depthShaderView != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class DirectionalShadowSurfaceCache {
|
class DirectionalShadowSurfaceCache {
|
||||||
public:
|
public:
|
||||||
DirectionalShadowSurfaceCache() = default;
|
DirectionalShadowSurfaceCache() = default;
|
||||||
@@ -21,10 +31,9 @@ public:
|
|||||||
DirectionalShadowSurfaceCache& operator=(const DirectionalShadowSurfaceCache&) = delete;
|
DirectionalShadowSurfaceCache& operator=(const DirectionalShadowSurfaceCache&) = delete;
|
||||||
~DirectionalShadowSurfaceCache();
|
~DirectionalShadowSurfaceCache();
|
||||||
|
|
||||||
bool EnsureSurface(const RenderContext& context, const DirectionalShadowRenderPlan& plan);
|
const DirectionalShadowSurfaceAllocation* Resolve(
|
||||||
|
const RenderContext& context,
|
||||||
const RenderSurface& GetSurface() const { return m_surface; }
|
const DirectionalShadowRenderPlan& plan);
|
||||||
RHI::RHIResourceView* GetDepthShaderView() const { return m_depthShaderView; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool Matches(const RenderContext& context, const DirectionalShadowRenderPlan& plan) const;
|
bool Matches(const RenderContext& context, const DirectionalShadowRenderPlan& plan) const;
|
||||||
@@ -35,8 +44,7 @@ private:
|
|||||||
uint32_t m_height = 0;
|
uint32_t m_height = 0;
|
||||||
RHI::RHITexture* m_depthTexture = nullptr;
|
RHI::RHITexture* m_depthTexture = nullptr;
|
||||||
RHI::RHIResourceView* m_depthView = nullptr;
|
RHI::RHIResourceView* m_depthView = nullptr;
|
||||||
RHI::RHIResourceView* m_depthShaderView = nullptr;
|
DirectionalShadowSurfaceAllocation m_allocation = {};
|
||||||
RenderSurface m_surface = {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
181
engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h
Normal file
181
engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/CameraFrameStage.h>
|
||||||
|
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct CameraFramePlan {
|
||||||
|
CameraRenderRequest request = {};
|
||||||
|
ShadowCasterRenderRequest shadowCaster = {};
|
||||||
|
DirectionalShadowRenderPlan directionalShadow = {};
|
||||||
|
PostProcessRenderRequest postProcess = {};
|
||||||
|
FinalOutputRenderRequest finalOutput = {};
|
||||||
|
ResolvedFinalColorPolicy finalColorPolicy = {};
|
||||||
|
RenderPassSequence* preScenePasses = nullptr;
|
||||||
|
RenderPassSequence* postScenePasses = nullptr;
|
||||||
|
RenderPassSequence* overlayPasses = nullptr;
|
||||||
|
|
||||||
|
static CameraFramePlan FromRequest(const CameraRenderRequest& request) {
|
||||||
|
CameraFramePlan plan = {};
|
||||||
|
plan.request = request;
|
||||||
|
plan.shadowCaster = request.shadowCaster;
|
||||||
|
plan.directionalShadow = request.directionalShadow;
|
||||||
|
plan.postProcess = request.postProcess;
|
||||||
|
plan.finalOutput = request.finalOutput;
|
||||||
|
plan.finalColorPolicy = request.finalColorPolicy;
|
||||||
|
plan.preScenePasses = request.preScenePasses;
|
||||||
|
plan.postScenePasses = request.postScenePasses;
|
||||||
|
plan.overlayPasses = request.overlayPasses;
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return request.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasFrameStage(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return preScenePasses != nullptr;
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return shadowCaster.IsRequested() || directionalShadow.IsValid();
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return request.depthOnly.IsRequested();
|
||||||
|
case CameraFrameStage::MainScene:
|
||||||
|
return true;
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return postProcess.IsRequested();
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return finalOutput.IsRequested();
|
||||||
|
case CameraFrameStage::ObjectId:
|
||||||
|
return request.objectId.IsRequested();
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return postScenePasses != nullptr;
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return overlayPasses != nullptr;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPassSequence* GetPassSequence(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return preScenePasses;
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return postProcess.passes;
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return finalOutput.passes;
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return postScenePasses;
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return overlayPasses;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScenePassRenderRequest* GetScenePassRequest(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return &shadowCaster;
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return &request.depthOnly;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ObjectIdRenderRequest* GetObjectIdRequest(CameraFrameStage stage) const {
|
||||||
|
return stage == CameraFrameStage::ObjectId ? &request.objectId : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderSurface& GetMainSceneSurface() const {
|
||||||
|
if (postProcess.IsRequested()) {
|
||||||
|
return postProcess.sourceSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalOutput.IsRequested()) {
|
||||||
|
return finalOutput.sourceSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderSurface& GetFinalCompositedSurface() const {
|
||||||
|
if (finalOutput.IsRequested()) {
|
||||||
|
return finalOutput.destinationSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postProcess.IsRequested()) {
|
||||||
|
return postProcess.destinationSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderSurface* GetOutputSurface(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
case CameraFrameStage::MainScene:
|
||||||
|
return &GetMainSceneSurface();
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return shadowCaster.IsRequested() ? &shadowCaster.surface : nullptr;
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return request.depthOnly.IsRequested() ? &request.depthOnly.surface : nullptr;
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return postProcess.IsRequested() ? &postProcess.destinationSurface : nullptr;
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return finalOutput.IsRequested() ? &finalOutput.destinationSurface : nullptr;
|
||||||
|
case CameraFrameStage::ObjectId:
|
||||||
|
return request.objectId.IsRequested() ? &request.objectId.surface : nullptr;
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return &GetFinalCompositedSurface();
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderSurface* GetSourceSurface(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return postProcess.IsRequested() ? &postProcess.sourceSurface : nullptr;
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return finalOutput.IsRequested() ? &finalOutput.sourceSurface : nullptr;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHIResourceView* GetSourceColorView(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return postProcess.IsRequested() ? postProcess.sourceColorView : nullptr;
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return finalOutput.IsRequested() ? finalOutput.sourceColorView : nullptr;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::ResourceStates GetSourceColorState(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return postProcess.IsRequested() ? postProcess.sourceColorState : RHI::ResourceStates::Common;
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return finalOutput.IsRequested() ? finalOutput.sourceColorState : RHI::ResourceStates::Common;
|
||||||
|
default:
|
||||||
|
return RHI::ResourceStates::Common;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequiresIntermediateSceneColor() const {
|
||||||
|
return postProcess.IsRequested() || finalOutput.IsRequested();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
enum class CameraFrameStage : uint8_t {
|
||||||
|
PreScenePasses,
|
||||||
|
ShadowCaster,
|
||||||
|
DepthOnly,
|
||||||
|
MainScene,
|
||||||
|
PostProcess,
|
||||||
|
FinalOutput,
|
||||||
|
ObjectId,
|
||||||
|
PostScenePasses,
|
||||||
|
OverlayPasses
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CameraFrameStageInfo {
|
||||||
|
CameraFrameStage stage = CameraFrameStage::MainScene;
|
||||||
|
const char* name = "";
|
||||||
|
bool runtimeStage = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr std::array<CameraFrameStageInfo, 9> kOrderedCameraFrameStages = { {
|
||||||
|
{ CameraFrameStage::PreScenePasses, "PreScenePasses", false },
|
||||||
|
{ CameraFrameStage::ShadowCaster, "ShadowCaster", true },
|
||||||
|
{ CameraFrameStage::DepthOnly, "DepthOnly", true },
|
||||||
|
{ CameraFrameStage::MainScene, "MainScene", true },
|
||||||
|
{ CameraFrameStage::PostProcess, "PostProcess", true },
|
||||||
|
{ CameraFrameStage::FinalOutput, "FinalOutput", true },
|
||||||
|
{ CameraFrameStage::ObjectId, "ObjectId", false },
|
||||||
|
{ CameraFrameStage::PostScenePasses, "PostScenePasses", false },
|
||||||
|
{ CameraFrameStage::OverlayPasses, "OverlayPasses", false }
|
||||||
|
} };
|
||||||
|
|
||||||
|
inline constexpr const char* GetCameraFrameStageName(CameraFrameStage stage) {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return "PreScenePasses";
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return "ShadowCaster";
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return "DepthOnly";
|
||||||
|
case CameraFrameStage::MainScene:
|
||||||
|
return "MainScene";
|
||||||
|
case CameraFrameStage::PostProcess:
|
||||||
|
return "PostProcess";
|
||||||
|
case CameraFrameStage::FinalOutput:
|
||||||
|
return "FinalOutput";
|
||||||
|
case CameraFrameStage::ObjectId:
|
||||||
|
return "ObjectId";
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return "PostScenePasses";
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return "OverlayPasses";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||||
#include <XCEngine/Rendering/Extraction/RenderSceneExtractor.h>
|
#include <XCEngine/Rendering/Extraction/RenderSceneExtractor.h>
|
||||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
|
||||||
#include <XCEngine/Rendering/RenderPass.h>
|
#include <XCEngine/Rendering/RenderPass.h>
|
||||||
#include <XCEngine/Rendering/RenderPipeline.h>
|
#include <XCEngine/Rendering/RenderPipeline.h>
|
||||||
|
|
||||||
@@ -19,7 +19,8 @@ class RHIResourceView;
|
|||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
class DirectionalShadowSurfaceCache;
|
struct DirectionalShadowExecutionState;
|
||||||
|
class DirectionalShadowRuntime;
|
||||||
class FullscreenPassSurfaceCache;
|
class FullscreenPassSurfaceCache;
|
||||||
class RenderSurface;
|
class RenderSurface;
|
||||||
class RenderPipelineAsset;
|
class RenderPipelineAsset;
|
||||||
@@ -48,21 +49,17 @@ public:
|
|||||||
RenderPass* GetDepthOnlyPass() const { return m_depthOnlyPass.get(); }
|
RenderPass* GetDepthOnlyPass() const { return m_depthOnlyPass.get(); }
|
||||||
RenderPass* GetShadowCasterPass() const { return m_shadowCasterPass.get(); }
|
RenderPass* GetShadowCasterPass() const { return m_shadowCasterPass.get(); }
|
||||||
|
|
||||||
bool Render(const CameraRenderRequest& request);
|
bool Render(const CameraFramePlan& plan);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ResetPipeline(std::unique_ptr<RenderPipeline> pipeline);
|
void ResetPipeline(std::unique_ptr<RenderPipeline> pipeline);
|
||||||
bool ResolveShadowCasterRequest(
|
bool BuildSceneDataForPlan(
|
||||||
const CameraRenderRequest& request,
|
const CameraFramePlan& plan,
|
||||||
ShadowCasterRenderRequest& outResolvedShadowCaster,
|
const DirectionalShadowExecutionState& shadowState,
|
||||||
RHI::RHIResourceView*& outShadowMapView);
|
|
||||||
bool BuildSceneDataForRequest(
|
|
||||||
const CameraRenderRequest& request,
|
|
||||||
RHI::RHIResourceView* shadowMapView,
|
|
||||||
RenderSceneData& outSceneData);
|
RenderSceneData& outSceneData);
|
||||||
bool ExecuteRenderPlan(
|
bool ExecuteRenderPlan(
|
||||||
const CameraRenderRequest& request,
|
const CameraFramePlan& plan,
|
||||||
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
const DirectionalShadowExecutionState& shadowState,
|
||||||
const RenderSceneData& sceneData);
|
const RenderSceneData& sceneData);
|
||||||
|
|
||||||
RenderSceneExtractor m_sceneExtractor;
|
RenderSceneExtractor m_sceneExtractor;
|
||||||
@@ -71,7 +68,7 @@ private:
|
|||||||
std::unique_ptr<RenderPass> m_objectIdPass;
|
std::unique_ptr<RenderPass> m_objectIdPass;
|
||||||
std::unique_ptr<RenderPass> m_depthOnlyPass;
|
std::unique_ptr<RenderPass> m_depthOnlyPass;
|
||||||
std::unique_ptr<RenderPass> m_shadowCasterPass;
|
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_postProcessSurfaceCache;
|
||||||
std::unique_ptr<FullscreenPassSurfaceCache> m_finalOutputSurfaceCache;
|
std::unique_ptr<FullscreenPassSurfaceCache> m_finalOutputSurfaceCache;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/FrameStageRenderRequest.h>
|
||||||
|
#include <XCEngine/Rendering/Shadow/DirectionalShadowData.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct DirectionalShadowExecutionState {
|
||||||
|
ShadowCasterRenderRequest shadowCasterRequest = {};
|
||||||
|
RenderDirectionalShadowData shadowData = {};
|
||||||
|
|
||||||
|
bool HasShadowPass() const {
|
||||||
|
return shadowCasterRequest.IsRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasShadowSampling() const {
|
||||||
|
return shadowData.IsValid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
15
engine/include/XCEngine/Rendering/Execution/DrawSettings.h
Normal file
15
engine/include/XCEngine/Rendering/Execution/DrawSettings.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/ScenePhase.h>
|
||||||
|
#include <XCEngine/Rendering/FrameData/CullingResults.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct DrawSettings {
|
||||||
|
ScenePhase scenePhase = ScenePhase::Opaque;
|
||||||
|
RendererListType rendererListType = RendererListType::AllVisible;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/ScenePhase.h>
|
||||||
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHIResourceView;
|
||||||
|
} // namespace RHI
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct RenderSceneData;
|
||||||
|
class RenderSurface;
|
||||||
|
|
||||||
|
struct FrameExecutionContext {
|
||||||
|
FrameExecutionContext(
|
||||||
|
const RenderContext& inRenderContext,
|
||||||
|
const RenderSurface& inSurface,
|
||||||
|
const RenderSceneData& inSceneData,
|
||||||
|
const RenderSurface* inSourceSurface = nullptr,
|
||||||
|
RHI::RHIResourceView* inSourceColorView = nullptr,
|
||||||
|
RHI::ResourceStates inSourceColorState = RHI::ResourceStates::Common)
|
||||||
|
: renderContext(inRenderContext)
|
||||||
|
, surface(inSurface)
|
||||||
|
, sceneData(inSceneData)
|
||||||
|
, sourceSurface(inSourceSurface)
|
||||||
|
, sourceColorView(inSourceColorView)
|
||||||
|
, sourceColorState(inSourceColorState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderContext& renderContext;
|
||||||
|
const RenderSurface& surface;
|
||||||
|
const RenderSceneData& sceneData;
|
||||||
|
const RenderSurface* sourceSurface = nullptr;
|
||||||
|
RHI::RHIResourceView* sourceColorView = nullptr;
|
||||||
|
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScenePhaseExecutionContext {
|
||||||
|
ScenePhaseExecutionContext(
|
||||||
|
const FrameExecutionContext& inFrameContext,
|
||||||
|
ScenePhase inScenePhase,
|
||||||
|
bool inSampledDirectionalShadow = false)
|
||||||
|
: frameContext(inFrameContext)
|
||||||
|
, scenePhase(inScenePhase)
|
||||||
|
, sampledDirectionalShadow(inSampledDirectionalShadow) {
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameExecutionContext frameContext;
|
||||||
|
ScenePhase scenePhase = ScenePhase::Opaque;
|
||||||
|
bool sampledDirectionalShadow = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||||
|
#include <XCEngine/Rendering/RenderPass.h>
|
||||||
|
#include <XCEngine/Rendering/RenderSurface.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHIResourceView;
|
||||||
|
} // namespace RHI
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct ScenePassRenderRequest {
|
||||||
|
RenderSurface surface;
|
||||||
|
RenderClearFlags clearFlags = RenderClearFlags::Depth;
|
||||||
|
bool hasClearColorOverride = false;
|
||||||
|
Math::Color clearColorOverride = Math::Color::Black();
|
||||||
|
bool hasCameraDataOverride = false;
|
||||||
|
RenderCameraData cameraDataOverride = {};
|
||||||
|
|
||||||
|
bool IsRequested() const {
|
||||||
|
return surface.GetDepthAttachment() != nullptr ||
|
||||||
|
!surface.GetColorAttachments().empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||||
|
return surface.GetDepthAttachment() != nullptr &&
|
||||||
|
(colorAttachments.empty() || colorAttachments[0] != nullptr) &&
|
||||||
|
surface.GetRenderAreaWidth() > 0 &&
|
||||||
|
surface.GetRenderAreaHeight() > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using DepthOnlyRenderRequest = ScenePassRenderRequest;
|
||||||
|
using ShadowCasterRenderRequest = ScenePassRenderRequest;
|
||||||
|
|
||||||
|
inline bool HasValidColorTarget(const RenderSurface& surface) {
|
||||||
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||||
|
return !colorAttachments.empty() &&
|
||||||
|
colorAttachments[0] != nullptr &&
|
||||||
|
surface.GetRenderAreaWidth() > 0 &&
|
||||||
|
surface.GetRenderAreaHeight() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool HasValidSurfaceSampleDescription(const RenderSurface& surface) {
|
||||||
|
const uint32_t sampleCount = surface.GetSampleCount();
|
||||||
|
const uint32_t sampleQuality = surface.GetSampleQuality();
|
||||||
|
return sampleCount > 0u &&
|
||||||
|
(sampleCount > 1u || sampleQuality == 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool HasValidSingleSampleColorSource(const RenderSurface& surface) {
|
||||||
|
return HasValidColorTarget(surface) &&
|
||||||
|
HasValidSurfaceSampleDescription(surface) &&
|
||||||
|
surface.GetSampleCount() == 1u &&
|
||||||
|
surface.GetSampleQuality() == 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ObjectIdRenderRequest {
|
||||||
|
RenderSurface surface;
|
||||||
|
|
||||||
|
bool IsRequested() const {
|
||||||
|
return !surface.GetColorAttachments().empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||||
|
return !colorAttachments.empty() &&
|
||||||
|
colorAttachments[0] != nullptr &&
|
||||||
|
surface.GetDepthAttachment() != nullptr &&
|
||||||
|
surface.GetRenderAreaWidth() > 0 &&
|
||||||
|
surface.GetRenderAreaHeight() > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FullscreenPassRenderRequest {
|
||||||
|
RenderSurface sourceSurface;
|
||||||
|
RHI::RHIResourceView* sourceColorView = nullptr;
|
||||||
|
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
||||||
|
RenderSurface destinationSurface;
|
||||||
|
RenderPassSequence* passes = nullptr;
|
||||||
|
|
||||||
|
size_t GetPassCount() const {
|
||||||
|
return passes != nullptr ? passes->GetPassCount() : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsRequested() const {
|
||||||
|
return passes != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequiresIntermediateSurface() const {
|
||||||
|
return GetPassCount() > 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
const bool sourceStateIsUsable =
|
||||||
|
sourceSurface.IsAutoTransitionEnabled() ||
|
||||||
|
sourceColorState == RHI::ResourceStates::PixelShaderResource;
|
||||||
|
return passes != nullptr &&
|
||||||
|
HasValidSingleSampleColorSource(sourceSurface) &&
|
||||||
|
sourceColorView != nullptr &&
|
||||||
|
sourceStateIsUsable &&
|
||||||
|
HasValidColorTarget(destinationSurface) &&
|
||||||
|
HasValidSurfaceSampleDescription(destinationSurface);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using PostProcessRenderRequest = FullscreenPassRenderRequest;
|
||||||
|
using FinalOutputRenderRequest = FullscreenPassRenderRequest;
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
40
engine/include/XCEngine/Rendering/Execution/ScenePhase.h
Normal file
40
engine/include/XCEngine/Rendering/Execution/ScenePhase.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Types.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
enum class ScenePhase : Core::uint32 {
|
||||||
|
Opaque = 0,
|
||||||
|
Skybox = 1,
|
||||||
|
Feature = 2,
|
||||||
|
Transparent = 3,
|
||||||
|
EditorExtension = 4,
|
||||||
|
PostProcess = 5,
|
||||||
|
FinalOutput = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const char* ToString(ScenePhase scenePhase) {
|
||||||
|
switch (scenePhase) {
|
||||||
|
case ScenePhase::Opaque:
|
||||||
|
return "Opaque";
|
||||||
|
case ScenePhase::Skybox:
|
||||||
|
return "Skybox";
|
||||||
|
case ScenePhase::Feature:
|
||||||
|
return "Feature";
|
||||||
|
case ScenePhase::Transparent:
|
||||||
|
return "Transparent";
|
||||||
|
case ScenePhase::EditorExtension:
|
||||||
|
return "EditorExtension";
|
||||||
|
case ScenePhase::PostProcess:
|
||||||
|
return "PostProcess";
|
||||||
|
case ScenePhase::FinalOutput:
|
||||||
|
return "FinalOutput";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||||
#include <XCEngine/Rendering/Execution/CameraRenderer.h>
|
#include <XCEngine/Rendering/Execution/CameraRenderer.h>
|
||||||
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
#include <XCEngine/Rendering/Planning/CameraRenderRequest.h>
|
||||||
#include <XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h>
|
#include <XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h>
|
||||||
@@ -15,8 +16,8 @@ class Scene;
|
|||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
|
class CameraFramePlanBuilder;
|
||||||
class RenderPipelineAsset;
|
class RenderPipelineAsset;
|
||||||
class FullscreenPassSurfaceCache;
|
|
||||||
|
|
||||||
class SceneRenderer {
|
class SceneRenderer {
|
||||||
public:
|
public:
|
||||||
@@ -30,14 +31,23 @@ public:
|
|||||||
RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); }
|
RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); }
|
||||||
const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); }
|
const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); }
|
||||||
|
|
||||||
|
// Legacy compatibility adapters retained for callers that still consume CameraRenderRequest.
|
||||||
std::vector<CameraRenderRequest> BuildRenderRequests(
|
std::vector<CameraRenderRequest> BuildRenderRequests(
|
||||||
const Components::Scene& scene,
|
const Components::Scene& scene,
|
||||||
Components::CameraComponent* overrideCamera,
|
Components::CameraComponent* overrideCamera,
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSurface& surface);
|
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 CameraRenderRequest& request);
|
||||||
bool Render(const std::vector<CameraRenderRequest>& requests);
|
bool Render(const std::vector<CameraRenderRequest>& requests);
|
||||||
|
bool Render(const CameraFramePlan& plan);
|
||||||
|
bool Render(const std::vector<CameraFramePlan>& plans);
|
||||||
bool Render(
|
bool Render(
|
||||||
const Components::Scene& scene,
|
const Components::Scene& scene,
|
||||||
Components::CameraComponent* overrideCamera,
|
Components::CameraComponent* overrideCamera,
|
||||||
@@ -45,18 +55,12 @@ public:
|
|||||||
const RenderSurface& surface);
|
const RenderSurface& surface);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PrepareOwnedFullscreenStageState(size_t requestCount);
|
std::vector<CameraFramePlan> CreateFramePlansFromLegacyRequests(
|
||||||
void ResolveCameraFinalColorPolicies(
|
const std::vector<CameraRenderRequest>& requests) const;
|
||||||
std::vector<CameraRenderRequest>& requests) const;
|
|
||||||
void AttachFullscreenStageRequests(
|
|
||||||
const RenderContext& context,
|
|
||||||
std::vector<CameraRenderRequest>& requests);
|
|
||||||
|
|
||||||
SceneRenderRequestPlanner m_requestPlanner;
|
SceneRenderRequestPlanner m_requestPlanner;
|
||||||
CameraRenderer m_cameraRenderer;
|
CameraRenderer m_cameraRenderer;
|
||||||
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedPostProcessSequences;
|
std::unique_ptr<CameraFramePlanBuilder> m_framePlanBuilder;
|
||||||
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedFinalOutputSequences;
|
|
||||||
std::vector<std::unique_ptr<FullscreenPassSurfaceCache>> m_ownedFullscreenStageSurfaces;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
85
engine/include/XCEngine/Rendering/FrameData/CullingResults.h
Normal file
85
engine/include/XCEngine/Rendering/FrameData/CullingResults.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Types.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
enum class RendererListType : Core::uint32 {
|
||||||
|
AllVisible = 0,
|
||||||
|
Opaque = 1,
|
||||||
|
Transparent = 2,
|
||||||
|
ShadowCaster = 3,
|
||||||
|
ObjectId = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RendererSortMode : Core::uint32 {
|
||||||
|
None = 0,
|
||||||
|
FrontToBack = 1,
|
||||||
|
BackToFront = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FilteringSettings {
|
||||||
|
Core::int32 renderQueueMin = std::numeric_limits<Core::int32>::lowest();
|
||||||
|
Core::int32 renderQueueMax = std::numeric_limits<Core::int32>::max();
|
||||||
|
bool requireShadowCasting = false;
|
||||||
|
bool requireRenderObjectId = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SortingSettings {
|
||||||
|
RendererSortMode sortMode = RendererSortMode::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RendererListDesc {
|
||||||
|
RendererListType type = RendererListType::AllVisible;
|
||||||
|
FilteringSettings filtering = {};
|
||||||
|
SortingSettings sorting = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RendererList {
|
||||||
|
RendererListDesc desc = {};
|
||||||
|
std::vector<Core::uint32> visibleRenderItemIndices;
|
||||||
|
|
||||||
|
bool Empty() const {
|
||||||
|
return visibleRenderItemIndices.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Size() const {
|
||||||
|
return visibleRenderItemIndices.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CullingResults {
|
||||||
|
std::vector<RendererList> rendererLists;
|
||||||
|
|
||||||
|
void Clear() {
|
||||||
|
rendererLists.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererList* FindRendererList(RendererListType type) {
|
||||||
|
for (RendererList& rendererList : rendererLists) {
|
||||||
|
if (rendererList.desc.type == type) {
|
||||||
|
return &rendererList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RendererList* FindRendererList(RendererListType type) const {
|
||||||
|
for (const RendererList& rendererList : rendererLists) {
|
||||||
|
if (rendererList.desc.type == type) {
|
||||||
|
return &rendererList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Math/Vector2.h>
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
#include <XCEngine/Core/Math/Vector4.h>
|
#include <XCEngine/Core/Math/Vector4.h>
|
||||||
|
#include <XCEngine/Rendering/FrameData/CullingResults.h>
|
||||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||||
#include <XCEngine/Rendering/FrameData/RenderEnvironmentData.h>
|
#include <XCEngine/Rendering/FrameData/RenderEnvironmentData.h>
|
||||||
|
#include <XCEngine/Rendering/Shadow/DirectionalShadowData.h>
|
||||||
#include <XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h>
|
#include <XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h>
|
||||||
#include <XCEngine/Rendering/FrameData/VisibleRenderItem.h>
|
#include <XCEngine/Rendering/FrameData/VisibleRenderItem.h>
|
||||||
#include <XCEngine/Rendering/FrameData/VisibleVolumeItem.h>
|
#include <XCEngine/Rendering/FrameData/VisibleVolumeItem.h>
|
||||||
@@ -18,10 +21,6 @@ namespace Components {
|
|||||||
class CameraComponent;
|
class CameraComponent;
|
||||||
} // namespace Components
|
} // namespace Components
|
||||||
|
|
||||||
namespace RHI {
|
|
||||||
class RHIResourceView;
|
|
||||||
} // namespace RHI
|
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
struct RenderDirectionalLightData {
|
struct RenderDirectionalLightData {
|
||||||
@@ -50,18 +49,6 @@ struct RenderAdditionalLightData {
|
|||||||
float spotAngle = 0.0f;
|
float spotAngle = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RenderDirectionalShadowData {
|
|
||||||
bool enabled = false;
|
|
||||||
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
|
|
||||||
Math::Vector4 shadowParams = Math::Vector4::Zero();
|
|
||||||
Math::Vector4 shadowOptions = Math::Vector4::Zero();
|
|
||||||
RHI::RHIResourceView* shadowMap = nullptr;
|
|
||||||
|
|
||||||
bool IsValid() const {
|
|
||||||
return enabled && shadowMap != nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderLightingData {
|
struct RenderLightingData {
|
||||||
static constexpr uint32_t kMaxAdditionalLightCount = 8u;
|
static constexpr uint32_t kMaxAdditionalLightCount = 8u;
|
||||||
|
|
||||||
@@ -89,6 +76,7 @@ struct RenderSceneData {
|
|||||||
RenderEnvironmentData environment;
|
RenderEnvironmentData environment;
|
||||||
RenderLightingData lighting;
|
RenderLightingData lighting;
|
||||||
Resources::ShaderKeywordSet globalShaderKeywords;
|
Resources::ShaderKeywordSet globalShaderKeywords;
|
||||||
|
CullingResults cullingResults;
|
||||||
std::vector<VisibleRenderItem> visibleItems;
|
std::vector<VisibleRenderItem> visibleItems;
|
||||||
std::vector<VisibleGaussianSplatItem> visibleGaussianSplats;
|
std::vector<VisibleGaussianSplatItem> visibleGaussianSplats;
|
||||||
std::vector<VisibleVolumeItem> visibleVolumes;
|
std::vector<VisibleVolumeItem> visibleVolumes;
|
||||||
@@ -96,6 +84,22 @@ struct RenderSceneData {
|
|||||||
bool HasCamera() const {
|
bool HasCamera() const {
|
||||||
return camera != nullptr;
|
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
|
} // namespace Rendering
|
||||||
|
|||||||
145
engine/include/XCEngine/Rendering/FrameData/RendererListUtils.h
Normal file
145
engine/include/XCEngine/Rendering/FrameData/RendererListUtils.h
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||||
|
#include <XCEngine/Rendering/FrameData/RenderSceneData.h>
|
||||||
|
#include <XCEngine/Resources/Material/Material.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
inline FilteringSettings BuildDefaultFilteringSettings(RendererListType type) {
|
||||||
|
FilteringSettings filtering = {};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case RendererListType::AllVisible:
|
||||||
|
break;
|
||||||
|
case RendererListType::Opaque:
|
||||||
|
filtering.renderQueueMax =
|
||||||
|
static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent) - 1;
|
||||||
|
break;
|
||||||
|
case RendererListType::Transparent:
|
||||||
|
filtering.renderQueueMin =
|
||||||
|
static_cast<Core::int32>(Resources::MaterialRenderQueue::Transparent);
|
||||||
|
break;
|
||||||
|
case RendererListType::ShadowCaster:
|
||||||
|
filtering.requireShadowCasting = true;
|
||||||
|
break;
|
||||||
|
case RendererListType::ObjectId:
|
||||||
|
filtering.requireRenderObjectId = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtering;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline SortingSettings BuildDefaultSortingSettings(RendererListType type) {
|
||||||
|
SortingSettings sorting = {};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case RendererListType::Opaque:
|
||||||
|
case RendererListType::ShadowCaster:
|
||||||
|
case RendererListType::ObjectId:
|
||||||
|
sorting.sortMode = RendererSortMode::FrontToBack;
|
||||||
|
break;
|
||||||
|
case RendererListType::Transparent:
|
||||||
|
sorting.sortMode = RendererSortMode::BackToFront;
|
||||||
|
break;
|
||||||
|
case RendererListType::AllVisible:
|
||||||
|
sorting.sortMode = RendererSortMode::None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorting;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline RendererListDesc BuildDefaultRendererListDesc(RendererListType type) {
|
||||||
|
RendererListDesc desc = {};
|
||||||
|
desc.type = type;
|
||||||
|
desc.filtering = BuildDefaultFilteringSettings(type);
|
||||||
|
desc.sorting = BuildDefaultSortingSettings(type);
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool MatchesFilteringSettings(
|
||||||
|
const VisibleRenderItem& visibleItem,
|
||||||
|
const FilteringSettings& filtering) {
|
||||||
|
if (visibleItem.renderQueue < filtering.renderQueueMin ||
|
||||||
|
visibleItem.renderQueue > filtering.renderQueueMax) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtering.requireShadowCasting &&
|
||||||
|
visibleItem.meshRenderer != nullptr &&
|
||||||
|
!visibleItem.meshRenderer->GetCastShadows()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtering.requireRenderObjectId &&
|
||||||
|
!IsValidRenderObjectId(visibleItem.renderObjectId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool MatchesRendererListDesc(
|
||||||
|
const VisibleRenderItem& visibleItem,
|
||||||
|
const RendererListDesc& desc) {
|
||||||
|
return MatchesFilteringSettings(visibleItem, desc.filtering);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline RendererList BuildRendererList(
|
||||||
|
RendererListType type,
|
||||||
|
const std::vector<VisibleRenderItem>& visibleItems) {
|
||||||
|
RendererList rendererList = {};
|
||||||
|
rendererList.desc = BuildDefaultRendererListDesc(type);
|
||||||
|
rendererList.visibleRenderItemIndices.reserve(visibleItems.size());
|
||||||
|
|
||||||
|
for (Core::uint32 visibleItemIndex = 0;
|
||||||
|
visibleItemIndex < static_cast<Core::uint32>(visibleItems.size());
|
||||||
|
++visibleItemIndex) {
|
||||||
|
if (!MatchesRendererListDesc(
|
||||||
|
visibleItems[visibleItemIndex],
|
||||||
|
rendererList.desc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rendererList.visibleRenderItemIndices.push_back(visibleItemIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rendererList;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Visitor>
|
||||||
|
inline void VisitRendererListVisibleItems(
|
||||||
|
const RenderSceneData& sceneData,
|
||||||
|
RendererListType rendererListType,
|
||||||
|
Visitor&& visitor) {
|
||||||
|
const RendererList* rendererList = sceneData.FindRendererList(rendererListType);
|
||||||
|
if (rendererList != nullptr) {
|
||||||
|
for (Core::uint32 visibleItemIndex : rendererList->visibleRenderItemIndices) {
|
||||||
|
const VisibleRenderItem* visibleItem =
|
||||||
|
sceneData.TryGetVisibleRenderItem(visibleItemIndex);
|
||||||
|
if (visibleItem == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor(*visibleItem);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RendererListDesc fallbackDesc = BuildDefaultRendererListDesc(rendererListType);
|
||||||
|
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
|
||||||
|
if (!MatchesRendererListDesc(visibleItem, fallbackDesc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor(visibleItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
110
engine/include/XCEngine/Rendering/Graph/RenderGraph.h
Normal file
110
engine/include/XCEngine/Rendering/Graph/RenderGraph.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Graph/RenderGraphTypes.h>
|
||||||
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderGraphPassBuilder;
|
||||||
|
class RenderGraphBuilder;
|
||||||
|
class RenderGraphCompiler;
|
||||||
|
class RenderGraphExecutor;
|
||||||
|
|
||||||
|
class RenderGraph {
|
||||||
|
public:
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
size_t GetTextureCount() const {
|
||||||
|
return m_textures.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetPassCount() const {
|
||||||
|
return m_passes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TextureResource {
|
||||||
|
Containers::String name;
|
||||||
|
RenderGraphTextureDesc desc = {};
|
||||||
|
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||||
|
RHI::RHIResourceView* importedView = nullptr;
|
||||||
|
RenderGraphImportedTextureOptions importedOptions = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureAccess {
|
||||||
|
RenderGraphTextureHandle texture = {};
|
||||||
|
RenderGraphAccessMode mode = RenderGraphAccessMode::Read;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PassNode {
|
||||||
|
Containers::String name;
|
||||||
|
RenderGraphPassType type = RenderGraphPassType::Raster;
|
||||||
|
std::vector<TextureAccess> accesses;
|
||||||
|
RenderGraphExecuteCallback executeCallback = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TextureResource> m_textures;
|
||||||
|
std::vector<PassNode> m_passes;
|
||||||
|
|
||||||
|
friend class RenderGraphBuilder;
|
||||||
|
friend class RenderGraphPassBuilder;
|
||||||
|
friend class RenderGraphCompiler;
|
||||||
|
friend class RenderGraphExecutor;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderGraphPassBuilder {
|
||||||
|
public:
|
||||||
|
void ReadTexture(RenderGraphTextureHandle texture);
|
||||||
|
void WriteTexture(RenderGraphTextureHandle texture);
|
||||||
|
void SetExecuteCallback(RenderGraphExecuteCallback callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderGraphPassBuilder(RenderGraph* graph, RenderGraphPassHandle passHandle);
|
||||||
|
|
||||||
|
RenderGraph* m_graph = nullptr;
|
||||||
|
RenderGraphPassHandle m_passHandle = {};
|
||||||
|
|
||||||
|
friend class RenderGraphBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderGraphBuilder {
|
||||||
|
public:
|
||||||
|
explicit RenderGraphBuilder(RenderGraph& graph)
|
||||||
|
: m_graph(graph) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
RenderGraphTextureHandle ImportTexture(
|
||||||
|
const Containers::String& name,
|
||||||
|
const RenderGraphTextureDesc& desc,
|
||||||
|
RHI::RHIResourceView* importedView = nullptr,
|
||||||
|
const RenderGraphImportedTextureOptions& importedOptions = {});
|
||||||
|
|
||||||
|
RenderGraphTextureHandle CreateTransientTexture(
|
||||||
|
const Containers::String& name,
|
||||||
|
const RenderGraphTextureDesc& desc);
|
||||||
|
|
||||||
|
RenderGraphPassHandle AddRasterPass(
|
||||||
|
const Containers::String& name,
|
||||||
|
const std::function<void(RenderGraphPassBuilder&)>& setup);
|
||||||
|
|
||||||
|
RenderGraphPassHandle AddComputePass(
|
||||||
|
const Containers::String& name,
|
||||||
|
const std::function<void(RenderGraphPassBuilder&)>& setup);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderGraphPassHandle AddPass(
|
||||||
|
const Containers::String& name,
|
||||||
|
RenderGraphPassType type,
|
||||||
|
const std::function<void(RenderGraphPassBuilder&)>& setup);
|
||||||
|
|
||||||
|
RenderGraph& m_graph;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Graph/RenderGraph.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class CompiledRenderGraph {
|
||||||
|
public:
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
size_t GetPassCount() const {
|
||||||
|
return m_passes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Containers::String& GetPassName(size_t index) const;
|
||||||
|
RenderGraphPassType GetPassType(size_t index) const;
|
||||||
|
bool TryGetTextureLifetime(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureLifetime& outLifetime) const;
|
||||||
|
bool TryGetImportedTextureOptions(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphImportedTextureOptions& outOptions) const;
|
||||||
|
bool TryGetTextureTransitionPlan(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureTransitionPlan& outPlan) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CompiledTextureAccess {
|
||||||
|
RenderGraphTextureHandle texture = {};
|
||||||
|
RenderGraphAccessMode mode = RenderGraphAccessMode::Read;
|
||||||
|
RHI::ResourceStates requiredState = RHI::ResourceStates::Common;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompiledPass {
|
||||||
|
Containers::String name;
|
||||||
|
RenderGraphPassType type = RenderGraphPassType::Raster;
|
||||||
|
Core::uint32 originalPassIndex = kInvalidRenderGraphHandle;
|
||||||
|
std::vector<CompiledTextureAccess> accesses;
|
||||||
|
RenderGraphExecuteCallback executeCallback = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompiledTexture {
|
||||||
|
Containers::String name;
|
||||||
|
RenderGraphTextureDesc desc = {};
|
||||||
|
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||||
|
RHI::RHIResourceView* importedView = nullptr;
|
||||||
|
RenderGraphImportedTextureOptions importedOptions = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<CompiledPass> m_passes;
|
||||||
|
std::vector<CompiledTexture> m_textures;
|
||||||
|
std::vector<RenderGraphTextureLifetime> m_textureLifetimes;
|
||||||
|
std::vector<RenderGraphTextureTransitionPlan> m_textureTransitionPlans;
|
||||||
|
|
||||||
|
friend class RenderGraphCompiler;
|
||||||
|
friend class RenderGraphExecutor;
|
||||||
|
friend class RenderGraphRuntimeResources;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderGraphCompiler {
|
||||||
|
public:
|
||||||
|
static bool Compile(
|
||||||
|
const RenderGraph& graph,
|
||||||
|
CompiledRenderGraph& outCompiledGraph,
|
||||||
|
Containers::String* outErrorMessage = nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderGraphExecutor {
|
||||||
|
public:
|
||||||
|
static bool Execute(
|
||||||
|
const CompiledRenderGraph& graph,
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
Containers::String* outErrorMessage = nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
114
engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h
Normal file
114
engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Containers/String.h>
|
||||||
|
#include <XCEngine/Core/Types.h>
|
||||||
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
#include <XCEngine/RHI/RHIEnums.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHIResourceView;
|
||||||
|
class RHITexture;
|
||||||
|
} // namespace RHI
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderGraphRuntimeResources;
|
||||||
|
|
||||||
|
constexpr Core::uint32 kInvalidRenderGraphHandle = std::numeric_limits<Core::uint32>::max();
|
||||||
|
|
||||||
|
enum class RenderGraphPassType : Core::uint8 {
|
||||||
|
Raster = 0,
|
||||||
|
Compute = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RenderGraphTextureKind : Core::uint8 {
|
||||||
|
Imported = 0,
|
||||||
|
Transient = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RenderGraphAccessMode : Core::uint8 {
|
||||||
|
Read = 0,
|
||||||
|
Write = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RenderGraphTextureViewType : Core::uint8 {
|
||||||
|
RenderTarget = 0,
|
||||||
|
ShaderResource = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphTextureHandle {
|
||||||
|
Core::uint32 index = kInvalidRenderGraphHandle;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return index != kInvalidRenderGraphHandle;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphPassHandle {
|
||||||
|
Core::uint32 index = kInvalidRenderGraphHandle;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return index != kInvalidRenderGraphHandle;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphTextureDesc {
|
||||||
|
Core::uint32 width = 0u;
|
||||||
|
Core::uint32 height = 0u;
|
||||||
|
Core::uint32 format = static_cast<Core::uint32>(RHI::Format::Unknown);
|
||||||
|
Core::uint32 textureType = static_cast<Core::uint32>(RHI::TextureType::Texture2D);
|
||||||
|
Core::uint32 sampleCount = 1u;
|
||||||
|
Core::uint32 sampleQuality = 0u;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return width > 0u &&
|
||||||
|
height > 0u &&
|
||||||
|
format != static_cast<Core::uint32>(RHI::Format::Unknown) &&
|
||||||
|
sampleCount > 0u;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphTextureLifetime {
|
||||||
|
RenderGraphTextureKind kind = RenderGraphTextureKind::Transient;
|
||||||
|
bool used = false;
|
||||||
|
Core::uint32 firstPassIndex = kInvalidRenderGraphHandle;
|
||||||
|
Core::uint32 lastPassIndex = kInvalidRenderGraphHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphImportedTextureOptions {
|
||||||
|
RHI::ResourceStates initialState = RHI::ResourceStates::Common;
|
||||||
|
RHI::ResourceStates finalState = RHI::ResourceStates::Common;
|
||||||
|
bool graphOwnsTransitions = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphTextureTransitionPlan {
|
||||||
|
bool graphOwnsTransitions = false;
|
||||||
|
bool hasFirstAccessState = false;
|
||||||
|
bool hasLastAccessState = false;
|
||||||
|
RHI::ResourceStates initialState = RHI::ResourceStates::Common;
|
||||||
|
RHI::ResourceStates firstAccessState = RHI::ResourceStates::Common;
|
||||||
|
RHI::ResourceStates lastAccessState = RHI::ResourceStates::Common;
|
||||||
|
RHI::ResourceStates finalState = RHI::ResourceStates::Common;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderGraphExecutionContext {
|
||||||
|
const RenderContext& renderContext;
|
||||||
|
const RenderGraphRuntimeResources* runtimeResources = nullptr;
|
||||||
|
|
||||||
|
RHI::RHIResourceView* ResolveTextureView(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureViewType viewType) const;
|
||||||
|
bool TryGetTextureDesc(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureDesc& outDesc) const;
|
||||||
|
bool IsTransientTexture(RenderGraphTextureHandle handle) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
using RenderGraphExecuteCallback = std::function<void(const RenderGraphExecutionContext&)>;
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||||
#include <XCEngine/Core/Math/Matrix4.h>
|
#include <XCEngine/Core/Math/Matrix4.h>
|
||||||
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
||||||
|
#include <XCEngine/Rendering/FrameData/CullingResults.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||||
#include <XCEngine/Rendering/RenderPass.h>
|
#include <XCEngine/Rendering/RenderPass.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||||
@@ -46,6 +47,7 @@ protected:
|
|||||||
BuiltinMaterialPass passType,
|
BuiltinMaterialPass passType,
|
||||||
Containers::String builtinShaderPath);
|
Containers::String builtinShaderPath);
|
||||||
|
|
||||||
|
virtual RendererListType GetRendererListType() const;
|
||||||
virtual bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const;
|
virtual bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -171,6 +173,10 @@ private:
|
|||||||
ResolvedShaderPass ResolveSurfaceShaderPass(
|
ResolvedShaderPass ResolveSurfaceShaderPass(
|
||||||
const RenderSceneData& sceneData,
|
const RenderSceneData& sceneData,
|
||||||
const Resources::Material* material) const;
|
const Resources::Material* material) const;
|
||||||
|
Resources::MaterialRenderState ResolveEffectiveDepthRenderState(
|
||||||
|
const Resources::ShaderPass* shaderPass,
|
||||||
|
const Resources::Material* material,
|
||||||
|
const RenderSceneData& sceneData) const;
|
||||||
bool TryBuildSupportedBindingPlan(
|
bool TryBuildSupportedBindingPlan(
|
||||||
const Resources::ShaderPass& shaderPass,
|
const Resources::ShaderPass& shaderPass,
|
||||||
BuiltinPassResourceBindingPlan& outPlan,
|
BuiltinPassResourceBindingPlan& outPlan,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.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/RHIDescriptorPool.h>
|
||||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||||
@@ -40,12 +40,16 @@ class BuiltinGaussianSplatPassResources;
|
|||||||
|
|
||||||
namespace Passes {
|
namespace Passes {
|
||||||
|
|
||||||
class BuiltinGaussianSplatPass final : public RenderPass {
|
class BuiltinGaussianSplatPass final : public SceneRenderFeaturePass {
|
||||||
public:
|
public:
|
||||||
~BuiltinGaussianSplatPass() override;
|
~BuiltinGaussianSplatPass() override;
|
||||||
|
|
||||||
const char* GetName() const override;
|
const char* GetName() const override;
|
||||||
bool Initialize(const RenderContext& context) 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(
|
bool PrepareGaussianSplatResources(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSceneData& sceneData);
|
const RenderSceneData& sceneData);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public:
|
|||||||
const char* GetName() const override;
|
const char* GetName() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
RendererListType GetRendererListType() const override;
|
||||||
bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const override;
|
bool ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.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/RHIDescriptorPool.h>
|
||||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||||
@@ -33,7 +33,7 @@ struct RenderLightingData;
|
|||||||
|
|
||||||
namespace Passes {
|
namespace Passes {
|
||||||
|
|
||||||
class BuiltinVolumetricPass final : public RenderPass {
|
class BuiltinVolumetricPass final : public SceneRenderFeaturePass {
|
||||||
public:
|
public:
|
||||||
~BuiltinVolumetricPass() override;
|
~BuiltinVolumetricPass() override;
|
||||||
|
|
||||||
@@ -41,6 +41,10 @@ public:
|
|||||||
|
|
||||||
const char* GetName() const override;
|
const char* GetName() const override;
|
||||||
bool Initialize(const RenderContext& context) 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(
|
bool PrepareVolumeResources(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSceneData& sceneData);
|
const RenderSceneData& sceneData);
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/DrawSettings.h>
|
||||||
|
#include <XCEngine/Rendering/Execution/FrameExecutionContext.h>
|
||||||
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
#include <XCEngine/Rendering/Builtin/BuiltinPassTypes.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
|
||||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||||
#include <XCEngine/Rendering/RenderPass.h>
|
#include <XCEngine/Rendering/RenderPass.h>
|
||||||
#include <XCEngine/Rendering/RenderPipeline.h>
|
#include <XCEngine/Rendering/RenderPipeline.h>
|
||||||
#include <XCEngine/Rendering/RenderPipelineAsset.h>
|
#include <XCEngine/Rendering/RenderPipelineAsset.h>
|
||||||
|
#include <XCEngine/Rendering/SceneRenderFeaturePass.h>
|
||||||
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
#include <XCEngine/Rendering/Caches/RenderResourceCache.h>
|
||||||
|
|
||||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||||
@@ -50,16 +53,21 @@ public:
|
|||||||
BuiltinForwardPipeline();
|
BuiltinForwardPipeline();
|
||||||
~BuiltinForwardPipeline() override;
|
~BuiltinForwardPipeline() override;
|
||||||
|
|
||||||
|
using RenderPipeline::Render;
|
||||||
|
|
||||||
static RHI::InputLayoutDesc BuildInputLayout();
|
static RHI::InputLayoutDesc BuildInputLayout();
|
||||||
|
|
||||||
bool Initialize(const RenderContext& context) override;
|
bool Initialize(const RenderContext& context) override;
|
||||||
void Shutdown() override;
|
void Shutdown() override;
|
||||||
|
bool Render(const FrameExecutionContext& executionContext) override;
|
||||||
bool Render(
|
bool Render(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSurface& surface,
|
const RenderSurface& surface,
|
||||||
const RenderSceneData& sceneData) override;
|
const RenderSceneData& sceneData) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using ForwardSceneFeaturePassArray = std::array<SceneRenderFeaturePass*, 2>;
|
||||||
|
|
||||||
struct OwnedDescriptorSet {
|
struct OwnedDescriptorSet {
|
||||||
RHI::RHIDescriptorPool* pool = nullptr;
|
RHI::RHIDescriptorPool* pool = nullptr;
|
||||||
RHI::RHIDescriptorSet* set = nullptr;
|
RHI::RHIDescriptorSet* set = nullptr;
|
||||||
@@ -91,8 +99,8 @@ private:
|
|||||||
|
|
||||||
struct ShadowReceiverConstants {
|
struct ShadowReceiverConstants {
|
||||||
Math::Matrix4x4 worldToShadow = Math::Matrix4x4::Identity();
|
Math::Matrix4x4 worldToShadow = Math::Matrix4x4::Identity();
|
||||||
Math::Vector4 shadowBiasAndTexelSize = Math::Vector4::Zero();
|
RenderDirectionalShadowMapMetrics shadowMapMetrics = {};
|
||||||
Math::Vector4 shadowOptions = Math::Vector4::Zero();
|
RenderDirectionalShadowSamplingData shadowSampling = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SkyboxConstants {
|
struct SkyboxConstants {
|
||||||
@@ -263,6 +271,19 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool EnsureInitialized(const RenderContext& context);
|
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);
|
bool CreatePipelineResources(const RenderContext& context);
|
||||||
void DestroyPipelineResources();
|
void DestroyPipelineResources();
|
||||||
static bool TryResolveSurfacePassType(
|
static bool TryResolveSurfacePassType(
|
||||||
@@ -315,18 +336,14 @@ private:
|
|||||||
bool HasProceduralSkybox(const RenderSceneData& sceneData) const;
|
bool HasProceduralSkybox(const RenderSceneData& sceneData) const;
|
||||||
bool BeginForwardScenePass(const RenderPassContext& context);
|
bool BeginForwardScenePass(const RenderPassContext& context);
|
||||||
void EndForwardScenePass(const RenderPassContext& context);
|
void EndForwardScenePass(const RenderPassContext& context);
|
||||||
bool ExecuteForwardOpaquePass(const RenderPassContext& context);
|
bool ExecuteForwardOpaquePass(const ScenePhaseExecutionContext& context);
|
||||||
bool ExecuteForwardSkyboxPass(const RenderPassContext& context);
|
bool ExecuteForwardSkyboxPass(const RenderPassContext& context);
|
||||||
bool ExecuteForwardTransparentPass(const RenderPassContext& context);
|
bool ExecuteForwardTransparentPass(const ScenePhaseExecutionContext& context);
|
||||||
bool DrawVisibleItems(
|
bool DrawVisibleItems(
|
||||||
const RenderContext& context,
|
const FrameExecutionContext& executionContext,
|
||||||
const RenderSurface& surface,
|
const DrawSettings& drawSettings);
|
||||||
const RenderSceneData& sceneData,
|
|
||||||
bool drawTransparentItems);
|
|
||||||
bool DrawVisibleItem(
|
bool DrawVisibleItem(
|
||||||
const RenderContext& context,
|
const FrameExecutionContext& executionContext,
|
||||||
const RenderSurface& surface,
|
|
||||||
const RenderSceneData& sceneData,
|
|
||||||
const VisibleRenderItem& visibleItem);
|
const VisibleRenderItem& visibleItem);
|
||||||
bool EnsureSkyboxResources(const RenderContext& context);
|
bool EnsureSkyboxResources(const RenderContext& context);
|
||||||
bool CreateSkyboxResources(const RenderContext& context);
|
bool CreateSkyboxResources(const RenderContext& context);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
#include <XCEngine/Rendering/Execution/FrameStageRenderRequest.h>
|
||||||
#include <XCEngine/Rendering/Planning/FinalColorSettings.h>
|
#include <XCEngine/Rendering/Planning/FinalColorSettings.h>
|
||||||
#include <XCEngine/Rendering/RenderContext.h>
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
#include <XCEngine/Rendering/RenderPass.h>
|
#include <XCEngine/Rendering/Shadow/DirectionalShadowData.h>
|
||||||
#include <XCEngine/Rendering/RenderSurface.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -15,187 +13,8 @@ class CameraComponent;
|
|||||||
class Scene;
|
class Scene;
|
||||||
} // namespace Components
|
} // namespace Components
|
||||||
|
|
||||||
namespace RHI {
|
|
||||||
class RHIResourceView;
|
|
||||||
} // namespace RHI
|
|
||||||
|
|
||||||
namespace Rendering {
|
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;
|
|
||||||
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 {
|
struct CameraRenderRequest {
|
||||||
const Components::Scene* scene = nullptr;
|
const Components::Scene* scene = nullptr;
|
||||||
Components::CameraComponent* camera = nullptr;
|
Components::CameraComponent* camera = nullptr;
|
||||||
@@ -217,147 +36,6 @@ struct CameraRenderRequest {
|
|||||||
RenderPassSequence* postScenePasses = nullptr;
|
RenderPassSequence* postScenePasses = nullptr;
|
||||||
RenderPassSequence* overlayPasses = 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 {
|
bool IsValid() const {
|
||||||
return scene != nullptr &&
|
return scene != nullptr &&
|
||||||
camera != nullptr &&
|
camera != nullptr &&
|
||||||
|
|||||||
@@ -12,15 +12,22 @@ class Scene;
|
|||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
|
// Planner-owned knobs for the single main-light shadow, including both camera
|
||||||
|
// fitting and the default sampling / caster bias values emitted into the
|
||||||
|
// runtime shadow contract.
|
||||||
struct DirectionalShadowPlanningSettings {
|
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 minFocusDistance = 5.0f;
|
||||||
float maxFocusDistance = 64.0f;
|
float maxFocusDistance = 32.0f;
|
||||||
float perspectiveFocusFactor = 1.0f;
|
float perspectiveFocusFactor = 1.0f;
|
||||||
float orthographicFocusFactor = 2.0f;
|
float orthographicFocusFactor = 2.0f;
|
||||||
float minDepthRange = 20.0f;
|
float minDepthRange = 20.0f;
|
||||||
float boundsPadding = 1.0f;
|
float boundsPadding = 0.5f;
|
||||||
float minDepthPadding = 2.0f;
|
float minDepthPadding = 2.0f;
|
||||||
|
DirectionalShadowSamplingSettings sampling = {};
|
||||||
|
DirectionalShadowCasterBiasSettings casterBias = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class SceneRenderRequestPlanner {
|
class SceneRenderRequestPlanner {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/FrameExecutionContext.h>
|
||||||
#include <XCEngine/Rendering/RenderContext.h>
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -25,6 +26,23 @@ struct RenderPassContext {
|
|||||||
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
|
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 {
|
class RenderPass {
|
||||||
public:
|
public:
|
||||||
virtual ~RenderPass() = default;
|
virtual ~RenderPass() = default;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/FrameExecutionContext.h>
|
||||||
#include <XCEngine/Rendering/FrameData/RenderSceneData.h>
|
#include <XCEngine/Rendering/FrameData/RenderSceneData.h>
|
||||||
#include <XCEngine/Rendering/RenderContext.h>
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
|
||||||
@@ -14,6 +15,12 @@ public:
|
|||||||
|
|
||||||
virtual bool Initialize(const RenderContext& context) = 0;
|
virtual bool Initialize(const RenderContext& context) = 0;
|
||||||
virtual void Shutdown() = 0;
|
virtual void Shutdown() = 0;
|
||||||
|
virtual bool Render(const FrameExecutionContext& executionContext) {
|
||||||
|
return Render(
|
||||||
|
executionContext.renderContext,
|
||||||
|
executionContext.surface,
|
||||||
|
executionContext.sceneData);
|
||||||
|
}
|
||||||
virtual bool Render(
|
virtual bool Render(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSurface& surface,
|
const RenderSurface& surface,
|
||||||
|
|||||||
24
engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h
Normal file
24
engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/RenderPass.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class SceneRenderFeaturePass : public RenderPass {
|
||||||
|
public:
|
||||||
|
~SceneRenderFeaturePass() override = default;
|
||||||
|
|
||||||
|
virtual bool IsActive(const RenderSceneData& sceneData) const = 0;
|
||||||
|
|
||||||
|
virtual bool Prepare(
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
const RenderSceneData& sceneData) {
|
||||||
|
(void)renderContext;
|
||||||
|
(void)sceneData;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Math/Matrix4.h>
|
||||||
|
#include <XCEngine/Core/Math/Vector2.h>
|
||||||
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
|
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
|
||||||
|
#include <XCEngine/Rendering/Shadow/DirectionalShadowSettings.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHIResourceView;
|
||||||
|
} // namespace RHI
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct DirectionalShadowRenderPlan {
|
||||||
|
bool enabled = false;
|
||||||
|
Math::Vector3 lightDirection = Math::Vector3::Back();
|
||||||
|
Math::Vector3 focusPoint = Math::Vector3::Zero();
|
||||||
|
float orthographicHalfExtent = 0.0f;
|
||||||
|
float texelWorldSize = 0.0f;
|
||||||
|
float nearClipPlane = 0.1f;
|
||||||
|
float farClipPlane = 0.0f;
|
||||||
|
DirectionalShadowSamplingSettings sampling = {};
|
||||||
|
DirectionalShadowCasterBiasSettings casterBias = {};
|
||||||
|
uint32_t mapWidth = 0;
|
||||||
|
uint32_t mapHeight = 0;
|
||||||
|
RenderCameraData cameraData = {};
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return enabled &&
|
||||||
|
mapWidth > 0 &&
|
||||||
|
mapHeight > 0 &&
|
||||||
|
cameraData.viewportWidth == mapWidth &&
|
||||||
|
cameraData.viewportHeight == mapHeight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderDirectionalShadowMapMetrics {
|
||||||
|
Math::Vector2 inverseMapSize = Math::Vector2::Zero();
|
||||||
|
float worldTexelSize = 0.0f;
|
||||||
|
float padding = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
sizeof(RenderDirectionalShadowMapMetrics) == sizeof(float) * 4u,
|
||||||
|
"RenderDirectionalShadowMapMetrics must stay float4-sized for GPU constant layout");
|
||||||
|
|
||||||
|
struct RenderDirectionalShadowSamplingData {
|
||||||
|
float enabled = 0.0f;
|
||||||
|
DirectionalShadowSamplingSettings settings = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u,
|
||||||
|
"RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout");
|
||||||
|
|
||||||
|
struct RenderDirectionalShadowCasterBiasData {
|
||||||
|
DirectionalShadowCasterBiasSettings settings = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderDirectionalShadowData {
|
||||||
|
bool enabled = false;
|
||||||
|
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
|
||||||
|
RenderDirectionalShadowMapMetrics mapMetrics = {};
|
||||||
|
RenderDirectionalShadowSamplingData sampling = {};
|
||||||
|
RenderDirectionalShadowCasterBiasData casterBias = {};
|
||||||
|
RHI::RHIResourceView* shadowMap = nullptr;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return enabled && shadowMap != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RenderDirectionalShadowData BuildRenderDirectionalShadowData(
|
||||||
|
const DirectionalShadowRenderPlan& plan,
|
||||||
|
RHI::RHIResourceView* shadowMapView);
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.h>
|
||||||
|
#include <XCEngine/Rendering/Execution/DirectionalShadowExecutionState.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct CameraFramePlan;
|
||||||
|
|
||||||
|
class DirectionalShadowRuntime {
|
||||||
|
public:
|
||||||
|
DirectionalShadowRuntime() = default;
|
||||||
|
DirectionalShadowRuntime(const DirectionalShadowRuntime&) = delete;
|
||||||
|
DirectionalShadowRuntime& operator=(const DirectionalShadowRuntime&) = delete;
|
||||||
|
~DirectionalShadowRuntime() = default;
|
||||||
|
|
||||||
|
bool ResolveExecutionState(
|
||||||
|
const CameraFramePlan& plan,
|
||||||
|
DirectionalShadowExecutionState& outShadowState);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DirectionalShadowSurfaceCache m_surfaceCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct DirectionalShadowSamplingSettings {
|
||||||
|
// Keep receiver bias modest and rely more on caster / normal bias so
|
||||||
|
// contact shadows do not detach too aggressively.
|
||||||
|
float receiverDepthBias = 0.0010f;
|
||||||
|
float normalBiasScale = 2.0f;
|
||||||
|
float shadowStrength = 0.85f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DirectionalShadowCasterBiasSettings {
|
||||||
|
// Single-map directional shadows need a stronger raster bias baseline than
|
||||||
|
// the previous placeholder values to keep acne under control.
|
||||||
|
float depthBiasFactor = 2.5f;
|
||||||
|
int32_t depthBiasUnits = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -49,6 +49,25 @@ inline float ClampFiniteExtent(float value, float minValue, float maxValue) {
|
|||||||
return (std::clamp)(value, minValue, maxValue);
|
return (std::clamp)(value, minValue, maxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float ResolveOverflowPrimaryExtentFromMinimums(
|
||||||
|
float usableExtent,
|
||||||
|
float preferredPrimaryMinimum,
|
||||||
|
float preferredSecondaryMinimum) {
|
||||||
|
const float clampedUsableExtent = ClampSplitterExtent(usableExtent);
|
||||||
|
const float primaryMinimum = ClampSplitterExtent(preferredPrimaryMinimum);
|
||||||
|
const float secondaryMinimum = ClampSplitterExtent(preferredSecondaryMinimum);
|
||||||
|
const float totalMinimum = primaryMinimum + secondaryMinimum;
|
||||||
|
if (clampedUsableExtent <= 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalMinimum <= 0.0f) {
|
||||||
|
return clampedUsableExtent * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clampedUsableExtent * (primaryMinimum / totalMinimum);
|
||||||
|
}
|
||||||
|
|
||||||
inline float GetMainExtent(const UISize& size, UILayoutAxis axis) {
|
inline float GetMainExtent(const UISize& size, UILayoutAxis axis) {
|
||||||
return axis == UILayoutAxis::Horizontal ? size.width : size.height;
|
return axis == UILayoutAxis::Horizontal ? size.width : size.height;
|
||||||
}
|
}
|
||||||
@@ -108,8 +127,12 @@ inline float ClampSplitterRatio(
|
|||||||
float minimumPrimaryExtent = minPrimaryExtent;
|
float minimumPrimaryExtent = minPrimaryExtent;
|
||||||
float maximumPrimaryExtent = usableExtent - minSecondaryExtent;
|
float maximumPrimaryExtent = usableExtent - minSecondaryExtent;
|
||||||
if (minimumPrimaryExtent > maximumPrimaryExtent) {
|
if (minimumPrimaryExtent > maximumPrimaryExtent) {
|
||||||
minimumPrimaryExtent = 0.0f;
|
const float overflowPrimaryExtent =
|
||||||
maximumPrimaryExtent = usableExtent;
|
SplitterDetail::ResolveOverflowPrimaryExtentFromMinimums(
|
||||||
|
usableExtent,
|
||||||
|
options.minPrimaryExtent,
|
||||||
|
options.minSecondaryExtent);
|
||||||
|
return usableExtent <= 0.0f ? 0.5f : overflowPrimaryExtent / usableExtent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float clampedPrimaryExtent = (std::clamp)(
|
const float clampedPrimaryExtent = (std::clamp)(
|
||||||
@@ -161,7 +184,20 @@ inline float ClampSplitterRatio(
|
|||||||
|
|
||||||
minimumPrimaryExtent = (std::max)(minimumPrimaryExtent, minimumFromSecondary);
|
minimumPrimaryExtent = (std::max)(minimumPrimaryExtent, minimumFromSecondary);
|
||||||
maximumPrimaryExtent = (std::min)(maximumPrimaryExtent, maximumFromSecondary);
|
maximumPrimaryExtent = (std::min)(maximumPrimaryExtent, maximumFromSecondary);
|
||||||
|
const bool minimumsOverflow =
|
||||||
|
SplitterDetail::ClampSplitterExtent(constraints.primaryMin) +
|
||||||
|
SplitterDetail::ClampSplitterExtent(constraints.secondaryMin) >
|
||||||
|
usableExtent;
|
||||||
if (minimumPrimaryExtent > maximumPrimaryExtent) {
|
if (minimumPrimaryExtent > maximumPrimaryExtent) {
|
||||||
|
if (minimumsOverflow) {
|
||||||
|
const float overflowPrimaryExtent =
|
||||||
|
SplitterDetail::ResolveOverflowPrimaryExtentFromMinimums(
|
||||||
|
usableExtent,
|
||||||
|
constraints.primaryMin,
|
||||||
|
constraints.secondaryMin);
|
||||||
|
return usableExtent <= 0.0f ? 0.5f : overflowPrimaryExtent / usableExtent;
|
||||||
|
}
|
||||||
|
|
||||||
minimumPrimaryExtent = 0.0f;
|
minimumPrimaryExtent = 0.0f;
|
||||||
maximumPrimaryExtent = usableExtent;
|
maximumPrimaryExtent = usableExtent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* comm
|
|||||||
m_width = width;
|
m_width = width;
|
||||||
m_height = height;
|
m_height = height;
|
||||||
m_bufferCount = bufferCount;
|
m_bufferCount = bufferCount;
|
||||||
|
m_lastResizeResult = S_OK;
|
||||||
|
|
||||||
return RefreshBackBuffers();
|
return RefreshBackBuffers();
|
||||||
}
|
}
|
||||||
@@ -61,6 +62,7 @@ bool D3D12SwapChain::Initialize(IDXGISwapChain* swapChain, uint32_t width, uint3
|
|||||||
m_width = width;
|
m_width = width;
|
||||||
m_height = height;
|
m_height = height;
|
||||||
m_bufferCount = desc.BufferCount;
|
m_bufferCount = desc.BufferCount;
|
||||||
|
m_lastResizeResult = S_OK;
|
||||||
|
|
||||||
return RefreshBackBuffers();
|
return RefreshBackBuffers();
|
||||||
}
|
}
|
||||||
@@ -71,20 +73,31 @@ void D3D12SwapChain::Shutdown() {
|
|||||||
m_swapChain.Reset();
|
m_swapChain.Reset();
|
||||||
m_width = 0;
|
m_width = 0;
|
||||||
m_height = 0;
|
m_height = 0;
|
||||||
|
m_lastResizeResult = S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const {
|
uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const {
|
||||||
return m_swapChain->GetCurrentBackBufferIndex();
|
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) {
|
D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) {
|
||||||
assert(index < m_backBuffers.size() && "BackBuffer index out of range");
|
D3D12Texture* backBuffer = TryGetBackBuffer(index);
|
||||||
return m_backBuffers[index];
|
assert(backBuffer != nullptr && "BackBuffer index out of range");
|
||||||
|
return *backBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const {
|
const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const {
|
||||||
assert(index < m_backBuffers.size() && "BackBuffer index out of range");
|
const D3D12Texture* backBuffer = TryGetBackBuffer(index);
|
||||||
return m_backBuffers[index];
|
assert(backBuffer != nullptr && "BackBuffer index out of range");
|
||||||
|
return *backBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) {
|
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) {
|
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();
|
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)) {
|
if (FAILED(hResult)) {
|
||||||
|
m_width = previousWidth;
|
||||||
|
m_height = previousHeight;
|
||||||
|
RefreshBackBuffers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Rendering/Caches/DirectionalShadowSurfaceCache.h"
|
#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/RHIDevice.h"
|
||||||
#include "RHI/RHIResourceView.h"
|
#include "RHI/RHIResourceView.h"
|
||||||
#include "RHI/RHITexture.h"
|
#include "RHI/RHITexture.h"
|
||||||
@@ -18,15 +19,15 @@ DirectionalShadowSurfaceCache::~DirectionalShadowSurfaceCache() {
|
|||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DirectionalShadowSurfaceCache::EnsureSurface(
|
const DirectionalShadowSurfaceAllocation* DirectionalShadowSurfaceCache::Resolve(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const DirectionalShadowRenderPlan& plan) {
|
const DirectionalShadowRenderPlan& plan) {
|
||||||
if (!context.IsValid() || !plan.IsValid()) {
|
if (!context.IsValid() || !plan.IsValid()) {
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Matches(context, plan)) {
|
if (Matches(context, plan)) {
|
||||||
return true;
|
return &m_allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::TextureDesc depthDesc = {};
|
RHI::TextureDesc depthDesc = {};
|
||||||
@@ -43,7 +44,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
|||||||
|
|
||||||
RHI::RHITexture* depthTexture = context.device->CreateTexture(depthDesc);
|
RHI::RHITexture* depthTexture = context.device->CreateTexture(depthDesc);
|
||||||
if (depthTexture == nullptr) {
|
if (depthTexture == nullptr) {
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::ResourceViewDesc depthViewDesc = {};
|
RHI::ResourceViewDesc depthViewDesc = {};
|
||||||
@@ -55,7 +56,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
|||||||
if (depthView == nullptr) {
|
if (depthView == nullptr) {
|
||||||
depthTexture->Shutdown();
|
depthTexture->Shutdown();
|
||||||
delete depthTexture;
|
delete depthTexture;
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::ResourceViewDesc depthShaderViewDesc = {};
|
RHI::ResourceViewDesc depthShaderViewDesc = {};
|
||||||
@@ -68,7 +69,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
|||||||
delete depthView;
|
delete depthView;
|
||||||
depthTexture->Shutdown();
|
depthTexture->Shutdown();
|
||||||
delete depthTexture;
|
delete depthTexture;
|
||||||
return false;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reset();
|
Reset();
|
||||||
@@ -77,11 +78,11 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
|||||||
m_height = plan.mapHeight;
|
m_height = plan.mapHeight;
|
||||||
m_depthTexture = depthTexture;
|
m_depthTexture = depthTexture;
|
||||||
m_depthView = depthView;
|
m_depthView = depthView;
|
||||||
m_depthShaderView = depthShaderView;
|
m_allocation.depthShaderView = depthShaderView;
|
||||||
m_surface = RenderSurface(plan.mapWidth, plan.mapHeight);
|
m_allocation.surface = RenderSurface(plan.mapWidth, plan.mapHeight);
|
||||||
m_surface.SetDepthAttachment(depthView);
|
m_allocation.surface.SetDepthAttachment(depthView);
|
||||||
m_surface.SetSampleDesc(1u, 0u);
|
m_allocation.surface.SetSampleDesc(1u, 0u);
|
||||||
return true;
|
return &m_allocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DirectionalShadowSurfaceCache::Matches(
|
bool DirectionalShadowSurfaceCache::Matches(
|
||||||
@@ -92,7 +93,7 @@ bool DirectionalShadowSurfaceCache::Matches(
|
|||||||
m_height == plan.mapHeight &&
|
m_height == plan.mapHeight &&
|
||||||
m_depthTexture != nullptr &&
|
m_depthTexture != nullptr &&
|
||||||
m_depthView != nullptr &&
|
m_depthView != nullptr &&
|
||||||
m_depthShaderView != nullptr;
|
m_allocation.IsValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DirectionalShadowSurfaceCache::Reset() {
|
void DirectionalShadowSurfaceCache::Reset() {
|
||||||
@@ -102,10 +103,10 @@ void DirectionalShadowSurfaceCache::Reset() {
|
|||||||
m_depthView = nullptr;
|
m_depthView = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_depthShaderView != nullptr) {
|
if (m_allocation.depthShaderView != nullptr) {
|
||||||
m_depthShaderView->Shutdown();
|
m_allocation.depthShaderView->Shutdown();
|
||||||
delete m_depthShaderView;
|
delete m_allocation.depthShaderView;
|
||||||
m_depthShaderView = nullptr;
|
m_allocation.depthShaderView = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_depthTexture != nullptr) {
|
if (m_depthTexture != nullptr) {
|
||||||
@@ -117,7 +118,7 @@ void DirectionalShadowSurfaceCache::Reset() {
|
|||||||
m_device = nullptr;
|
m_device = nullptr;
|
||||||
m_width = 0;
|
m_width = 0;
|
||||||
m_height = 0;
|
m_height = 0;
|
||||||
m_surface = RenderSurface();
|
m_allocation = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
#include "Rendering/Execution/CameraRenderer.h"
|
#include "Rendering/Execution/CameraRenderer.h"
|
||||||
|
|
||||||
#include "Components/CameraComponent.h"
|
#include "Components/CameraComponent.h"
|
||||||
#include "Rendering/Caches/DirectionalShadowSurfaceCache.h"
|
|
||||||
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||||
|
#include "Rendering/Execution/DirectionalShadowExecutionState.h"
|
||||||
|
#include "Rendering/Graph/RenderGraph.h"
|
||||||
|
#include "Rendering/Graph/RenderGraphCompiler.h"
|
||||||
|
#include "Rendering/Graph/RenderGraphExecutor.h"
|
||||||
#include "Rendering/Passes/BuiltinDepthOnlyPass.h"
|
#include "Rendering/Passes/BuiltinDepthOnlyPass.h"
|
||||||
#include "Rendering/Passes/BuiltinObjectIdPass.h"
|
#include "Rendering/Passes/BuiltinObjectIdPass.h"
|
||||||
#include "Rendering/Passes/BuiltinShadowCasterPass.h"
|
#include "Rendering/Passes/BuiltinShadowCasterPass.h"
|
||||||
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||||
#include "Rendering/RenderPipelineAsset.h"
|
#include "Rendering/RenderPipelineAsset.h"
|
||||||
#include "Rendering/RenderSurface.h"
|
#include "Rendering/RenderSurface.h"
|
||||||
|
#include "Rendering/Shadow/DirectionalShadowRuntime.h"
|
||||||
#include "RHI/RHIResourceView.h"
|
#include "RHI/RHIResourceView.h"
|
||||||
#include "Scene/Scene.h"
|
#include "Scene/Scene.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr RHI::Format kRenderGraphImportedColorFormat = RHI::Format::R8G8B8A8_UNorm;
|
||||||
|
constexpr RHI::Format kRenderGraphImportedDepthFormat = RHI::Format::D24_UNorm_S8_UInt;
|
||||||
|
|
||||||
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() {
|
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() {
|
||||||
static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset =
|
static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset =
|
||||||
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
|
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
|
||||||
@@ -45,6 +54,229 @@ std::unique_ptr<RenderPipeline> CreatePipelineFromAsset(
|
|||||||
return std::make_unique<Pipelines::BuiltinForwardPipeline>();
|
return std::make_unique<Pipelines::BuiltinForwardPipeline>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RenderGraphImportedSurface {
|
||||||
|
std::vector<RenderGraphTextureHandle> colorTextures = {};
|
||||||
|
RenderGraphTextureHandle depthTexture = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
using RenderGraphImportedTextureRegistry =
|
||||||
|
std::unordered_map<RHI::RHIResourceView*, RenderGraphTextureHandle>;
|
||||||
|
|
||||||
|
enum class RenderGraphSurfaceImportUsage {
|
||||||
|
Source = 0,
|
||||||
|
Output = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
Containers::String BuildRenderGraphResourceName(
|
||||||
|
const Containers::String& surfaceName,
|
||||||
|
const char* slotName,
|
||||||
|
size_t index = 0u,
|
||||||
|
bool indexed = false) {
|
||||||
|
std::string name = surfaceName.CStr();
|
||||||
|
name += '.';
|
||||||
|
name += slotName;
|
||||||
|
if (indexed) {
|
||||||
|
name += std::to_string(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Containers::String(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureDesc BuildImportedTextureDesc(
|
||||||
|
const RenderSurface& surface,
|
||||||
|
RHI::Format format) {
|
||||||
|
RenderGraphTextureDesc desc = {};
|
||||||
|
desc.width = surface.GetWidth() > 0u ? surface.GetWidth() : surface.GetRenderAreaWidth();
|
||||||
|
desc.height = surface.GetHeight() > 0u ? surface.GetHeight() : surface.GetRenderAreaHeight();
|
||||||
|
desc.format = static_cast<Core::uint32>(format);
|
||||||
|
desc.textureType = static_cast<Core::uint32>(RHI::TextureType::Texture2D);
|
||||||
|
desc.sampleCount = std::max(surface.GetSampleCount(), 1u);
|
||||||
|
desc.sampleQuality = surface.GetSampleQuality();
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphImportedTextureOptions BuildImportedTextureOptions(
|
||||||
|
const RenderSurface& surface,
|
||||||
|
bool isDepth,
|
||||||
|
RenderGraphSurfaceImportUsage usage) {
|
||||||
|
const RHI::ResourceStates beforeState =
|
||||||
|
isDepth ? surface.GetDepthStateBefore() : surface.GetColorStateBefore();
|
||||||
|
const RHI::ResourceStates afterState =
|
||||||
|
isDepth ? surface.GetDepthStateAfter() : surface.GetColorStateAfter();
|
||||||
|
|
||||||
|
RenderGraphImportedTextureOptions options = {};
|
||||||
|
options.initialState =
|
||||||
|
usage == RenderGraphSurfaceImportUsage::Output
|
||||||
|
? beforeState
|
||||||
|
: afterState;
|
||||||
|
options.finalState =
|
||||||
|
usage == RenderGraphSurfaceImportUsage::Output
|
||||||
|
? afterState
|
||||||
|
: options.initialState;
|
||||||
|
options.graphOwnsTransitions = false;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureHandle ImportRenderGraphTexture(
|
||||||
|
RenderGraphBuilder& builder,
|
||||||
|
RenderGraphImportedTextureRegistry& registry,
|
||||||
|
const Containers::String& name,
|
||||||
|
const RenderGraphTextureDesc& desc,
|
||||||
|
RHI::RHIResourceView* view,
|
||||||
|
const RenderGraphImportedTextureOptions& importedOptions) {
|
||||||
|
if (view == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto existing = registry.find(view);
|
||||||
|
if (existing != registry.end()) {
|
||||||
|
return existing->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderGraphTextureHandle handle =
|
||||||
|
builder.ImportTexture(name, desc, view, importedOptions);
|
||||||
|
registry.emplace(view, handle);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphImportedSurface ImportRenderGraphSurface(
|
||||||
|
RenderGraphBuilder& builder,
|
||||||
|
RenderGraphImportedTextureRegistry& registry,
|
||||||
|
const Containers::String& surfaceName,
|
||||||
|
const RenderSurface* surface,
|
||||||
|
RenderGraphSurfaceImportUsage usage) {
|
||||||
|
RenderGraphImportedSurface importedSurface = {};
|
||||||
|
if (surface == nullptr) {
|
||||||
|
return importedSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderGraphTextureDesc colorDesc =
|
||||||
|
BuildImportedTextureDesc(*surface, kRenderGraphImportedColorFormat);
|
||||||
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface->GetColorAttachments();
|
||||||
|
importedSurface.colorTextures.reserve(colorAttachments.size());
|
||||||
|
for (size_t colorIndex = 0; colorIndex < colorAttachments.size(); ++colorIndex) {
|
||||||
|
RHI::RHIResourceView* colorAttachment = colorAttachments[colorIndex];
|
||||||
|
if (colorAttachment == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
importedSurface.colorTextures.push_back(
|
||||||
|
ImportRenderGraphTexture(
|
||||||
|
builder,
|
||||||
|
registry,
|
||||||
|
BuildRenderGraphResourceName(surfaceName, "Color", colorIndex, true),
|
||||||
|
colorDesc,
|
||||||
|
colorAttachment,
|
||||||
|
BuildImportedTextureOptions(
|
||||||
|
*surface,
|
||||||
|
false,
|
||||||
|
usage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RHI::RHIResourceView* depthAttachment = surface->GetDepthAttachment();
|
||||||
|
depthAttachment != nullptr) {
|
||||||
|
importedSurface.depthTexture =
|
||||||
|
ImportRenderGraphTexture(
|
||||||
|
builder,
|
||||||
|
registry,
|
||||||
|
BuildRenderGraphResourceName(surfaceName, "Depth"),
|
||||||
|
BuildImportedTextureDesc(*surface, kRenderGraphImportedDepthFormat),
|
||||||
|
depthAttachment,
|
||||||
|
BuildImportedTextureOptions(
|
||||||
|
*surface,
|
||||||
|
true,
|
||||||
|
usage));
|
||||||
|
}
|
||||||
|
|
||||||
|
return importedSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureHandle GetPrimaryColorTexture(
|
||||||
|
const RenderGraphImportedSurface& surface) {
|
||||||
|
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||||
|
if (texture.IsValid()) {
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadRenderGraphSurface(
|
||||||
|
RenderGraphPassBuilder& passBuilder,
|
||||||
|
const RenderGraphImportedSurface& surface) {
|
||||||
|
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||||
|
if (texture.IsValid()) {
|
||||||
|
passBuilder.ReadTexture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface.depthTexture.IsValid()) {
|
||||||
|
passBuilder.ReadTexture(surface.depthTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadRenderGraphColorSurface(
|
||||||
|
RenderGraphPassBuilder& passBuilder,
|
||||||
|
const RenderGraphImportedSurface& surface) {
|
||||||
|
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||||
|
if (texture.IsValid()) {
|
||||||
|
passBuilder.ReadTexture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteRenderGraphSurface(
|
||||||
|
RenderGraphPassBuilder& passBuilder,
|
||||||
|
const RenderGraphImportedSurface& surface) {
|
||||||
|
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||||
|
if (texture.IsValid()) {
|
||||||
|
passBuilder.WriteTexture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface.depthTexture.IsValid()) {
|
||||||
|
passBuilder.WriteTexture(surface.depthTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteRenderGraphColorSurface(
|
||||||
|
RenderGraphPassBuilder& passBuilder,
|
||||||
|
const RenderGraphImportedSurface& surface) {
|
||||||
|
for (RenderGraphTextureHandle texture : surface.colorTextures) {
|
||||||
|
if (texture.IsValid()) {
|
||||||
|
passBuilder.WriteTexture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsFullscreenSequenceStage(
|
||||||
|
CameraFrameStage stage) {
|
||||||
|
return stage == CameraFrameStage::PostProcess ||
|
||||||
|
stage == CameraFrameStage::FinalOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
Containers::String BuildRenderGraphSequencePassName(
|
||||||
|
const Containers::String& stageName,
|
||||||
|
size_t passIndex) {
|
||||||
|
std::string name = stageName.CStr();
|
||||||
|
name += ".Pass";
|
||||||
|
name += std::to_string(passIndex);
|
||||||
|
return Containers::String(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureDesc BuildFullscreenTransientTextureDesc(
|
||||||
|
const RenderSurface& surface) {
|
||||||
|
RenderGraphTextureDesc desc = {};
|
||||||
|
desc.width = surface.GetWidth() > 0u ? surface.GetWidth() : surface.GetRenderAreaWidth();
|
||||||
|
desc.height = surface.GetHeight() > 0u ? surface.GetHeight() : surface.GetRenderAreaHeight();
|
||||||
|
desc.format = static_cast<Core::uint32>(kRenderGraphImportedColorFormat);
|
||||||
|
desc.textureType = static_cast<Core::uint32>(RHI::TextureType::Texture2D);
|
||||||
|
desc.sampleCount = 1u;
|
||||||
|
desc.sampleQuality = 0u;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
Resources::ShaderKeywordSet BuildSceneGlobalShaderKeywords(
|
Resources::ShaderKeywordSet BuildSceneGlobalShaderKeywords(
|
||||||
const RenderSceneData& sceneData) {
|
const RenderSceneData& sceneData) {
|
||||||
Resources::ShaderKeywordSet keywords = {};
|
Resources::ShaderKeywordSet keywords = {};
|
||||||
@@ -173,6 +405,17 @@ private:
|
|||||||
bool m_failed = false;
|
bool m_failed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool EnsureInitializedPassSequence(
|
||||||
|
std::unique_ptr<ScopedInitializedPassSequence>& activeSequence,
|
||||||
|
RenderPassSequence* sequence,
|
||||||
|
const RenderContext& context) {
|
||||||
|
if (activeSequence == nullptr) {
|
||||||
|
activeSequence = std::make_unique<ScopedInitializedPassSequence>(sequence, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeSequence->IsReady();
|
||||||
|
}
|
||||||
|
|
||||||
struct CameraFrameExecutionState {
|
struct CameraFrameExecutionState {
|
||||||
RenderPipeline* pipeline = nullptr;
|
RenderPipeline* pipeline = nullptr;
|
||||||
RenderPass* objectIdPass = nullptr;
|
RenderPass* objectIdPass = nullptr;
|
||||||
@@ -221,8 +464,13 @@ bool ExecuteFullscreenPassSequenceStage(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sequence == nullptr || sequence->GetPassCount() <= 1u) {
|
if (sequence == nullptr) {
|
||||||
return activeSequence->Execute(passContext);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence->GetPassCount() <= 1u) {
|
||||||
|
return sequence->GetPassCount() == 0u ||
|
||||||
|
sequence->ExecutePass(0u, passContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (surfaceCache == nullptr ||
|
if (surfaceCache == nullptr ||
|
||||||
@@ -287,139 +535,319 @@ bool ExecuteFullscreenPassSequenceStage(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TryBuildRenderGraphTransientSurface(
|
||||||
|
const RenderSurface& templateSurface,
|
||||||
|
const RenderGraphExecutionContext& graphContext,
|
||||||
|
RenderGraphTextureHandle textureHandle,
|
||||||
|
RenderSurface& outSurface) {
|
||||||
|
if (!textureHandle.IsValid() ||
|
||||||
|
!graphContext.IsTransientTexture(textureHandle)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureDesc textureDesc = {};
|
||||||
|
RHI::RHIResourceView* renderTargetView =
|
||||||
|
graphContext.ResolveTextureView(
|
||||||
|
textureHandle,
|
||||||
|
RenderGraphTextureViewType::RenderTarget);
|
||||||
|
if (renderTargetView == nullptr ||
|
||||||
|
!graphContext.TryGetTextureDesc(textureHandle, textureDesc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outSurface = templateSurface;
|
||||||
|
CopyIntermediateSurfaceLayout(templateSurface, outSurface);
|
||||||
|
outSurface.SetColorAttachment(renderTargetView);
|
||||||
|
outSurface.SetAutoTransitionEnabled(false);
|
||||||
|
outSurface.SetSampleDesc(textureDesc.sampleCount, textureDesc.sampleQuality);
|
||||||
|
outSurface.SetColorStateBefore(RHI::ResourceStates::RenderTarget);
|
||||||
|
outSurface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExecuteFullscreenPassSequencePass(
|
||||||
|
RenderPassSequence* sequence,
|
||||||
|
size_t passIndex,
|
||||||
|
const RenderPassContext& passContext,
|
||||||
|
const RenderGraphExecutionContext& graphContext,
|
||||||
|
RenderGraphTextureHandle sourceColorHandle,
|
||||||
|
RenderGraphTextureHandle outputColorHandle);
|
||||||
|
|
||||||
RenderPassContext BuildFrameStagePassContext(
|
RenderPassContext BuildFrameStagePassContext(
|
||||||
CameraFrameStage stage,
|
CameraFrameStage stage,
|
||||||
const CameraRenderRequest& request,
|
const CameraFramePlan& plan,
|
||||||
const RenderSceneData& sceneData) {
|
const RenderSceneData& sceneData) {
|
||||||
const RenderSurface* outputSurface = request.GetOutputSurface(stage);
|
const RenderSurface* outputSurface = plan.GetOutputSurface(stage);
|
||||||
return {
|
return {
|
||||||
request.context,
|
plan.request.context,
|
||||||
outputSurface != nullptr ? *outputSurface : request.surface,
|
outputSurface != nullptr ? *outputSurface : plan.request.surface,
|
||||||
sceneData,
|
sceneData,
|
||||||
request.GetSourceSurface(stage),
|
plan.GetSourceSurface(stage),
|
||||||
request.GetSourceColorView(stage),
|
plan.GetSourceColorView(stage),
|
||||||
request.GetSourceColorState(stage)
|
plan.GetSourceColorState(stage)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExecuteFrameStage(
|
bool ExecuteFrameStage(
|
||||||
CameraFrameStage stage,
|
CameraFrameStage stage,
|
||||||
const CameraRenderRequest& request,
|
const CameraFramePlan& plan,
|
||||||
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
const DirectionalShadowExecutionState& shadowState,
|
||||||
const RenderSceneData& sceneData,
|
const RenderSceneData& sceneData,
|
||||||
CameraFrameExecutionState& executionState) {
|
CameraFrameExecutionState& executionState) {
|
||||||
const RenderPassContext passContext = BuildFrameStagePassContext(stage, request, sceneData);
|
const RenderPassContext passContext = BuildFrameStagePassContext(stage, plan, sceneData);
|
||||||
|
|
||||||
switch (stage) {
|
switch (stage) {
|
||||||
case CameraFrameStage::PreScenePasses:
|
case CameraFrameStage::PreScenePasses:
|
||||||
return ExecutePassSequenceStage(
|
return ExecutePassSequenceStage(
|
||||||
executionState.preScenePasses,
|
executionState.preScenePasses,
|
||||||
request.GetPassSequence(stage),
|
plan.GetPassSequence(stage),
|
||||||
request.context,
|
plan.request.context,
|
||||||
passContext);
|
passContext);
|
||||||
case CameraFrameStage::ShadowCaster:
|
case CameraFrameStage::ShadowCaster:
|
||||||
return ExecuteScenePassRequest(
|
return ExecuteScenePassRequest(
|
||||||
executionState.shadowCasterPass,
|
executionState.shadowCasterPass,
|
||||||
resolvedShadowCaster,
|
shadowState.shadowCasterRequest,
|
||||||
request.context,
|
plan.request.context,
|
||||||
sceneData);
|
sceneData);
|
||||||
case CameraFrameStage::DepthOnly:
|
case CameraFrameStage::DepthOnly:
|
||||||
return ExecuteScenePassRequest(
|
return ExecuteScenePassRequest(
|
||||||
executionState.depthOnlyPass,
|
executionState.depthOnlyPass,
|
||||||
request.depthOnly,
|
plan.request.depthOnly,
|
||||||
request.context,
|
plan.request.context,
|
||||||
sceneData);
|
sceneData);
|
||||||
case CameraFrameStage::MainScene:
|
case CameraFrameStage::MainScene:
|
||||||
return executionState.pipeline != nullptr &&
|
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:
|
case CameraFrameStage::PostProcess:
|
||||||
return ExecuteFullscreenPassSequenceStage(
|
return ExecuteFullscreenPassSequenceStage(
|
||||||
executionState.postProcessPasses,
|
executionState.postProcessPasses,
|
||||||
request.GetPassSequence(stage),
|
plan.GetPassSequence(stage),
|
||||||
request.context,
|
plan.request.context,
|
||||||
passContext,
|
passContext,
|
||||||
executionState.postProcessSurfaceCache);
|
executionState.postProcessSurfaceCache);
|
||||||
case CameraFrameStage::FinalOutput:
|
case CameraFrameStage::FinalOutput:
|
||||||
return ExecuteFullscreenPassSequenceStage(
|
return ExecuteFullscreenPassSequenceStage(
|
||||||
executionState.finalOutputPasses,
|
executionState.finalOutputPasses,
|
||||||
request.GetPassSequence(stage),
|
plan.GetPassSequence(stage),
|
||||||
request.context,
|
plan.request.context,
|
||||||
passContext,
|
passContext,
|
||||||
executionState.finalOutputSurfaceCache);
|
executionState.finalOutputSurfaceCache);
|
||||||
case CameraFrameStage::ObjectId:
|
case CameraFrameStage::ObjectId:
|
||||||
return !request.objectId.IsRequested() ||
|
return !plan.request.objectId.IsRequested() ||
|
||||||
ExecuteStandalonePass(
|
ExecuteStandalonePass(
|
||||||
executionState.objectIdPass,
|
executionState.objectIdPass,
|
||||||
request.context,
|
plan.request.context,
|
||||||
request.objectId.surface,
|
plan.request.objectId.surface,
|
||||||
sceneData);
|
sceneData);
|
||||||
case CameraFrameStage::PostScenePasses:
|
case CameraFrameStage::PostScenePasses:
|
||||||
return ExecutePassSequenceStage(
|
return ExecutePassSequenceStage(
|
||||||
executionState.postScenePasses,
|
executionState.postScenePasses,
|
||||||
request.GetPassSequence(stage),
|
plan.GetPassSequence(stage),
|
||||||
request.context,
|
plan.request.context,
|
||||||
passContext);
|
passContext);
|
||||||
case CameraFrameStage::OverlayPasses:
|
case CameraFrameStage::OverlayPasses:
|
||||||
return ExecutePassSequenceStage(
|
return ExecutePassSequenceStage(
|
||||||
executionState.overlayPasses,
|
executionState.overlayPasses,
|
||||||
request.GetPassSequence(stage),
|
plan.GetPassSequence(stage),
|
||||||
request.context,
|
plan.request.context,
|
||||||
passContext);
|
passContext);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderDirectionalShadowData BuildDirectionalShadowData(
|
bool ExecuteRenderGraphPlan(
|
||||||
const DirectionalShadowRenderPlan& plan,
|
const CameraFramePlan& plan,
|
||||||
RHI::RHIResourceView* shadowMapView) {
|
const DirectionalShadowExecutionState& shadowState,
|
||||||
RenderDirectionalShadowData shadowData = {};
|
const RenderSceneData& sceneData,
|
||||||
if (!plan.IsValid() || shadowMapView == nullptr) {
|
CameraFrameExecutionState& executionState) {
|
||||||
return shadowData;
|
RenderGraph graph = {};
|
||||||
|
RenderGraphBuilder graphBuilder(graph);
|
||||||
|
RenderGraphImportedTextureRegistry importedTextures = {};
|
||||||
|
|
||||||
|
bool stageExecutionSucceeded = true;
|
||||||
|
for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) {
|
||||||
|
if (!plan.HasFrameStage(stageInfo.stage) &&
|
||||||
|
stageInfo.stage != CameraFrameStage::MainScene) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowData.enabled = true;
|
const CameraFrameStage stage = stageInfo.stage;
|
||||||
shadowData.viewProjection = plan.cameraData.viewProjection;
|
const Containers::String stageName(GetCameraFrameStageName(stageInfo.stage));
|
||||||
shadowData.shadowMap = shadowMapView;
|
RenderPassSequence* const stageSequence = plan.GetPassSequence(stage);
|
||||||
const float texelWorldSize = plan.texelWorldSize > Math::EPSILON
|
|
||||||
? plan.texelWorldSize
|
if (IsFullscreenSequenceStage(stage) &&
|
||||||
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
|
stageSequence != nullptr &&
|
||||||
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
|
stageSequence->GetPassCount() > 1u) {
|
||||||
: 0.0f);
|
const RenderPassContext stagePassContext =
|
||||||
shadowData.shadowParams = Math::Vector4(
|
BuildFrameStagePassContext(stage, plan, sceneData);
|
||||||
0.0015f,
|
const RenderGraphImportedSurface sourceSurface =
|
||||||
1.0f / static_cast<float>(plan.mapWidth),
|
ImportRenderGraphSurface(
|
||||||
1.0f / static_cast<float>(plan.mapHeight),
|
graphBuilder,
|
||||||
0.85f);
|
importedTextures,
|
||||||
shadowData.shadowOptions = Math::Vector4(
|
stageName + ".Source",
|
||||||
1.0f,
|
stagePassContext.sourceSurface,
|
||||||
texelWorldSize,
|
RenderGraphSurfaceImportUsage::Source);
|
||||||
1.5f,
|
const RenderGraphImportedSurface outputSurface =
|
||||||
0.0f);
|
ImportRenderGraphSurface(
|
||||||
return shadowData;
|
graphBuilder,
|
||||||
|
importedTextures,
|
||||||
|
stageName + ".Output",
|
||||||
|
&stagePassContext.surface,
|
||||||
|
RenderGraphSurfaceImportUsage::Output);
|
||||||
|
RenderGraphTextureHandle currentSourceColor = GetPrimaryColorTexture(sourceSurface);
|
||||||
|
const RenderGraphTextureHandle finalOutputColor =
|
||||||
|
GetPrimaryColorTexture(outputSurface);
|
||||||
|
const RenderGraphTextureDesc transientDesc =
|
||||||
|
BuildFullscreenTransientTextureDesc(stagePassContext.surface);
|
||||||
|
|
||||||
|
for (size_t passIndex = 0; passIndex < stageSequence->GetPassCount(); ++passIndex) {
|
||||||
|
const bool isLastPass = (passIndex + 1u) == stageSequence->GetPassCount();
|
||||||
|
const size_t sequencePassIndex = passIndex;
|
||||||
|
const RenderGraphTextureHandle passSourceColor = currentSourceColor;
|
||||||
|
const RenderGraphTextureHandle passOutputColor =
|
||||||
|
isLastPass
|
||||||
|
? finalOutputColor
|
||||||
|
: graphBuilder.CreateTransientTexture(
|
||||||
|
BuildRenderGraphSequencePassName(stageName, sequencePassIndex) + ".Color",
|
||||||
|
transientDesc);
|
||||||
|
|
||||||
|
graphBuilder.AddRasterPass(
|
||||||
|
BuildRenderGraphSequencePassName(stageName, sequencePassIndex),
|
||||||
|
[&, stage, sequencePassIndex, passSourceColor, passOutputColor](
|
||||||
|
RenderGraphPassBuilder& passBuilder) {
|
||||||
|
if (passSourceColor.IsValid()) {
|
||||||
|
passBuilder.ReadTexture(passSourceColor);
|
||||||
|
}
|
||||||
|
if (passOutputColor.IsValid()) {
|
||||||
|
passBuilder.WriteTexture(passOutputColor);
|
||||||
|
}
|
||||||
|
passBuilder.SetExecuteCallback(
|
||||||
|
[&, stage, sequencePassIndex, passSourceColor, passOutputColor](
|
||||||
|
const RenderGraphExecutionContext& executionContext) {
|
||||||
|
if (!stageExecutionSucceeded) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderEnvironmentData BuildEnvironmentData(const CameraRenderRequest& request) {
|
std::unique_ptr<ScopedInitializedPassSequence>& activeSequence =
|
||||||
|
stage == CameraFrameStage::PostProcess
|
||||||
|
? executionState.postProcessPasses
|
||||||
|
: executionState.finalOutputPasses;
|
||||||
|
if (!EnsureInitializedPassSequence(
|
||||||
|
activeSequence,
|
||||||
|
plan.GetPassSequence(stage),
|
||||||
|
plan.request.context)) {
|
||||||
|
stageExecutionSucceeded = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stageExecutionSucceeded =
|
||||||
|
ExecuteFullscreenPassSequencePass(
|
||||||
|
plan.GetPassSequence(stage),
|
||||||
|
sequencePassIndex,
|
||||||
|
BuildFrameStagePassContext(stage, plan, sceneData),
|
||||||
|
executionContext,
|
||||||
|
passSourceColor,
|
||||||
|
passOutputColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
currentSourceColor = passOutputColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderGraphImportedSurface sourceSurface =
|
||||||
|
ImportRenderGraphSurface(
|
||||||
|
graphBuilder,
|
||||||
|
importedTextures,
|
||||||
|
stageName + ".Source",
|
||||||
|
plan.GetSourceSurface(stageInfo.stage),
|
||||||
|
RenderGraphSurfaceImportUsage::Source);
|
||||||
|
const RenderGraphImportedSurface outputSurface =
|
||||||
|
ImportRenderGraphSurface(
|
||||||
|
graphBuilder,
|
||||||
|
importedTextures,
|
||||||
|
stageName + ".Output",
|
||||||
|
plan.GetOutputSurface(stageInfo.stage),
|
||||||
|
RenderGraphSurfaceImportUsage::Output);
|
||||||
|
|
||||||
|
graphBuilder.AddRasterPass(
|
||||||
|
stageName,
|
||||||
|
[&, sourceSurface, outputSurface, stage](RenderGraphPassBuilder& passBuilder) {
|
||||||
|
if (IsFullscreenSequenceStage(stage)) {
|
||||||
|
ReadRenderGraphColorSurface(passBuilder, sourceSurface);
|
||||||
|
WriteRenderGraphColorSurface(passBuilder, outputSurface);
|
||||||
|
} else {
|
||||||
|
ReadRenderGraphSurface(passBuilder, sourceSurface);
|
||||||
|
WriteRenderGraphSurface(passBuilder, outputSurface);
|
||||||
|
}
|
||||||
|
passBuilder.SetExecuteCallback(
|
||||||
|
[&, stage](const RenderGraphExecutionContext&) {
|
||||||
|
if (!stageExecutionSucceeded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stageExecutionSucceeded = ExecuteFrameStage(
|
||||||
|
stage,
|
||||||
|
plan,
|
||||||
|
shadowState,
|
||||||
|
sceneData,
|
||||||
|
executionState);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CompiledRenderGraph compiledGraph = {};
|
||||||
|
Containers::String errorMessage;
|
||||||
|
if (!RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) {
|
||||||
|
Debug::Logger::Get().Error(
|
||||||
|
Debug::LogCategory::Rendering,
|
||||||
|
Containers::String("CameraRenderer::Render failed: RenderGraph compile failed: ") +
|
||||||
|
errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RenderGraphExecutor::Execute(compiledGraph, plan.request.context, &errorMessage)) {
|
||||||
|
Debug::Logger::Get().Error(
|
||||||
|
Debug::LogCategory::Rendering,
|
||||||
|
Containers::String("CameraRenderer::Render failed: RenderGraph execute failed: ") +
|
||||||
|
errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stageExecutionSucceeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderEnvironmentData BuildEnvironmentData(const CameraFramePlan& plan) {
|
||||||
RenderEnvironmentData environment = {};
|
RenderEnvironmentData environment = {};
|
||||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface();
|
||||||
if (request.camera == nullptr ||
|
if (plan.request.camera == nullptr ||
|
||||||
mainSceneSurface.GetDepthAttachment() == nullptr ||
|
mainSceneSurface.GetDepthAttachment() == nullptr ||
|
||||||
!HasRenderClearFlag(request.clearFlags, RenderClearFlags::Color) ||
|
!HasRenderClearFlag(plan.request.clearFlags, RenderClearFlags::Color) ||
|
||||||
!request.camera->IsSkyboxEnabled() ||
|
!plan.request.camera->IsSkyboxEnabled() ||
|
||||||
request.camera->GetProjectionType() != Components::CameraProjectionType::Perspective) {
|
plan.request.camera->GetProjectionType() != Components::CameraProjectionType::Perspective) {
|
||||||
return environment;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const Resources::Material* skyboxMaterial = request.camera->GetSkyboxMaterial()) {
|
if (const Resources::Material* skyboxMaterial = plan.request.camera->GetSkyboxMaterial()) {
|
||||||
environment.mode = RenderEnvironmentMode::MaterialSkybox;
|
environment.mode = RenderEnvironmentMode::MaterialSkybox;
|
||||||
environment.materialSkybox.material = skyboxMaterial;
|
environment.materialSkybox.material = skyboxMaterial;
|
||||||
return environment;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
environment.mode = RenderEnvironmentMode::ProceduralSkybox;
|
environment.mode = RenderEnvironmentMode::ProceduralSkybox;
|
||||||
environment.skybox.topColor = request.camera->GetSkyboxTopColor();
|
environment.skybox.topColor = plan.request.camera->GetSkyboxTopColor();
|
||||||
environment.skybox.horizonColor = request.camera->GetSkyboxHorizonColor();
|
environment.skybox.horizonColor = plan.request.camera->GetSkyboxHorizonColor();
|
||||||
environment.skybox.bottomColor = request.camera->GetSkyboxBottomColor();
|
environment.skybox.bottomColor = plan.request.camera->GetSkyboxBottomColor();
|
||||||
return environment;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,6 +874,7 @@ CameraRenderer::CameraRenderer(
|
|||||||
, m_objectIdPass(std::move(objectIdPass))
|
, m_objectIdPass(std::move(objectIdPass))
|
||||||
, m_depthOnlyPass(std::move(depthOnlyPass))
|
, m_depthOnlyPass(std::move(depthOnlyPass))
|
||||||
, m_shadowCasterPass(std::move(shadowCasterPass))
|
, m_shadowCasterPass(std::move(shadowCasterPass))
|
||||||
|
, m_directionalShadowRuntime(std::make_unique<DirectionalShadowRuntime>())
|
||||||
, m_postProcessSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>())
|
, m_postProcessSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>())
|
||||||
, m_finalOutputSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>()) {
|
, m_finalOutputSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>()) {
|
||||||
if (m_objectIdPass == nullptr) {
|
if (m_objectIdPass == nullptr) {
|
||||||
@@ -465,6 +894,7 @@ CameraRenderer::CameraRenderer(std::shared_ptr<const RenderPipelineAsset> pipeli
|
|||||||
, m_objectIdPass(std::make_unique<Passes::BuiltinObjectIdPass>())
|
, m_objectIdPass(std::make_unique<Passes::BuiltinObjectIdPass>())
|
||||||
, m_depthOnlyPass(CreateDefaultDepthOnlyPass())
|
, m_depthOnlyPass(CreateDefaultDepthOnlyPass())
|
||||||
, m_shadowCasterPass(CreateDefaultShadowCasterPass())
|
, m_shadowCasterPass(CreateDefaultShadowCasterPass())
|
||||||
|
, m_directionalShadowRuntime(std::make_unique<DirectionalShadowRuntime>())
|
||||||
, m_postProcessSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>())
|
, m_postProcessSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>())
|
||||||
, m_finalOutputSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>()) {
|
, m_finalOutputSurfaceCache(std::make_unique<FullscreenPassSurfaceCache>()) {
|
||||||
SetPipelineAsset(m_pipelineAsset);
|
SetPipelineAsset(m_pipelineAsset);
|
||||||
@@ -540,72 +970,105 @@ void CameraRenderer::ResetPipeline(std::unique_ptr<RenderPipeline> pipeline) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CameraRenderer::ResolveShadowCasterRequest(
|
bool CameraRenderer::BuildSceneDataForPlan(
|
||||||
const CameraRenderRequest& request,
|
const CameraFramePlan& plan,
|
||||||
ShadowCasterRenderRequest& outResolvedShadowCaster,
|
const DirectionalShadowExecutionState& shadowState,
|
||||||
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,
|
|
||||||
RenderSceneData& outSceneData) {
|
RenderSceneData& outSceneData) {
|
||||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface();
|
||||||
outSceneData = m_sceneExtractor.ExtractForCamera(
|
outSceneData = m_sceneExtractor.ExtractForCamera(
|
||||||
*request.scene,
|
*plan.request.scene,
|
||||||
*request.camera,
|
*plan.request.camera,
|
||||||
mainSceneSurface.GetRenderAreaWidth(),
|
mainSceneSurface.GetRenderAreaWidth(),
|
||||||
mainSceneSurface.GetRenderAreaHeight());
|
mainSceneSurface.GetRenderAreaHeight());
|
||||||
if (!outSceneData.HasCamera()) {
|
if (!outSceneData.HasCamera()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.directionalShadow.IsValid()) {
|
outSceneData.lighting.mainDirectionalShadow = shadowState.shadowData;
|
||||||
outSceneData.lighting.mainDirectionalShadow =
|
|
||||||
BuildDirectionalShadowData(request.directionalShadow, shadowMapView);
|
|
||||||
}
|
|
||||||
outSceneData.globalShaderKeywords = BuildSceneGlobalShaderKeywords(outSceneData);
|
outSceneData.globalShaderKeywords = BuildSceneGlobalShaderKeywords(outSceneData);
|
||||||
|
|
||||||
outSceneData.cameraData.clearFlags = request.clearFlags;
|
outSceneData.cameraData.clearFlags = plan.request.clearFlags;
|
||||||
outSceneData.environment = BuildEnvironmentData(request);
|
outSceneData.environment = BuildEnvironmentData(plan);
|
||||||
if (request.hasClearColorOverride) {
|
if (plan.request.hasClearColorOverride) {
|
||||||
outSceneData.cameraData.clearColor = request.clearColorOverride;
|
outSceneData.cameraData.clearColor = plan.request.clearColorOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool ExecuteFullscreenPassSequencePass(
|
||||||
|
RenderPassSequence* sequence,
|
||||||
|
size_t passIndex,
|
||||||
|
const RenderPassContext& passContext,
|
||||||
|
const RenderGraphExecutionContext& graphContext,
|
||||||
|
RenderGraphTextureHandle sourceColorHandle,
|
||||||
|
RenderGraphTextureHandle outputColorHandle) {
|
||||||
|
if (sequence == nullptr || passIndex >= sequence->GetPassCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence->GetPassCount() <= 1u) {
|
||||||
|
return sequence->ExecutePass(passIndex, passContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderSurface* currentSourceSurface = passContext.sourceSurface;
|
||||||
|
RHI::RHIResourceView* currentSourceColorView = passContext.sourceColorView;
|
||||||
|
RHI::ResourceStates currentSourceColorState = passContext.sourceColorState;
|
||||||
|
RenderSurface transientSourceSurface = {};
|
||||||
|
if (sourceColorHandle.IsValid() &&
|
||||||
|
graphContext.IsTransientTexture(sourceColorHandle)) {
|
||||||
|
if (!TryBuildRenderGraphTransientSurface(
|
||||||
|
passContext.surface,
|
||||||
|
graphContext,
|
||||||
|
sourceColorHandle,
|
||||||
|
transientSourceSurface)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSourceSurface = &transientSourceSurface;
|
||||||
|
currentSourceColorView =
|
||||||
|
graphContext.ResolveTextureView(
|
||||||
|
sourceColorHandle,
|
||||||
|
RenderGraphTextureViewType::ShaderResource);
|
||||||
|
if (currentSourceColorView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentSourceColorState = RHI::ResourceStates::PixelShaderResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderSurface* outputSurface = &passContext.surface;
|
||||||
|
RenderSurface transientOutputSurface = {};
|
||||||
|
if (outputColorHandle.IsValid() &&
|
||||||
|
graphContext.IsTransientTexture(outputColorHandle)) {
|
||||||
|
if (!TryBuildRenderGraphTransientSurface(
|
||||||
|
passContext.surface,
|
||||||
|
graphContext,
|
||||||
|
outputColorHandle,
|
||||||
|
transientOutputSurface)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSurface = &transientOutputSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderPassContext chainedContext = {
|
||||||
|
passContext.renderContext,
|
||||||
|
*outputSurface,
|
||||||
|
passContext.sceneData,
|
||||||
|
currentSourceSurface,
|
||||||
|
currentSourceColorView,
|
||||||
|
currentSourceColorState
|
||||||
|
};
|
||||||
|
return sequence->ExecutePass(passIndex, chainedContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool CameraRenderer::ExecuteRenderPlan(
|
bool CameraRenderer::ExecuteRenderPlan(
|
||||||
const CameraRenderRequest& request,
|
const CameraFramePlan& plan,
|
||||||
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
const DirectionalShadowExecutionState& shadowState,
|
||||||
const RenderSceneData& sceneData) {
|
const RenderSceneData& sceneData) {
|
||||||
CameraFrameExecutionState executionState = {};
|
CameraFrameExecutionState executionState = {};
|
||||||
executionState.pipeline = m_pipeline.get();
|
executionState.pipeline = m_pipeline.get();
|
||||||
@@ -614,36 +1077,19 @@ bool CameraRenderer::ExecuteRenderPlan(
|
|||||||
executionState.shadowCasterPass = m_shadowCasterPass.get();
|
executionState.shadowCasterPass = m_shadowCasterPass.get();
|
||||||
executionState.postProcessSurfaceCache = m_postProcessSurfaceCache.get();
|
executionState.postProcessSurfaceCache = m_postProcessSurfaceCache.get();
|
||||||
executionState.finalOutputSurfaceCache = m_finalOutputSurfaceCache.get();
|
executionState.finalOutputSurfaceCache = m_finalOutputSurfaceCache.get();
|
||||||
|
return ExecuteRenderGraphPlan(plan, shadowState, sceneData, executionState);
|
||||||
for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) {
|
|
||||||
if (!request.HasFrameStage(stageInfo.stage) &&
|
|
||||||
stageInfo.stage != CameraFrameStage::MainScene) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ExecuteFrameStage(
|
|
||||||
stageInfo.stage,
|
|
||||||
request,
|
|
||||||
resolvedShadowCaster,
|
|
||||||
sceneData,
|
|
||||||
executionState)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CameraRenderer::Render(
|
bool CameraRenderer::Render(
|
||||||
const CameraRenderRequest& request) {
|
const CameraFramePlan& plan) {
|
||||||
if (!request.IsValid() || m_pipeline == nullptr) {
|
if (!plan.IsValid() || m_pipeline == nullptr) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: request invalid or pipeline missing");
|
"CameraRenderer::Render failed: plan invalid or pipeline missing");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface();
|
||||||
if (mainSceneSurface.GetRenderAreaWidth() == 0 ||
|
if (mainSceneSurface.GetRenderAreaWidth() == 0 ||
|
||||||
mainSceneSurface.GetRenderAreaHeight() == 0) {
|
mainSceneSurface.GetRenderAreaHeight() == 0) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
@@ -651,53 +1097,53 @@ bool CameraRenderer::Render(
|
|||||||
"CameraRenderer::Render failed: main scene surface render area is empty");
|
"CameraRenderer::Render failed: main scene surface render area is empty");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (request.depthOnly.IsRequested() &&
|
if (plan.request.depthOnly.IsRequested() &&
|
||||||
!request.depthOnly.IsValid()) {
|
!plan.request.depthOnly.IsValid()) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: depth-only request invalid");
|
"CameraRenderer::Render failed: depth-only request invalid");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (request.postProcess.IsRequested() &&
|
if (plan.postProcess.IsRequested() &&
|
||||||
!request.postProcess.IsValid()) {
|
!plan.postProcess.IsValid()) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: post-process request invalid");
|
"CameraRenderer::Render failed: post-process request invalid");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (request.finalOutput.IsRequested() &&
|
if (plan.finalOutput.IsRequested() &&
|
||||||
!request.finalOutput.IsValid()) {
|
!plan.finalOutput.IsValid()) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: final-output request invalid");
|
"CameraRenderer::Render failed: final-output request invalid");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (request.objectId.IsRequested() &&
|
if (plan.request.objectId.IsRequested() &&
|
||||||
!request.objectId.IsValid()) {
|
!plan.request.objectId.IsValid()) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: object-id request invalid");
|
"CameraRenderer::Render failed: object-id request invalid");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowCasterRenderRequest resolvedShadowCaster = {};
|
DirectionalShadowExecutionState shadowState = {};
|
||||||
RHI::RHIResourceView* shadowMapView = nullptr;
|
if (m_directionalShadowRuntime == nullptr ||
|
||||||
if (!ResolveShadowCasterRequest(request, resolvedShadowCaster, shadowMapView)) {
|
!m_directionalShadowRuntime->ResolveExecutionState(plan, shadowState)) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: ResolveShadowCasterRequest returned false");
|
"CameraRenderer::Render failed: DirectionalShadowRuntime::ResolveExecutionState returned false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderSceneData sceneData = {};
|
RenderSceneData sceneData = {};
|
||||||
if (!BuildSceneDataForRequest(request, shadowMapView, sceneData)) {
|
if (!BuildSceneDataForPlan(plan, shadowState, sceneData)) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: BuildSceneDataForRequest returned false");
|
"CameraRenderer::Render failed: BuildSceneDataForPlan returned false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ExecuteRenderPlan(request, resolvedShadowCaster, sceneData)) {
|
if (!ExecuteRenderPlan(plan, shadowState, sceneData)) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"CameraRenderer::Render failed: ExecuteRenderPlan returned false");
|
"CameraRenderer::Render failed: ExecuteRenderPlan returned false");
|
||||||
|
|||||||
@@ -1,82 +1,37 @@
|
|||||||
#include "Rendering/Execution/SceneRenderer.h"
|
#include "Rendering/Execution/SceneRenderer.h"
|
||||||
|
|
||||||
#include "Components/CameraComponent.h"
|
#include "Components/CameraComponent.h"
|
||||||
#include "Debug/Logger.h"
|
|
||||||
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||||
#include "Rendering/Planning/CameraPostProcessPassFactory.h"
|
#include "Rendering/Planning/CameraFramePlanBuilder.h"
|
||||||
#include "Rendering/Planning/FinalColorPassFactory.h"
|
|
||||||
#include "Rendering/Planning/SceneRenderRequestUtils.h"
|
#include "Rendering/Planning/SceneRenderRequestUtils.h"
|
||||||
#include "Rendering/RenderPipelineAsset.h"
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
RenderSurface ConfigureFullscreenStageSurface(
|
bool CompareCameraFramePlans(
|
||||||
const FullscreenPassSurfaceCache::SurfaceEntry& entry,
|
const CameraFramePlan& lhs,
|
||||||
const RenderSurface& templateSurface,
|
const CameraFramePlan& rhs) {
|
||||||
bool copyDepthAttachment) {
|
return SceneRenderRequestUtils::CompareCameraRenderRequests(
|
||||||
RenderSurface surface = entry.surface;
|
lhs.request,
|
||||||
if (copyDepthAttachment) {
|
rhs.request);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SceneRenderer::SceneRenderer() = default;
|
SceneRenderer::SceneRenderer()
|
||||||
|
: m_framePlanBuilder(std::make_unique<CameraFramePlanBuilder>()) {
|
||||||
|
}
|
||||||
|
|
||||||
SceneRenderer::SceneRenderer(std::unique_ptr<RenderPipeline> pipeline)
|
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)
|
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;
|
SceneRenderer::~SceneRenderer() = default;
|
||||||
@@ -89,49 +44,52 @@ void SceneRenderer::SetPipelineAsset(std::shared_ptr<const RenderPipelineAsset>
|
|||||||
m_cameraRenderer.SetPipelineAsset(std::move(pipelineAsset));
|
m_cameraRenderer.SetPipelineAsset(std::move(pipelineAsset));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CameraRenderRequest> SceneRenderer::BuildRenderRequests(
|
std::vector<CameraFramePlan> SceneRenderer::BuildFramePlans(
|
||||||
const Components::Scene& scene,
|
const Components::Scene& scene,
|
||||||
Components::CameraComponent* overrideCamera,
|
Components::CameraComponent* overrideCamera,
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSurface& surface) {
|
const RenderSurface& surface) {
|
||||||
std::vector<CameraRenderRequest> requests =
|
const std::vector<CameraRenderRequest> requests =
|
||||||
m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface);
|
m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface);
|
||||||
ResolveCameraFinalColorPolicies(requests);
|
return m_framePlanBuilder != nullptr
|
||||||
AttachFullscreenStageRequests(context, requests);
|
? m_framePlanBuilder->BuildPlans(requests, GetPipelineAsset())
|
||||||
return requests;
|
: std::vector<CameraFramePlan>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SceneRenderer::Render(const CameraRenderRequest& request) {
|
bool SceneRenderer::Render(const CameraFramePlan& plan) {
|
||||||
return m_cameraRenderer.Render(request);
|
return m_cameraRenderer.Render(plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SceneRenderer::Render(const std::vector<CameraRenderRequest>& requests) {
|
bool SceneRenderer::Render(const std::vector<CameraFramePlan>& plans) {
|
||||||
if (requests.empty()) {
|
if (plans.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const CameraRenderRequest& request : requests) {
|
for (const CameraFramePlan& plan : plans) {
|
||||||
if (!request.IsValid()) {
|
if (!plan.IsValid()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CameraRenderRequest> sortedRequests = requests;
|
std::vector<CameraFramePlan> sortedPlans = plans;
|
||||||
SceneRenderRequestUtils::SortCameraRenderRequests(sortedRequests);
|
std::stable_sort(
|
||||||
|
sortedPlans.begin(),
|
||||||
|
sortedPlans.end(),
|
||||||
|
CompareCameraFramePlans);
|
||||||
|
|
||||||
bool rendered = false;
|
bool rendered = false;
|
||||||
for (const CameraRenderRequest& request : sortedRequests) {
|
for (const CameraFramePlan& plan : sortedPlans) {
|
||||||
if (!m_cameraRenderer.Render(request)) {
|
if (!m_cameraRenderer.Render(plan)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateTrackedFullscreenSurfaceState(
|
if (m_framePlanBuilder != nullptr) {
|
||||||
m_ownedFullscreenStageSurfaces,
|
m_framePlanBuilder->UpdateTrackedSurfaceState(&plan.GetMainSceneSurface());
|
||||||
&request.GetMainSceneSurface());
|
}
|
||||||
if (request.postProcess.IsRequested()) {
|
if (plan.postProcess.IsRequested()) {
|
||||||
UpdateTrackedFullscreenSurfaceState(
|
if (m_framePlanBuilder != nullptr) {
|
||||||
m_ownedFullscreenStageSurfaces,
|
m_framePlanBuilder->UpdateTrackedSurfaceState(&plan.postProcess.destinationSurface);
|
||||||
&request.postProcess.destinationSurface);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rendered = true;
|
rendered = true;
|
||||||
@@ -145,133 +103,7 @@ bool SceneRenderer::Render(
|
|||||||
Components::CameraComponent* overrideCamera,
|
Components::CameraComponent* overrideCamera,
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSurface& surface) {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Components/CameraComponent.h"
|
#include "Components/CameraComponent.h"
|
||||||
#include "Components/GameObject.h"
|
#include "Components/GameObject.h"
|
||||||
#include "Components/LightComponent.h"
|
#include "Components/LightComponent.h"
|
||||||
|
#include "Rendering/FrameData/RendererListUtils.h"
|
||||||
#include "Rendering/Extraction/RenderSceneUtility.h"
|
#include "Rendering/Extraction/RenderSceneUtility.h"
|
||||||
#include "Scene/Scene.h"
|
#include "Scene/Scene.h"
|
||||||
|
|
||||||
@@ -125,6 +126,21 @@ bool CompareVisibleGaussianSplats(
|
|||||||
return CompareVisibleGaussianSplatsStable(lhs, rhs);
|
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
|
} // namespace
|
||||||
|
|
||||||
RenderSceneData RenderSceneExtractor::Extract(
|
RenderSceneData RenderSceneExtractor::Extract(
|
||||||
@@ -165,6 +181,7 @@ RenderSceneData RenderSceneExtractor::Extract(
|
|||||||
sceneData.visibleVolumes.begin(),
|
sceneData.visibleVolumes.begin(),
|
||||||
sceneData.visibleVolumes.end(),
|
sceneData.visibleVolumes.end(),
|
||||||
CompareVisibleVolumes);
|
CompareVisibleVolumes);
|
||||||
|
BuildRendererLists(sceneData);
|
||||||
ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting);
|
ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting);
|
||||||
|
|
||||||
return sceneData;
|
return sceneData;
|
||||||
@@ -208,6 +225,7 @@ RenderSceneData RenderSceneExtractor::ExtractForCamera(
|
|||||||
sceneData.visibleVolumes.begin(),
|
sceneData.visibleVolumes.begin(),
|
||||||
sceneData.visibleVolumes.end(),
|
sceneData.visibleVolumes.end(),
|
||||||
CompareVisibleVolumes);
|
CompareVisibleVolumes);
|
||||||
|
BuildRendererLists(sceneData);
|
||||||
ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting);
|
ExtractLighting(scene, cameraPosition, cullingMask, sceneData.lighting);
|
||||||
|
|
||||||
return sceneData;
|
return sceneData;
|
||||||
|
|||||||
120
engine/src/Rendering/Graph/RenderGraph.cpp
Normal file
120
engine/src/Rendering/Graph/RenderGraph.cpp
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#include <XCEngine/Rendering/Graph/RenderGraph.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
void RenderGraph::Reset() {
|
||||||
|
m_textures.clear();
|
||||||
|
m_passes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphPassBuilder::RenderGraphPassBuilder(
|
||||||
|
RenderGraph* graph,
|
||||||
|
RenderGraphPassHandle passHandle)
|
||||||
|
: m_graph(graph)
|
||||||
|
, m_passHandle(passHandle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphPassBuilder::ReadTexture(RenderGraphTextureHandle texture) {
|
||||||
|
if (m_graph == nullptr || !m_passHandle.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::TextureAccess access = {};
|
||||||
|
access.texture = texture;
|
||||||
|
access.mode = RenderGraphAccessMode::Read;
|
||||||
|
m_graph->m_passes[m_passHandle.index].accesses.push_back(access);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphPassBuilder::WriteTexture(RenderGraphTextureHandle texture) {
|
||||||
|
if (m_graph == nullptr || !m_passHandle.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::TextureAccess access = {};
|
||||||
|
access.texture = texture;
|
||||||
|
access.mode = RenderGraphAccessMode::Write;
|
||||||
|
m_graph->m_passes[m_passHandle.index].accesses.push_back(access);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphPassBuilder::SetExecuteCallback(RenderGraphExecuteCallback callback) {
|
||||||
|
if (m_graph == nullptr || !m_passHandle.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_graph->m_passes[m_passHandle.index].executeCallback = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphBuilder::Reset() {
|
||||||
|
m_graph.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureHandle RenderGraphBuilder::ImportTexture(
|
||||||
|
const Containers::String& name,
|
||||||
|
const RenderGraphTextureDesc& desc,
|
||||||
|
RHI::RHIResourceView* importedView,
|
||||||
|
const RenderGraphImportedTextureOptions& importedOptions) {
|
||||||
|
RenderGraph::TextureResource resource = {};
|
||||||
|
resource.name = name;
|
||||||
|
resource.desc = desc;
|
||||||
|
resource.kind = RenderGraphTextureKind::Imported;
|
||||||
|
resource.importedView = importedView;
|
||||||
|
resource.importedOptions = importedOptions;
|
||||||
|
m_graph.m_textures.push_back(resource);
|
||||||
|
|
||||||
|
RenderGraphTextureHandle handle = {};
|
||||||
|
handle.index = static_cast<Core::uint32>(m_graph.m_textures.size() - 1u);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureHandle RenderGraphBuilder::CreateTransientTexture(
|
||||||
|
const Containers::String& name,
|
||||||
|
const RenderGraphTextureDesc& desc) {
|
||||||
|
RenderGraph::TextureResource resource = {};
|
||||||
|
resource.name = name;
|
||||||
|
resource.desc = desc;
|
||||||
|
resource.kind = RenderGraphTextureKind::Transient;
|
||||||
|
resource.importedView = nullptr;
|
||||||
|
m_graph.m_textures.push_back(resource);
|
||||||
|
|
||||||
|
RenderGraphTextureHandle handle = {};
|
||||||
|
handle.index = static_cast<Core::uint32>(m_graph.m_textures.size() - 1u);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphPassHandle RenderGraphBuilder::AddRasterPass(
|
||||||
|
const Containers::String& name,
|
||||||
|
const std::function<void(RenderGraphPassBuilder&)>& setup) {
|
||||||
|
return AddPass(name, RenderGraphPassType::Raster, setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphPassHandle RenderGraphBuilder::AddComputePass(
|
||||||
|
const Containers::String& name,
|
||||||
|
const std::function<void(RenderGraphPassBuilder&)>& setup) {
|
||||||
|
return AddPass(name, RenderGraphPassType::Compute, setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphPassHandle RenderGraphBuilder::AddPass(
|
||||||
|
const Containers::String& name,
|
||||||
|
RenderGraphPassType type,
|
||||||
|
const std::function<void(RenderGraphPassBuilder&)>& setup) {
|
||||||
|
RenderGraph::PassNode pass = {};
|
||||||
|
pass.name = name;
|
||||||
|
pass.type = type;
|
||||||
|
m_graph.m_passes.push_back(pass);
|
||||||
|
|
||||||
|
RenderGraphPassHandle handle = {};
|
||||||
|
handle.index = static_cast<Core::uint32>(m_graph.m_passes.size() - 1u);
|
||||||
|
|
||||||
|
if (setup) {
|
||||||
|
RenderGraphPassBuilder passBuilder(&m_graph, handle);
|
||||||
|
setup(passBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
303
engine/src/Rendering/Graph/RenderGraphCompiler.cpp
Normal file
303
engine/src/Rendering/Graph/RenderGraphCompiler.cpp
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
RHI::ResourceStates ResolveRequiredState(
|
||||||
|
RenderGraphPassType passType,
|
||||||
|
RenderGraphAccessMode accessMode) {
|
||||||
|
if (accessMode == RenderGraphAccessMode::Write) {
|
||||||
|
return passType == RenderGraphPassType::Compute
|
||||||
|
? RHI::ResourceStates::UnorderedAccess
|
||||||
|
: RHI::ResourceStates::RenderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passType == RenderGraphPassType::Compute
|
||||||
|
? RHI::ResourceStates::GenericRead
|
||||||
|
: RHI::ResourceStates::PixelShaderResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteError(
|
||||||
|
const Containers::String& message,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
*outErrorMessage = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void CompiledRenderGraph::Reset() {
|
||||||
|
m_passes.clear();
|
||||||
|
m_textures.clear();
|
||||||
|
m_textureLifetimes.clear();
|
||||||
|
m_textureTransitionPlans.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Containers::String& CompiledRenderGraph::GetPassName(size_t index) const {
|
||||||
|
static const Containers::String kEmptyString;
|
||||||
|
return index < m_passes.size() ? m_passes[index].name : kEmptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphPassType CompiledRenderGraph::GetPassType(size_t index) const {
|
||||||
|
return index < m_passes.size()
|
||||||
|
? m_passes[index].type
|
||||||
|
: RenderGraphPassType::Raster;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CompiledRenderGraph::TryGetTextureLifetime(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureLifetime& outLifetime) const {
|
||||||
|
if (!handle.IsValid() || handle.index >= m_textureLifetimes.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outLifetime = m_textureLifetimes[handle.index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CompiledRenderGraph::TryGetImportedTextureOptions(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphImportedTextureOptions& outOptions) const {
|
||||||
|
if (!handle.IsValid() || handle.index >= m_textures.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompiledRenderGraph::CompiledTexture& texture = m_textures[handle.index];
|
||||||
|
if (texture.kind != RenderGraphTextureKind::Imported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outOptions = texture.importedOptions;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CompiledRenderGraph::TryGetTextureTransitionPlan(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureTransitionPlan& outPlan) const {
|
||||||
|
if (!handle.IsValid() || handle.index >= m_textureTransitionPlans.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPlan = m_textureTransitionPlans[handle.index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderGraphCompiler::Compile(
|
||||||
|
const RenderGraph& graph,
|
||||||
|
CompiledRenderGraph& outCompiledGraph,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
outCompiledGraph.Reset();
|
||||||
|
|
||||||
|
for (const RenderGraph::TextureResource& texture : graph.m_textures) {
|
||||||
|
if (!texture.desc.IsValid()) {
|
||||||
|
WriteError(
|
||||||
|
Containers::String("RenderGraph texture desc is invalid: ") + texture.name,
|
||||||
|
outErrorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture.kind == RenderGraphTextureKind::Imported &&
|
||||||
|
texture.importedOptions.graphOwnsTransitions &&
|
||||||
|
texture.importedView == nullptr) {
|
||||||
|
WriteError(
|
||||||
|
Containers::String("RenderGraph imported texture requires a valid view when graph owns transitions: ") +
|
||||||
|
texture.name,
|
||||||
|
outErrorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t passCount = graph.m_passes.size();
|
||||||
|
const size_t textureCount = graph.m_textures.size();
|
||||||
|
|
||||||
|
std::vector<std::vector<Core::uint32>> outgoingEdges(passCount);
|
||||||
|
std::vector<Core::uint32> incomingEdgeCount(passCount, 0u);
|
||||||
|
std::unordered_set<Core::uint64> edgeKeys;
|
||||||
|
std::vector<Core::uint32> lastWriter(textureCount, kInvalidRenderGraphHandle);
|
||||||
|
std::vector<std::vector<Core::uint32>> lastReaders(textureCount);
|
||||||
|
|
||||||
|
auto addUniqueReader = [](
|
||||||
|
std::vector<Core::uint32>& readers,
|
||||||
|
Core::uint32 passIndex) {
|
||||||
|
if (std::find(readers.begin(), readers.end(), passIndex) == readers.end()) {
|
||||||
|
readers.push_back(passIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto addEdge = [&](
|
||||||
|
Core::uint32 from,
|
||||||
|
Core::uint32 to) {
|
||||||
|
if (from == kInvalidRenderGraphHandle || to == kInvalidRenderGraphHandle || from == to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Core::uint64 key =
|
||||||
|
(static_cast<Core::uint64>(from) << 32u) | static_cast<Core::uint64>(to);
|
||||||
|
if (!edgeKeys.insert(key).second) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoingEdges[from].push_back(to);
|
||||||
|
++incomingEdgeCount[to];
|
||||||
|
};
|
||||||
|
|
||||||
|
for (Core::uint32 passIndex = 0u; passIndex < static_cast<Core::uint32>(passCount); ++passIndex) {
|
||||||
|
const RenderGraph::PassNode& pass = graph.m_passes[passIndex];
|
||||||
|
for (const RenderGraph::TextureAccess& access : pass.accesses) {
|
||||||
|
if (!access.texture.IsValid() || access.texture.index >= textureCount) {
|
||||||
|
WriteError(
|
||||||
|
Containers::String("RenderGraph pass '") + pass.name +
|
||||||
|
"' references an invalid texture handle",
|
||||||
|
outErrorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderGraph::TextureResource& texture = graph.m_textures[access.texture.index];
|
||||||
|
std::vector<Core::uint32>& readers = lastReaders[access.texture.index];
|
||||||
|
Core::uint32& writer = lastWriter[access.texture.index];
|
||||||
|
|
||||||
|
if (access.mode == RenderGraphAccessMode::Read) {
|
||||||
|
if (texture.kind == RenderGraphTextureKind::Transient &&
|
||||||
|
writer == kInvalidRenderGraphHandle) {
|
||||||
|
WriteError(
|
||||||
|
Containers::String("RenderGraph transient texture '") + texture.name +
|
||||||
|
"' is read before any pass writes it",
|
||||||
|
outErrorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEdge(writer, passIndex);
|
||||||
|
addUniqueReader(readers, passIndex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEdge(writer, passIndex);
|
||||||
|
for (Core::uint32 readerPassIndex : readers) {
|
||||||
|
addEdge(readerPassIndex, passIndex);
|
||||||
|
}
|
||||||
|
readers.clear();
|
||||||
|
writer = passIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Core::uint32> executionOrder;
|
||||||
|
executionOrder.reserve(passCount);
|
||||||
|
std::vector<bool> emitted(passCount, false);
|
||||||
|
|
||||||
|
while (executionOrder.size() < passCount) {
|
||||||
|
bool progressed = false;
|
||||||
|
for (Core::uint32 passIndex = 0u; passIndex < static_cast<Core::uint32>(passCount); ++passIndex) {
|
||||||
|
if (emitted[passIndex] || incomingEdgeCount[passIndex] != 0u) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitted[passIndex] = true;
|
||||||
|
executionOrder.push_back(passIndex);
|
||||||
|
for (Core::uint32 dependentPassIndex : outgoingEdges[passIndex]) {
|
||||||
|
if (incomingEdgeCount[dependentPassIndex] > 0u) {
|
||||||
|
--incomingEdgeCount[dependentPassIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progressed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!progressed) {
|
||||||
|
WriteError(
|
||||||
|
"RenderGraph failed to compile because pass dependencies contain a cycle",
|
||||||
|
outErrorMessage);
|
||||||
|
outCompiledGraph.Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outCompiledGraph.m_textureLifetimes.resize(textureCount);
|
||||||
|
outCompiledGraph.m_textureTransitionPlans.resize(textureCount);
|
||||||
|
for (size_t textureIndex = 0u; textureIndex < textureCount; ++textureIndex) {
|
||||||
|
CompiledRenderGraph::CompiledTexture compiledTexture = {};
|
||||||
|
compiledTexture.name = graph.m_textures[textureIndex].name;
|
||||||
|
compiledTexture.desc = graph.m_textures[textureIndex].desc;
|
||||||
|
compiledTexture.kind = graph.m_textures[textureIndex].kind;
|
||||||
|
compiledTexture.importedView = graph.m_textures[textureIndex].importedView;
|
||||||
|
compiledTexture.importedOptions = graph.m_textures[textureIndex].importedOptions;
|
||||||
|
outCompiledGraph.m_textures.push_back(std::move(compiledTexture));
|
||||||
|
|
||||||
|
outCompiledGraph.m_textureLifetimes[textureIndex].kind = graph.m_textures[textureIndex].kind;
|
||||||
|
RenderGraphTextureTransitionPlan& transitionPlan =
|
||||||
|
outCompiledGraph.m_textureTransitionPlans[textureIndex];
|
||||||
|
transitionPlan.graphOwnsTransitions =
|
||||||
|
graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Transient ||
|
||||||
|
graph.m_textures[textureIndex].importedOptions.graphOwnsTransitions;
|
||||||
|
transitionPlan.initialState =
|
||||||
|
graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Imported
|
||||||
|
? graph.m_textures[textureIndex].importedOptions.initialState
|
||||||
|
: RHI::ResourceStates::Common;
|
||||||
|
transitionPlan.finalState =
|
||||||
|
graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Imported
|
||||||
|
? graph.m_textures[textureIndex].importedOptions.finalState
|
||||||
|
: RHI::ResourceStates::Common;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCompiledGraph.m_passes.reserve(passCount);
|
||||||
|
for (Core::uint32 compiledPassIndex = 0u;
|
||||||
|
compiledPassIndex < static_cast<Core::uint32>(executionOrder.size());
|
||||||
|
++compiledPassIndex) {
|
||||||
|
const Core::uint32 originalPassIndex = executionOrder[compiledPassIndex];
|
||||||
|
const RenderGraph::PassNode& sourcePass = graph.m_passes[originalPassIndex];
|
||||||
|
|
||||||
|
CompiledRenderGraph::CompiledPass compiledPass = {};
|
||||||
|
compiledPass.name = sourcePass.name;
|
||||||
|
compiledPass.type = sourcePass.type;
|
||||||
|
compiledPass.originalPassIndex = originalPassIndex;
|
||||||
|
compiledPass.accesses.reserve(sourcePass.accesses.size());
|
||||||
|
for (const RenderGraph::TextureAccess& access : sourcePass.accesses) {
|
||||||
|
CompiledRenderGraph::CompiledTextureAccess compiledAccess = {};
|
||||||
|
compiledAccess.texture = access.texture;
|
||||||
|
compiledAccess.mode = access.mode;
|
||||||
|
compiledAccess.requiredState =
|
||||||
|
ResolveRequiredState(sourcePass.type, access.mode);
|
||||||
|
compiledPass.accesses.push_back(compiledAccess);
|
||||||
|
}
|
||||||
|
compiledPass.executeCallback = sourcePass.executeCallback;
|
||||||
|
outCompiledGraph.m_passes.push_back(std::move(compiledPass));
|
||||||
|
|
||||||
|
for (const RenderGraph::TextureAccess& access : sourcePass.accesses) {
|
||||||
|
RenderGraphTextureLifetime& lifetime =
|
||||||
|
outCompiledGraph.m_textureLifetimes[access.texture.index];
|
||||||
|
RenderGraphTextureTransitionPlan& transitionPlan =
|
||||||
|
outCompiledGraph.m_textureTransitionPlans[access.texture.index];
|
||||||
|
if (!lifetime.used) {
|
||||||
|
lifetime.used = true;
|
||||||
|
lifetime.firstPassIndex = compiledPassIndex;
|
||||||
|
}
|
||||||
|
lifetime.lastPassIndex = compiledPassIndex;
|
||||||
|
|
||||||
|
const RHI::ResourceStates accessState =
|
||||||
|
ResolveRequiredState(sourcePass.type, access.mode);
|
||||||
|
if (!transitionPlan.hasFirstAccessState) {
|
||||||
|
transitionPlan.hasFirstAccessState = true;
|
||||||
|
transitionPlan.firstAccessState = accessState;
|
||||||
|
}
|
||||||
|
transitionPlan.hasLastAccessState = true;
|
||||||
|
transitionPlan.lastAccessState = accessState;
|
||||||
|
if (graph.m_textures[access.texture.index].kind == RenderGraphTextureKind::Transient) {
|
||||||
|
transitionPlan.finalState = accessState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
outErrorMessage->Clear();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
357
engine/src/Rendering/Graph/RenderGraphExecutor.cpp
Normal file
357
engine/src/Rendering/Graph/RenderGraphExecutor.cpp
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
#include <XCEngine/Rendering/Graph/RenderGraphExecutor.h>
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHICommandList.h>
|
||||||
|
#include <XCEngine/RHI/RHIDevice.h>
|
||||||
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool IsGraphManagedTransientState(RHI::ResourceStates state) {
|
||||||
|
return state == RHI::ResourceStates::Common ||
|
||||||
|
state == RHI::ResourceStates::RenderTarget ||
|
||||||
|
state == RHI::ResourceStates::PixelShaderResource ||
|
||||||
|
state == RHI::ResourceStates::GenericRead ||
|
||||||
|
state == RHI::ResourceStates::Present;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphTextureViewType ResolveBarrierViewType(RHI::ResourceStates state) {
|
||||||
|
return state == RHI::ResourceStates::RenderTarget
|
||||||
|
? RenderGraphTextureViewType::RenderTarget
|
||||||
|
: RenderGraphTextureViewType::ShaderResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class RenderGraphRuntimeResources {
|
||||||
|
public:
|
||||||
|
explicit RenderGraphRuntimeResources(const CompiledRenderGraph& graph)
|
||||||
|
: m_graph(graph)
|
||||||
|
, m_textureAllocations(graph.m_textures.size())
|
||||||
|
, m_textureStates(
|
||||||
|
graph.m_textures.size(),
|
||||||
|
RHI::ResourceStates::Common) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~RenderGraphRuntimeResources() {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Initialize(
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
|
||||||
|
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
|
||||||
|
const RenderGraphTextureLifetime& lifetime = m_graph.m_textureLifetimes[textureIndex];
|
||||||
|
if (texture.kind == RenderGraphTextureKind::Imported) {
|
||||||
|
m_textureStates[textureIndex] = texture.importedOptions.initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture.kind != RenderGraphTextureKind::Transient || !lifetime.used) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderContext.device == nullptr) {
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
*outErrorMessage =
|
||||||
|
Containers::String("RenderGraph cannot allocate transient texture without a valid device: ") +
|
||||||
|
texture.name;
|
||||||
|
}
|
||||||
|
Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CreateTransientTexture(renderContext, texture, m_textureAllocations[textureIndex])) {
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
*outErrorMessage =
|
||||||
|
Containers::String("RenderGraph failed to allocate transient texture: ") +
|
||||||
|
texture.name;
|
||||||
|
}
|
||||||
|
Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHIResourceView* ResolveTextureView(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureViewType viewType) const {
|
||||||
|
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[handle.index];
|
||||||
|
if (texture.kind == RenderGraphTextureKind::Imported) {
|
||||||
|
return texture.importedView;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextureAllocation& allocation = m_textureAllocations[handle.index];
|
||||||
|
switch (viewType) {
|
||||||
|
case RenderGraphTextureViewType::RenderTarget:
|
||||||
|
return allocation.renderTargetView;
|
||||||
|
case RenderGraphTextureViewType::ShaderResource:
|
||||||
|
return allocation.shaderResourceView;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryGetTextureDesc(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureDesc& outDesc) const {
|
||||||
|
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outDesc = m_graph.m_textures[handle.index].desc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTransientTexture(RenderGraphTextureHandle handle) const {
|
||||||
|
return handle.IsValid() &&
|
||||||
|
handle.index < m_graph.m_textures.size() &&
|
||||||
|
m_graph.m_textures[handle.index].kind == RenderGraphTextureKind::Transient;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransitionGraphOwnedImportsToFinalStates(
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
for (size_t textureIndex = 0u; textureIndex < m_graph.m_textures.size(); ++textureIndex) {
|
||||||
|
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[textureIndex];
|
||||||
|
if (texture.kind != RenderGraphTextureKind::Imported ||
|
||||||
|
!texture.importedOptions.graphOwnsTransitions ||
|
||||||
|
texture.importedView == nullptr ||
|
||||||
|
textureIndex >= m_graph.m_textureLifetimes.size() ||
|
||||||
|
!m_graph.m_textureLifetimes[textureIndex].used ||
|
||||||
|
!IsGraphManagedTransientState(texture.importedOptions.finalState)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TransitionTexture(
|
||||||
|
{ static_cast<Core::uint32>(textureIndex) },
|
||||||
|
texture.importedOptions.finalState,
|
||||||
|
renderContext,
|
||||||
|
outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransitionPassResources(
|
||||||
|
const CompiledRenderGraph::CompiledPass& pass,
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
for (const CompiledRenderGraph::CompiledTextureAccess& access : pass.accesses) {
|
||||||
|
if (!access.texture.IsValid() ||
|
||||||
|
access.texture.index >= m_graph.m_textures.size() ||
|
||||||
|
!ShouldGraphManageTransitions(access.texture) ||
|
||||||
|
!IsGraphManagedTransientState(access.requiredState)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TransitionTexture(
|
||||||
|
access.texture,
|
||||||
|
access.requiredState,
|
||||||
|
renderContext,
|
||||||
|
outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TextureAllocation {
|
||||||
|
RHI::RHITexture* texture = nullptr;
|
||||||
|
RHI::RHIResourceView* renderTargetView = nullptr;
|
||||||
|
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ShouldGraphManageTransitions(RenderGraphTextureHandle handle) const {
|
||||||
|
if (!handle.IsValid() || handle.index >= m_graph.m_textures.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompiledRenderGraph::CompiledTexture& texture = m_graph.m_textures[handle.index];
|
||||||
|
return texture.kind == RenderGraphTextureKind::Transient ||
|
||||||
|
(texture.kind == RenderGraphTextureKind::Imported &&
|
||||||
|
texture.importedOptions.graphOwnsTransitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransitionTexture(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RHI::ResourceStates targetState,
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
if (renderContext.commandList == nullptr) {
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
*outErrorMessage =
|
||||||
|
Containers::String("RenderGraph cannot transition texture without a valid command list: ") +
|
||||||
|
m_graph.m_textures[handle.index].name;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::ResourceStates& currentState = m_textureStates[handle.index];
|
||||||
|
if (currentState == targetState) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHIResourceView* resourceView =
|
||||||
|
ResolveTextureView(handle, ResolveBarrierViewType(targetState));
|
||||||
|
if (resourceView == nullptr) {
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
*outErrorMessage =
|
||||||
|
Containers::String("RenderGraph cannot resolve texture view for state transition: ") +
|
||||||
|
m_graph.m_textures[handle.index].name;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContext.commandList->TransitionBarrier(
|
||||||
|
resourceView,
|
||||||
|
currentState,
|
||||||
|
targetState);
|
||||||
|
currentState = targetState;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroyTextureAllocation(TextureAllocation& allocation) {
|
||||||
|
if (allocation.renderTargetView != nullptr) {
|
||||||
|
allocation.renderTargetView->Shutdown();
|
||||||
|
delete allocation.renderTargetView;
|
||||||
|
allocation.renderTargetView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocation.shaderResourceView != nullptr) {
|
||||||
|
allocation.shaderResourceView->Shutdown();
|
||||||
|
delete allocation.shaderResourceView;
|
||||||
|
allocation.shaderResourceView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocation.texture != nullptr) {
|
||||||
|
allocation.texture->Shutdown();
|
||||||
|
delete allocation.texture;
|
||||||
|
allocation.texture = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateTransientTexture(
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
const CompiledRenderGraph::CompiledTexture& texture,
|
||||||
|
TextureAllocation& allocation) {
|
||||||
|
RHI::TextureDesc textureDesc = {};
|
||||||
|
textureDesc.width = texture.desc.width;
|
||||||
|
textureDesc.height = texture.desc.height;
|
||||||
|
textureDesc.depth = 1u;
|
||||||
|
textureDesc.mipLevels = 1u;
|
||||||
|
textureDesc.arraySize = 1u;
|
||||||
|
textureDesc.format = texture.desc.format;
|
||||||
|
textureDesc.textureType = texture.desc.textureType;
|
||||||
|
textureDesc.sampleCount = texture.desc.sampleCount;
|
||||||
|
textureDesc.sampleQuality = texture.desc.sampleQuality;
|
||||||
|
textureDesc.flags = 0u;
|
||||||
|
|
||||||
|
allocation.texture = renderContext.device->CreateTexture(textureDesc);
|
||||||
|
if (allocation.texture == nullptr) {
|
||||||
|
DestroyTextureAllocation(allocation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::ResourceViewDesc viewDesc = {};
|
||||||
|
viewDesc.format = texture.desc.format;
|
||||||
|
viewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||||
|
viewDesc.mipLevel = 0u;
|
||||||
|
|
||||||
|
allocation.renderTargetView =
|
||||||
|
renderContext.device->CreateRenderTargetView(allocation.texture, viewDesc);
|
||||||
|
if (allocation.renderTargetView == nullptr) {
|
||||||
|
DestroyTextureAllocation(allocation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocation.shaderResourceView =
|
||||||
|
renderContext.device->CreateShaderResourceView(allocation.texture, viewDesc);
|
||||||
|
if (allocation.shaderResourceView == nullptr) {
|
||||||
|
DestroyTextureAllocation(allocation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
for (TextureAllocation& allocation : m_textureAllocations) {
|
||||||
|
DestroyTextureAllocation(allocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompiledRenderGraph& m_graph;
|
||||||
|
std::vector<TextureAllocation> m_textureAllocations;
|
||||||
|
std::vector<RHI::ResourceStates> m_textureStates;
|
||||||
|
};
|
||||||
|
|
||||||
|
RHI::RHIResourceView* RenderGraphExecutionContext::ResolveTextureView(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureViewType viewType) const {
|
||||||
|
return runtimeResources != nullptr
|
||||||
|
? runtimeResources->ResolveTextureView(handle, viewType)
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderGraphExecutionContext::TryGetTextureDesc(
|
||||||
|
RenderGraphTextureHandle handle,
|
||||||
|
RenderGraphTextureDesc& outDesc) const {
|
||||||
|
return runtimeResources != nullptr &&
|
||||||
|
runtimeResources->TryGetTextureDesc(handle, outDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderGraphExecutionContext::IsTransientTexture(RenderGraphTextureHandle handle) const {
|
||||||
|
return runtimeResources != nullptr &&
|
||||||
|
runtimeResources->IsTransientTexture(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderGraphExecutor::Execute(
|
||||||
|
const CompiledRenderGraph& graph,
|
||||||
|
const RenderContext& renderContext,
|
||||||
|
Containers::String* outErrorMessage) {
|
||||||
|
if (outErrorMessage != nullptr) {
|
||||||
|
outErrorMessage->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphRuntimeResources runtimeResources(graph);
|
||||||
|
if (!runtimeResources.Initialize(renderContext, outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraphExecutionContext executionContext = {
|
||||||
|
renderContext,
|
||||||
|
&runtimeResources
|
||||||
|
};
|
||||||
|
for (const CompiledRenderGraph::CompiledPass& pass : graph.m_passes) {
|
||||||
|
if (!runtimeResources.TransitionPassResources(pass, renderContext, outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pass.executeCallback) {
|
||||||
|
pass.executeCallback(executionContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!runtimeResources.TransitionGraphOwnedImportsToFinalStates(
|
||||||
|
renderContext,
|
||||||
|
outErrorMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -276,42 +276,97 @@ inline bool TryBuildRuntimeShaderBindings(
|
|||||||
return false;
|
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 nextConstantBufferRegister = 0;
|
||||||
Core::uint32 nextTextureRegister = 0;
|
Core::uint32 nextTextureRegister = 0;
|
||||||
Core::uint32 nextSamplerRegister = 0;
|
Core::uint32 nextSamplerRegister = 0;
|
||||||
Core::uint32 nextUnorderedAccessRegister = 0;
|
Core::uint32 nextUnorderedAccessRegister = 0;
|
||||||
Core::uint32 nextStorageBufferRegister = 0;
|
Core::uint32 nextStorageBufferRegister = 0;
|
||||||
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
|
|
||||||
binding.set = 0;
|
const auto constantBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||||
switch (binding.type) {
|
return binding.type == Resources::ShaderResourceType::ConstantBuffer;
|
||||||
case Resources::ShaderResourceType::ConstantBuffer:
|
});
|
||||||
binding.binding = nextConstantBufferRegister++;
|
for (size_t index : constantBufferIndices) {
|
||||||
break;
|
outBindings[index].set = 0;
|
||||||
case Resources::ShaderResourceType::Texture2D:
|
outBindings[index].binding = nextConstantBufferRegister++;
|
||||||
case Resources::ShaderResourceType::TextureCube:
|
}
|
||||||
binding.binding = nextTextureRegister++;
|
|
||||||
break;
|
const auto textureIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||||
case Resources::ShaderResourceType::StructuredBuffer:
|
return binding.type == Resources::ShaderResourceType::Texture2D ||
|
||||||
case Resources::ShaderResourceType::RawBuffer:
|
binding.type == Resources::ShaderResourceType::TextureCube;
|
||||||
binding.binding =
|
});
|
||||||
|
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
|
backend == Resources::ShaderBackend::OpenGL
|
||||||
? nextStorageBufferRegister++
|
? nextStorageBufferRegister++
|
||||||
: nextTextureRegister++;
|
: nextTextureRegister++;
|
||||||
break;
|
}
|
||||||
case Resources::ShaderResourceType::Sampler:
|
|
||||||
binding.binding = nextSamplerRegister++;
|
const auto samplerIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
|
||||||
break;
|
return binding.type == Resources::ShaderResourceType::Sampler;
|
||||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
});
|
||||||
case Resources::ShaderResourceType::RWRawBuffer:
|
for (size_t index : samplerIndices) {
|
||||||
binding.binding =
|
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
|
backend == Resources::ShaderBackend::OpenGL
|
||||||
? nextStorageBufferRegister++
|
? nextStorageBufferRegister++
|
||||||
: nextUnorderedAccessRegister++;
|
: nextUnorderedAccessRegister++;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
binding.binding = nextUnorderedAccessRegister++;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "RHI/RHICommandList.h"
|
#include "RHI/RHICommandList.h"
|
||||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||||
|
#include "Rendering/FrameData/RendererListUtils.h"
|
||||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||||
#include "Rendering/RenderSurface.h"
|
#include "Rendering/RenderSurface.h"
|
||||||
#include "Resources/Mesh/Mesh.h"
|
#include "Resources/Mesh/Mesh.h"
|
||||||
@@ -139,13 +140,16 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
|||||||
|
|
||||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||||
|
|
||||||
for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) {
|
VisitRendererListVisibleItems(
|
||||||
|
context.sceneData,
|
||||||
|
GetRendererListType(),
|
||||||
|
[&](const VisibleRenderItem& visibleItem) {
|
||||||
if (!ShouldRenderVisibleItem(visibleItem)) {
|
if (!ShouldRenderVisibleItem(visibleItem)) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawVisibleItem(context.renderContext, context.surface, context.sceneData, visibleItem);
|
DrawVisibleItem(context.renderContext, context.surface, context.sceneData, visibleItem);
|
||||||
}
|
});
|
||||||
|
|
||||||
commandList->EndRenderPass();
|
commandList->EndRenderPass();
|
||||||
|
|
||||||
@@ -165,6 +169,10 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RendererListType BuiltinDepthStylePassBase::GetRendererListType() const {
|
||||||
|
return RendererListType::AllVisible;
|
||||||
|
}
|
||||||
|
|
||||||
bool BuiltinDepthStylePassBase::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const {
|
bool BuiltinDepthStylePassBase::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const {
|
||||||
(void)visibleItem;
|
(void)visibleItem;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|||||||
const Resources::ShaderPass& shaderPass,
|
const Resources::ShaderPass& shaderPass,
|
||||||
const Containers::String& passName,
|
const Containers::String& passName,
|
||||||
const Resources::ShaderKeywordSet& keywordSet,
|
const Resources::ShaderKeywordSet& keywordSet,
|
||||||
const Resources::Material* material,
|
const Resources::MaterialRenderState& renderState,
|
||||||
const RenderSurface& surface,
|
const RenderSurface& surface,
|
||||||
const RHI::InputLayoutDesc& inputLayout) {
|
const RHI::InputLayoutDesc& inputLayout) {
|
||||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||||
@@ -116,7 +116,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|||||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||||
::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
||||||
pipelineDesc.inputLayout = inputLayout;
|
pipelineDesc.inputLayout = inputLayout;
|
||||||
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
ApplyRenderState(renderState, pipelineDesc);
|
||||||
|
|
||||||
if (!shaderPass.hasFixedFunctionState) {
|
if (!shaderPass.hasFixedFunctionState) {
|
||||||
pipelineDesc.blendState.blendEnable = false;
|
pipelineDesc.blendState.blendEnable = false;
|
||||||
@@ -345,6 +345,20 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Resources::MaterialRenderState BuiltinDepthStylePassBase::ResolveEffectiveDepthRenderState(
|
||||||
|
const Resources::ShaderPass* shaderPass,
|
||||||
|
const Resources::Material* material,
|
||||||
|
const RenderSceneData& sceneData) const {
|
||||||
|
Resources::MaterialRenderState renderState = ResolveEffectiveRenderState(shaderPass, material);
|
||||||
|
if (m_passType == BuiltinMaterialPass::ShadowCaster) {
|
||||||
|
renderState.depthBiasFactor =
|
||||||
|
sceneData.lighting.mainDirectionalShadow.casterBias.settings.depthBiasFactor;
|
||||||
|
renderState.depthBiasUnits =
|
||||||
|
sceneData.lighting.mainDirectionalShadow.casterBias.settings.depthBiasUnits;
|
||||||
|
}
|
||||||
|
return renderState;
|
||||||
|
}
|
||||||
|
|
||||||
bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan(
|
bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan(
|
||||||
const Resources::ShaderPass& shaderPass,
|
const Resources::ShaderPass& shaderPass,
|
||||||
BuiltinPassResourceBindingPlan& outPlan,
|
BuiltinPassResourceBindingPlan& outPlan,
|
||||||
@@ -466,8 +480,9 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
PipelineStateKey pipelineKey = {};
|
PipelineStateKey pipelineKey = {};
|
||||||
pipelineKey.renderState =
|
const Resources::MaterialRenderState effectiveRenderState =
|
||||||
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
ResolveEffectiveDepthRenderState(resolvedShaderPass.pass, material, sceneData);
|
||||||
|
pipelineKey.renderState = BuildStaticPipelineRenderStateKey(effectiveRenderState);
|
||||||
pipelineKey.shader = resolvedShaderPass.shader;
|
pipelineKey.shader = resolvedShaderPass.shader;
|
||||||
pipelineKey.passName = resolvedShaderPass.passName;
|
pipelineKey.passName = resolvedShaderPass.passName;
|
||||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
||||||
@@ -493,7 +508,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
|
|||||||
*resolvedShaderPass.pass,
|
*resolvedShaderPass.pass,
|
||||||
resolvedShaderPass.passName,
|
resolvedShaderPass.passName,
|
||||||
keywordSet,
|
keywordSet,
|
||||||
material,
|
effectiveRenderState,
|
||||||
surface,
|
surface,
|
||||||
BuildCommonInputLayout());
|
BuildCommonInputLayout());
|
||||||
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
||||||
@@ -743,7 +758,7 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const Resources::MaterialRenderState effectiveRenderState =
|
const Resources::MaterialRenderState effectiveRenderState =
|
||||||
ResolveEffectiveRenderState(resolvedShaderPass.pass, material);
|
ResolveEffectiveDepthRenderState(resolvedShaderPass.pass, material, sceneData);
|
||||||
|
|
||||||
PassLayoutKey passLayoutKey = {};
|
PassLayoutKey passLayoutKey = {};
|
||||||
passLayoutKey.shader = resolvedShaderPass.shader;
|
passLayoutKey.shader = resolvedShaderPass.shader;
|
||||||
|
|||||||
@@ -222,6 +222,16 @@ bool BuiltinGaussianSplatPass::Initialize(const RenderContext& context) {
|
|||||||
return EnsureInitialized(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(
|
bool BuiltinGaussianSplatPass::PrepareGaussianSplatResources(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSceneData& sceneData) {
|
const RenderSceneData& sceneData) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "Core/Asset/ResourceManager.h"
|
#include "Core/Asset/ResourceManager.h"
|
||||||
#include "RHI/RHICommandList.h"
|
#include "RHI/RHICommandList.h"
|
||||||
|
#include "Rendering/FrameData/RendererListUtils.h"
|
||||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||||
#include "Rendering/RenderSurface.h"
|
#include "Rendering/RenderSurface.h"
|
||||||
@@ -119,9 +120,12 @@ bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
|||||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||||
commandList->SetPipelineState(m_pipelineState);
|
commandList->SetPipelineState(m_pipelineState);
|
||||||
|
|
||||||
for (const VisibleRenderItem& visibleItem : context.sceneData.visibleItems) {
|
VisitRendererListVisibleItems(
|
||||||
|
context.sceneData,
|
||||||
|
RendererListType::ObjectId,
|
||||||
|
[&](const VisibleRenderItem& visibleItem) {
|
||||||
DrawVisibleItem(context.renderContext, context.sceneData, visibleItem);
|
DrawVisibleItem(context.renderContext, context.sceneData, visibleItem);
|
||||||
}
|
});
|
||||||
|
|
||||||
commandList->EndRenderPass();
|
commandList->EndRenderPass();
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ const char* BuiltinShadowCasterPass::GetName() const {
|
|||||||
return "BuiltinShadowCasterPass";
|
return "BuiltinShadowCasterPass";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RendererListType BuiltinShadowCasterPass::GetRendererListType() const {
|
||||||
|
return RendererListType::ShadowCaster;
|
||||||
|
}
|
||||||
|
|
||||||
bool BuiltinShadowCasterPass::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const {
|
bool BuiltinShadowCasterPass::ShouldRenderVisibleItem(const VisibleRenderItem& visibleItem) const {
|
||||||
return visibleItem.meshRenderer == nullptr || visibleItem.meshRenderer->GetCastShadows();
|
return visibleItem.meshRenderer == nullptr || visibleItem.meshRenderer->GetCastShadows();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,6 +210,16 @@ bool BuiltinVolumetricPass::Initialize(const RenderContext& context) {
|
|||||||
return EnsureInitialized(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(
|
bool BuiltinVolumetricPass::PrepareVolumeResources(
|
||||||
const RenderContext& context,
|
const RenderContext& context,
|
||||||
const RenderSceneData& sceneData) {
|
const RenderSceneData& sceneData) {
|
||||||
|
|||||||
@@ -143,58 +143,31 @@ RHI::InputLayoutDesc BuiltinForwardPipeline::BuildInputLayout() {
|
|||||||
|
|
||||||
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
|
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
|
||||||
return EnsureInitialized(context) &&
|
return EnsureInitialized(context) &&
|
||||||
m_gaussianSplatPass != nullptr &&
|
InitializeForwardSceneFeaturePasses(context);
|
||||||
m_gaussianSplatPass->Initialize(context) &&
|
|
||||||
m_volumetricPass != nullptr &&
|
|
||||||
m_volumetricPass->Initialize(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuiltinForwardPipeline::Shutdown() {
|
void BuiltinForwardPipeline::Shutdown() {
|
||||||
if (m_gaussianSplatPass != nullptr) {
|
ShutdownForwardSceneFeaturePasses();
|
||||||
m_gaussianSplatPass->Shutdown();
|
|
||||||
}
|
|
||||||
if (m_volumetricPass != nullptr) {
|
|
||||||
m_volumetricPass->Shutdown();
|
|
||||||
}
|
|
||||||
DestroyPipelineResources();
|
DestroyPipelineResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinForwardPipeline::Render(
|
bool BuiltinForwardPipeline::Render(
|
||||||
const RenderContext& context,
|
const FrameExecutionContext& executionContext) {
|
||||||
const RenderSurface& surface,
|
if (!Initialize(executionContext.renderContext)) {
|
||||||
const RenderSceneData& sceneData) {
|
|
||||||
if (!Initialize(context)) {
|
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"BuiltinForwardPipeline::Render failed: Initialize returned false");
|
"BuiltinForwardPipeline::Render failed: Initialize returned false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_volumetricPass != nullptr &&
|
if (!PrepareForwardSceneFeaturePasses(executionContext)) {
|
||||||
!sceneData.visibleVolumes.empty() &&
|
|
||||||
!m_volumetricPass->PrepareVolumeResources(context, sceneData)) {
|
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::Rendering,
|
Debug::LogCategory::Rendering,
|
||||||
"BuiltinForwardPipeline::Render failed: PrepareVolumeResources returned false");
|
"BuiltinForwardPipeline::Render failed: PrepareForwardSceneFeaturePasses 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");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderPassContext passContext = {
|
const RenderPassContext passContext = BuildRenderPassContext(executionContext);
|
||||||
context,
|
|
||||||
surface,
|
|
||||||
sceneData,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
RHI::ResourceStates::Common
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!BeginForwardScenePass(passContext)) {
|
if (!BeginForwardScenePass(passContext)) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
@@ -203,53 +176,33 @@ bool BuiltinForwardPipeline::Render(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool sampledDirectionalShadow = ShouldSampleMainDirectionalShadowMap(sceneData);
|
const bool sampledDirectionalShadow =
|
||||||
|
ShouldSampleMainDirectionalShadowMap(executionContext.sceneData);
|
||||||
if (sampledDirectionalShadow) {
|
if (sampledDirectionalShadow) {
|
||||||
TransitionMainDirectionalShadowForSampling(context, sceneData);
|
TransitionMainDirectionalShadowForSampling(
|
||||||
|
executionContext.renderContext,
|
||||||
|
executionContext.sceneData);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool renderResult = ExecuteForwardOpaquePass(passContext);
|
const bool renderResult = ExecuteForwardScene(executionContext);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sampledDirectionalShadow) {
|
if (sampledDirectionalShadow) {
|
||||||
RestoreMainDirectionalShadowAfterSampling(context, sceneData);
|
RestoreMainDirectionalShadowAfterSampling(
|
||||||
|
executionContext.renderContext,
|
||||||
|
executionContext.sceneData);
|
||||||
}
|
}
|
||||||
EndForwardScenePass(passContext);
|
EndForwardScenePass(passContext);
|
||||||
|
|
||||||
return renderResult;
|
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) {
|
bool BuiltinForwardPipeline::BeginForwardScenePass(const RenderPassContext& passContext) {
|
||||||
const RenderContext& context = passContext.renderContext;
|
const RenderContext& context = passContext.renderContext;
|
||||||
const RenderSurface& surface = passContext.surface;
|
const RenderSurface& surface = passContext.surface;
|
||||||
@@ -358,20 +311,18 @@ void BuiltinForwardPipeline::EndForwardScenePass(const RenderPassContext& passCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& passContext) {
|
bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(
|
||||||
const RenderContext& context = passContext.renderContext;
|
const ScenePhaseExecutionContext& executionContext) {
|
||||||
const RenderSurface& surface = passContext.surface;
|
return DrawVisibleItems(
|
||||||
const RenderSceneData& sceneData = passContext.sceneData;
|
executionContext.frameContext,
|
||||||
|
BuildDrawSettings(executionContext.scenePhase));
|
||||||
return DrawVisibleItems(context, surface, sceneData, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinForwardPipeline::ExecuteForwardTransparentPass(const RenderPassContext& passContext) {
|
bool BuiltinForwardPipeline::ExecuteForwardTransparentPass(
|
||||||
const RenderContext& context = passContext.renderContext;
|
const ScenePhaseExecutionContext& executionContext) {
|
||||||
const RenderSurface& surface = passContext.surface;
|
return DrawVisibleItems(
|
||||||
const RenderSceneData& sceneData = passContext.sceneData;
|
executionContext.frameContext,
|
||||||
|
BuildDrawSettings(executionContext.scenePhase));
|
||||||
return DrawVisibleItems(context, surface, sceneData, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) {
|
bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) {
|
||||||
@@ -392,6 +343,146 @@ bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) {
|
|||||||
return m_initialized;
|
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) {
|
bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) {
|
||||||
m_builtinForwardShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
m_builtinForwardShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
||||||
Resources::GetBuiltinForwardLitShaderPath());
|
Resources::GetBuiltinForwardLitShaderPath());
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "RHI/RHICommandList.h"
|
#include "RHI/RHICommandList.h"
|
||||||
#include "RHI/RHIDevice.h"
|
#include "RHI/RHIDevice.h"
|
||||||
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
||||||
|
#include "Rendering/FrameData/RendererListUtils.h"
|
||||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||||
#include "Rendering/Materials/RenderMaterialResolve.h"
|
#include "Rendering/Materials/RenderMaterialResolve.h"
|
||||||
@@ -687,10 +688,12 @@ BuiltinForwardPipeline::AdditionalLightConstants BuiltinForwardPipeline::BuildAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinForwardPipeline::DrawVisibleItem(
|
bool BuiltinForwardPipeline::DrawVisibleItem(
|
||||||
const RenderContext& context,
|
const FrameExecutionContext& executionContext,
|
||||||
const RenderSurface& surface,
|
|
||||||
const RenderSceneData& sceneData,
|
|
||||||
const VisibleRenderItem& visibleItem) {
|
const VisibleRenderItem& visibleItem) {
|
||||||
|
const RenderContext& context = executionContext.renderContext;
|
||||||
|
const RenderSurface& surface = executionContext.surface;
|
||||||
|
const RenderSceneData& sceneData = executionContext.sceneData;
|
||||||
|
|
||||||
(void)surface;
|
(void)surface;
|
||||||
const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh);
|
const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleItem.mesh);
|
||||||
if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) {
|
if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) {
|
||||||
@@ -714,17 +717,12 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
|
|||||||
visibleItem.localToWorld.Inverse()
|
visibleItem.localToWorld.Inverse()
|
||||||
};
|
};
|
||||||
const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting);
|
const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting);
|
||||||
const ShadowReceiverConstants shadowReceiverConstants = {
|
ShadowReceiverConstants shadowReceiverConstants = {};
|
||||||
sceneData.lighting.HasMainDirectionalShadow()
|
if (sceneData.lighting.HasMainDirectionalShadow()) {
|
||||||
? sceneData.lighting.mainDirectionalShadow.viewProjection
|
shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection;
|
||||||
: Math::Matrix4x4::Identity(),
|
shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics;
|
||||||
sceneData.lighting.HasMainDirectionalShadow()
|
shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling;
|
||||||
? sceneData.lighting.mainDirectionalShadow.shadowParams
|
}
|
||||||
: Math::Vector4::Zero(),
|
|
||||||
sceneData.lighting.HasMainDirectionalShadow()
|
|
||||||
? sceneData.lighting.mainDirectionalShadow.shadowOptions
|
|
||||||
: Math::Vector4::Zero()
|
|
||||||
};
|
|
||||||
|
|
||||||
const Resources::Material* material = ResolveMaterial(visibleItem);
|
const Resources::Material* material = ResolveMaterial(visibleItem);
|
||||||
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
|
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
|
||||||
@@ -866,36 +864,35 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinForwardPipeline::DrawVisibleItems(
|
bool BuiltinForwardPipeline::DrawVisibleItems(
|
||||||
const RenderContext& context,
|
const FrameExecutionContext& executionContext,
|
||||||
const RenderSurface& surface,
|
const DrawSettings& drawSettings) {
|
||||||
const RenderSceneData& sceneData,
|
const RenderContext& context = executionContext.renderContext;
|
||||||
bool drawTransparentItems) {
|
const RenderSurface& surface = executionContext.surface;
|
||||||
|
const RenderSceneData& sceneData = executionContext.sceneData;
|
||||||
|
|
||||||
RHI::RHICommandList* commandList = context.commandList;
|
RHI::RHICommandList* commandList = context.commandList;
|
||||||
RHI::RHIPipelineState* currentPipelineState = nullptr;
|
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);
|
const Resources::Material* material = ResolveMaterial(visibleItem);
|
||||||
BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
|
BuiltinMaterialPass pass = BuiltinMaterialPass::ForwardLit;
|
||||||
if (!TryResolveSurfacePassType(material, pass)) {
|
if (!TryResolveSurfacePassType(material, pass)) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, sceneData, material);
|
||||||
if (pipelineState == nullptr) {
|
if (pipelineState == nullptr) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
if (pipelineState != currentPipelineState) {
|
if (pipelineState != currentPipelineState) {
|
||||||
commandList->SetPipelineState(pipelineState);
|
commandList->SetPipelineState(pipelineState);
|
||||||
currentPipelineState = pipelineState;
|
currentPipelineState = pipelineState;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawVisibleItem(context, surface, sceneData, visibleItem);
|
DrawVisibleItem(executionContext, visibleItem);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
VisitRendererListVisibleItems(sceneData, drawSettings.rendererListType, drawVisibleItem);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
217
engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp
Normal file
217
engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#include "Rendering/Planning/CameraFramePlanBuilder.h"
|
||||||
|
|
||||||
|
#include "Components/CameraComponent.h"
|
||||||
|
#include "Debug/Logger.h"
|
||||||
|
#include "Rendering/Caches/FullscreenPassSurfaceCache.h"
|
||||||
|
#include "Rendering/Planning/CameraPostProcessPassFactory.h"
|
||||||
|
#include "Rendering/Planning/FinalColorPassFactory.h"
|
||||||
|
#include "Rendering/RenderPipelineAsset.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
RenderSurface ConfigureFullscreenStageSurface(
|
||||||
|
const FullscreenPassSurfaceCache::SurfaceEntry& entry,
|
||||||
|
const RenderSurface& templateSurface,
|
||||||
|
bool copyDepthAttachment) {
|
||||||
|
RenderSurface surface = entry.surface;
|
||||||
|
if (copyDepthAttachment) {
|
||||||
|
surface.SetDepthAttachment(templateSurface.GetDepthAttachment());
|
||||||
|
surface.SetDepthStateBefore(templateSurface.GetDepthStateBefore());
|
||||||
|
surface.SetDepthStateAfter(templateSurface.GetDepthStateAfter());
|
||||||
|
if (templateSurface.HasClearColorOverride()) {
|
||||||
|
surface.SetClearColorOverride(templateSurface.GetClearColorOverride());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (templateSurface.HasCustomRenderArea()) {
|
||||||
|
surface.SetRenderArea(templateSurface.GetRenderArea());
|
||||||
|
} else {
|
||||||
|
surface.ResetRenderArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
surface.SetColorStateBefore(entry.currentColorState);
|
||||||
|
surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::vector<CameraFramePlan> CameraFramePlanBuilder::BuildPlans(
|
||||||
|
const std::vector<CameraRenderRequest>& requests,
|
||||||
|
const RenderPipelineAsset* pipelineAsset) {
|
||||||
|
std::vector<CameraFramePlan> plans = CreatePlansFromRequests(requests);
|
||||||
|
ResolveCameraFinalColorPolicies(plans, pipelineAsset);
|
||||||
|
AttachFullscreenStageRequests(plans);
|
||||||
|
return plans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraFramePlanBuilder::UpdateTrackedSurfaceState(const RenderSurface* surface) {
|
||||||
|
if (surface == nullptr || surface->GetColorAttachments().empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHIResourceView* colorAttachment = surface->GetColorAttachments()[0];
|
||||||
|
if (colorAttachment == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::unique_ptr<FullscreenPassSurfaceCache>& cache : m_ownedFullscreenStageSurfaces) {
|
||||||
|
if (cache == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t entryIndex = 0; entryIndex < cache->GetSurfaceCount(); ++entryIndex) {
|
||||||
|
FullscreenPassSurfaceCache::SurfaceEntry* entry = cache->GetSurfaceEntry(entryIndex);
|
||||||
|
if (entry == nullptr || entry->renderTargetView != colorAttachment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->currentColorState = surface->GetColorStateAfter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CameraFramePlan> CameraFramePlanBuilder::CreatePlansFromRequests(
|
||||||
|
const std::vector<CameraRenderRequest>& requests) const {
|
||||||
|
std::vector<CameraFramePlan> plans = {};
|
||||||
|
plans.reserve(requests.size());
|
||||||
|
for (const CameraRenderRequest& request : requests) {
|
||||||
|
plans.push_back(CameraFramePlan::FromRequest(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
return plans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraFramePlanBuilder::PrepareOwnedFullscreenStageState(size_t planCount) {
|
||||||
|
m_ownedPostProcessSequences.clear();
|
||||||
|
m_ownedPostProcessSequences.resize(planCount);
|
||||||
|
m_ownedFinalOutputSequences.clear();
|
||||||
|
m_ownedFinalOutputSequences.resize(planCount);
|
||||||
|
|
||||||
|
if (m_ownedFullscreenStageSurfaces.size() < planCount) {
|
||||||
|
m_ownedFullscreenStageSurfaces.resize(planCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t index = 0; index < planCount; ++index) {
|
||||||
|
if (m_ownedFullscreenStageSurfaces[index] == nullptr) {
|
||||||
|
m_ownedFullscreenStageSurfaces[index] = std::make_unique<FullscreenPassSurfaceCache>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraFramePlanBuilder::ResolveCameraFinalColorPolicies(
|
||||||
|
std::vector<CameraFramePlan>& plans,
|
||||||
|
const RenderPipelineAsset* pipelineAsset) const {
|
||||||
|
const FinalColorSettings pipelineDefaults =
|
||||||
|
pipelineAsset != nullptr ? pipelineAsset->GetDefaultFinalColorSettings() : FinalColorSettings();
|
||||||
|
|
||||||
|
for (CameraFramePlan& plan : plans) {
|
||||||
|
if (plan.request.camera == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.finalColorPolicy = ResolveFinalColorPolicy(
|
||||||
|
pipelineDefaults,
|
||||||
|
&plan.request.camera->GetFinalColorOverrides());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CameraFramePlanBuilder::AttachFullscreenStageRequests(
|
||||||
|
std::vector<CameraFramePlan>& plans) {
|
||||||
|
PrepareOwnedFullscreenStageState(plans.size());
|
||||||
|
|
||||||
|
for (size_t index = 0; index < plans.size(); ++index) {
|
||||||
|
CameraFramePlan& plan = plans[index];
|
||||||
|
if (plan.request.camera == nullptr ||
|
||||||
|
plan.request.context.device == nullptr ||
|
||||||
|
!HasValidColorTarget(plan.request.surface)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPassSequence> postProcessSequence =
|
||||||
|
BuildCameraPostProcessPassSequence(plan.request.camera->GetPostProcessPasses());
|
||||||
|
std::unique_ptr<RenderPassSequence> finalOutputSequence =
|
||||||
|
BuildFinalColorPassSequence(plan.finalColorPolicy);
|
||||||
|
|
||||||
|
const bool hasPostProcess =
|
||||||
|
postProcessSequence != nullptr && postProcessSequence->GetPassCount() > 0u;
|
||||||
|
const bool hasFinalOutput =
|
||||||
|
finalOutputSequence != nullptr && finalOutputSequence->GetPassCount() > 0u;
|
||||||
|
if (!hasPostProcess && !hasFinalOutput) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.request.surface.GetSampleCount() > 1u) {
|
||||||
|
Debug::Logger::Get().Error(
|
||||||
|
Debug::LogCategory::Rendering,
|
||||||
|
"SceneRenderer fullscreen post-process/final-output chain currently requires a single-sample main scene surface");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<RHI::RHIResourceView*>& colorAttachments =
|
||||||
|
plan.request.surface.GetColorAttachments();
|
||||||
|
const RHI::Format colorFormat = colorAttachments[0]->GetFormat();
|
||||||
|
if (colorFormat == RHI::Format::Unknown) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t fullscreenSurfaceCount = hasPostProcess && hasFinalOutput ? 2u : 1u;
|
||||||
|
FullscreenPassSurfaceCache* surfaceCache = m_ownedFullscreenStageSurfaces[index].get();
|
||||||
|
if (surfaceCache == nullptr ||
|
||||||
|
!surfaceCache->EnsureSurfaces(
|
||||||
|
plan.request.context,
|
||||||
|
plan.request.surface.GetWidth(),
|
||||||
|
plan.request.surface.GetHeight(),
|
||||||
|
colorFormat,
|
||||||
|
fullscreenSurfaceCount)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FullscreenPassSurfaceCache::SurfaceEntry* sceneColorEntry =
|
||||||
|
surfaceCache->GetSurfaceEntry(0u);
|
||||||
|
if (sceneColorEntry == nullptr || sceneColorEntry->shaderResourceView == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FullscreenPassSurfaceCache::SurfaceEntry* postProcessOutputEntry =
|
||||||
|
hasPostProcess && hasFinalOutput ? surfaceCache->GetSurfaceEntry(1u) : nullptr;
|
||||||
|
if (hasPostProcess && hasFinalOutput &&
|
||||||
|
(postProcessOutputEntry == nullptr || postProcessOutputEntry->shaderResourceView == nullptr)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPostProcess) {
|
||||||
|
plan.postProcess.sourceSurface =
|
||||||
|
ConfigureFullscreenStageSurface(*sceneColorEntry, plan.request.surface, true);
|
||||||
|
plan.postProcess.sourceColorView = sceneColorEntry->shaderResourceView;
|
||||||
|
plan.postProcess.sourceColorState = plan.postProcess.sourceSurface.GetColorStateAfter();
|
||||||
|
plan.postProcess.destinationSurface =
|
||||||
|
hasFinalOutput
|
||||||
|
? ConfigureFullscreenStageSurface(*postProcessOutputEntry, plan.request.surface, false)
|
||||||
|
: plan.request.surface;
|
||||||
|
m_ownedPostProcessSequences[index] = std::move(postProcessSequence);
|
||||||
|
plan.postProcess.passes = m_ownedPostProcessSequences[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFinalOutput) {
|
||||||
|
const FullscreenPassSurfaceCache::SurfaceEntry* finalOutputSourceEntry =
|
||||||
|
hasPostProcess ? postProcessOutputEntry : sceneColorEntry;
|
||||||
|
plan.finalOutput.sourceSurface =
|
||||||
|
hasPostProcess
|
||||||
|
? plan.postProcess.destinationSurface
|
||||||
|
: ConfigureFullscreenStageSurface(*sceneColorEntry, plan.request.surface, true);
|
||||||
|
plan.finalOutput.sourceColorView = finalOutputSourceEntry->shaderResourceView;
|
||||||
|
plan.finalOutput.sourceColorState = plan.finalOutput.sourceSurface.GetColorStateAfter();
|
||||||
|
plan.finalOutput.destinationSurface = plan.request.surface;
|
||||||
|
m_ownedFinalOutputSequences[index] = std::move(finalOutputSequence);
|
||||||
|
plan.finalOutput.passes = m_ownedFinalOutputSequences[index].get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
41
engine/src/Rendering/Planning/CameraFramePlanBuilder.h
Normal file
41
engine/src/Rendering/Planning/CameraFramePlanBuilder.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/Execution/CameraFramePlan.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class FullscreenPassSurfaceCache;
|
||||||
|
class RenderPipelineAsset;
|
||||||
|
|
||||||
|
class CameraFramePlanBuilder {
|
||||||
|
public:
|
||||||
|
CameraFramePlanBuilder() = default;
|
||||||
|
CameraFramePlanBuilder(const CameraFramePlanBuilder&) = delete;
|
||||||
|
CameraFramePlanBuilder& operator=(const CameraFramePlanBuilder&) = delete;
|
||||||
|
~CameraFramePlanBuilder() = default;
|
||||||
|
|
||||||
|
std::vector<CameraFramePlan> BuildPlans(
|
||||||
|
const std::vector<CameraRenderRequest>& requests,
|
||||||
|
const RenderPipelineAsset* pipelineAsset);
|
||||||
|
void UpdateTrackedSurfaceState(const RenderSurface* surface);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<CameraFramePlan> CreatePlansFromRequests(
|
||||||
|
const std::vector<CameraRenderRequest>& requests) const;
|
||||||
|
void PrepareOwnedFullscreenStageState(size_t planCount);
|
||||||
|
void ResolveCameraFinalColorPolicies(
|
||||||
|
std::vector<CameraFramePlan>& plans,
|
||||||
|
const RenderPipelineAsset* pipelineAsset) const;
|
||||||
|
void AttachFullscreenStageRequests(std::vector<CameraFramePlan>& plans);
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedPostProcessSequences;
|
||||||
|
std::vector<std::unique_ptr<RenderPassSequence>> m_ownedFinalOutputSequences;
|
||||||
|
std::vector<std::unique_ptr<FullscreenPassSurfaceCache>> m_ownedFullscreenStageSurfaces;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -112,6 +111,27 @@ DirectionalShadowPlanningSettings SanitizeDirectionalShadowPlanningSettings(
|
|||||||
if (sanitized.minDepthPadding < 0.0f) {
|
if (sanitized.minDepthPadding < 0.0f) {
|
||||||
sanitized.minDepthPadding = defaults.minDepthPadding;
|
sanitized.minDepthPadding = defaults.minDepthPadding;
|
||||||
}
|
}
|
||||||
|
if (!std::isfinite(sanitized.sampling.receiverDepthBias) ||
|
||||||
|
sanitized.sampling.receiverDepthBias < 0.0f) {
|
||||||
|
sanitized.sampling.receiverDepthBias = defaults.sampling.receiverDepthBias;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(sanitized.sampling.normalBiasScale) ||
|
||||||
|
sanitized.sampling.normalBiasScale < 0.0f) {
|
||||||
|
sanitized.sampling.normalBiasScale = defaults.sampling.normalBiasScale;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(sanitized.sampling.shadowStrength)) {
|
||||||
|
sanitized.sampling.shadowStrength = defaults.sampling.shadowStrength;
|
||||||
|
} else {
|
||||||
|
sanitized.sampling.shadowStrength =
|
||||||
|
std::clamp(sanitized.sampling.shadowStrength, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
if (!std::isfinite(sanitized.casterBias.depthBiasFactor) ||
|
||||||
|
sanitized.casterBias.depthBiasFactor < 0.0f) {
|
||||||
|
sanitized.casterBias.depthBiasFactor = defaults.casterBias.depthBiasFactor;
|
||||||
|
}
|
||||||
|
if (sanitized.casterBias.depthBiasUnits < 0) {
|
||||||
|
sanitized.casterBias.depthBiasUnits = defaults.casterBias.depthBiasUnits;
|
||||||
|
}
|
||||||
|
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
@@ -134,12 +154,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
const Components::LightComponent& light,
|
const Components::LightComponent& light,
|
||||||
const DirectionalShadowPlanningSettings& shadowSettings,
|
const DirectionalShadowPlanningSettings& shadowSettings,
|
||||||
float viewportAspect) {
|
float viewportAspect) {
|
||||||
std::fprintf(stderr, "[shadow-debug] enter BuildDirectionalShadowRenderPlan\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
DirectionalShadowRenderPlan plan = {};
|
DirectionalShadowRenderPlan plan = {};
|
||||||
if (!light.GetCastsShadows()) {
|
if (!light.GetCastsShadows()) {
|
||||||
std::fprintf(stderr, "[shadow-debug] early out: light shadows disabled\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +189,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
shadowSettings.maxFocusDistance);
|
shadowSettings.maxFocusDistance);
|
||||||
const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f);
|
const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f);
|
||||||
const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance);
|
const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance);
|
||||||
std::fprintf(stderr, "[shadow-debug] built slice distances\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
|
|
||||||
std::array<Math::Vector3, 8> frustumCorners = {};
|
std::array<Math::Vector3, 8> frustumCorners = {};
|
||||||
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
|
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
|
||||||
@@ -216,8 +230,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
focusPoint += corner;
|
focusPoint += corner;
|
||||||
}
|
}
|
||||||
focusPoint /= static_cast<float>(frustumCorners.size());
|
focusPoint /= static_cast<float>(frustumCorners.size());
|
||||||
std::fprintf(stderr, "[shadow-debug] built frustum corners\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
|
|
||||||
Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero());
|
Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero());
|
||||||
for (size_t index = 1; index < frustumCorners.size(); ++index) {
|
for (size_t index = 1; index < frustumCorners.size(); ++index) {
|
||||||
@@ -241,8 +253,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
shadowWorldPosition,
|
shadowWorldPosition,
|
||||||
shadowRotation,
|
shadowRotation,
|
||||||
Math::Vector3::One()).Inverse();
|
Math::Vector3::One()).Inverse();
|
||||||
std::fprintf(stderr, "[shadow-debug] built light view matrix\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
|
|
||||||
float minX = std::numeric_limits<float>::max();
|
float minX = std::numeric_limits<float>::max();
|
||||||
float maxX = std::numeric_limits<float>::lowest();
|
float maxX = std::numeric_limits<float>::lowest();
|
||||||
@@ -251,14 +261,10 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
float minZ = std::numeric_limits<float>::max();
|
float minZ = std::numeric_limits<float>::max();
|
||||||
float maxZ = std::numeric_limits<float>::lowest();
|
float maxZ = std::numeric_limits<float>::lowest();
|
||||||
ExpandLightSpaceBounds(view, frustumCorners, minX, maxX, minY, maxY, minZ, maxZ);
|
ExpandLightSpaceBounds(view, frustumCorners, minX, maxX, minY, maxY, minZ, maxZ);
|
||||||
std::fprintf(stderr, "[shadow-debug] expanded frustum bounds\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
|
|
||||||
const uint32_t cullingMask = camera.GetCullingMask();
|
const uint32_t cullingMask = camera.GetCullingMask();
|
||||||
const std::vector<Components::MeshFilterComponent*> meshFilters =
|
const std::vector<Components::MeshFilterComponent*> meshFilters =
|
||||||
scene.FindObjectsOfType<Components::MeshFilterComponent>();
|
scene.FindObjectsOfType<Components::MeshFilterComponent>();
|
||||||
std::fprintf(stderr, "[shadow-debug] mesh filter count=%zu\n", meshFilters.size());
|
|
||||||
std::fflush(stderr);
|
|
||||||
for (Components::MeshFilterComponent* meshFilter : meshFilters) {
|
for (Components::MeshFilterComponent* meshFilter : meshFilters) {
|
||||||
if (meshFilter == nullptr ||
|
if (meshFilter == nullptr ||
|
||||||
!meshFilter->IsEnabled() ||
|
!meshFilter->IsEnabled() ||
|
||||||
@@ -280,7 +286,7 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
gameObject->GetComponent<Components::MeshRendererComponent>();
|
gameObject->GetComponent<Components::MeshRendererComponent>();
|
||||||
if (meshRenderer == nullptr ||
|
if (meshRenderer == nullptr ||
|
||||||
!meshRenderer->IsEnabled() ||
|
!meshRenderer->IsEnabled() ||
|
||||||
(!meshRenderer->GetCastShadows() && !meshRenderer->GetReceiveShadows())) {
|
!meshRenderer->GetCastShadows()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,8 +306,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
|
|
||||||
ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ);
|
ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ);
|
||||||
}
|
}
|
||||||
std::fprintf(stderr, "[shadow-debug] finished mesh bounds expansion\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
|
|
||||||
minX -= shadowSettings.boundsPadding;
|
minX -= shadowSettings.boundsPadding;
|
||||||
maxX += shadowSettings.boundsPadding;
|
maxX += shadowSettings.boundsPadding;
|
||||||
@@ -349,8 +353,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
maxY,
|
maxY,
|
||||||
minZ,
|
minZ,
|
||||||
maxZ);
|
maxZ);
|
||||||
std::fprintf(stderr, "[shadow-debug] built orthographic projection\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
|
|
||||||
plan.enabled = true;
|
plan.enabled = true;
|
||||||
plan.lightDirection = lightDirection;
|
plan.lightDirection = lightDirection;
|
||||||
@@ -359,6 +361,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
plan.texelWorldSize = std::max(shadowTexelSizeX, shadowTexelSizeY);
|
plan.texelWorldSize = std::max(shadowTexelSizeX, shadowTexelSizeY);
|
||||||
plan.nearClipPlane = minZ;
|
plan.nearClipPlane = minZ;
|
||||||
plan.farClipPlane = maxZ;
|
plan.farClipPlane = maxZ;
|
||||||
|
plan.sampling = shadowSettings.sampling;
|
||||||
|
plan.casterBias = shadowSettings.casterBias;
|
||||||
plan.mapWidth = shadowSettings.mapDimension;
|
plan.mapWidth = shadowSettings.mapDimension;
|
||||||
plan.mapHeight = shadowSettings.mapDimension;
|
plan.mapHeight = shadowSettings.mapDimension;
|
||||||
plan.cameraData.view = view.Transpose();
|
plan.cameraData.view = view.Transpose();
|
||||||
@@ -369,8 +373,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
plan.cameraData.clearFlags = RenderClearFlags::Depth;
|
plan.cameraData.clearFlags = RenderClearFlags::Depth;
|
||||||
plan.cameraData.viewportWidth = plan.mapWidth;
|
plan.cameraData.viewportWidth = plan.mapWidth;
|
||||||
plan.cameraData.viewportHeight = plan.mapHeight;
|
plan.cameraData.viewportHeight = plan.mapHeight;
|
||||||
std::fprintf(stderr, "[shadow-debug] leave BuildDirectionalShadowRenderPlan\n");
|
|
||||||
std::fflush(stderr);
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,17 +81,20 @@ std::vector<CameraRenderRequest> SceneRenderRequestPlanner::BuildRequests(
|
|||||||
? static_cast<float>(surface.GetRenderAreaWidth()) /
|
? static_cast<float>(surface.GetRenderAreaWidth()) /
|
||||||
static_cast<float>(surface.GetRenderAreaHeight())
|
static_cast<float>(surface.GetRenderAreaHeight())
|
||||||
: 1.0f;
|
: 1.0f;
|
||||||
|
DirectionalShadowPlanningSettings effectiveShadowSettings =
|
||||||
|
m_directionalShadowPlanningSettings;
|
||||||
|
if (mainDirectionalLight->GetOverridesDirectionalShadowSettings()) {
|
||||||
|
effectiveShadowSettings.sampling =
|
||||||
|
mainDirectionalLight->GetDirectionalShadowSamplingSettings();
|
||||||
|
effectiveShadowSettings.casterBias =
|
||||||
|
mainDirectionalLight->GetDirectionalShadowCasterBiasSettings();
|
||||||
|
}
|
||||||
request.directionalShadow = Internal::BuildDirectionalShadowRenderPlan(
|
request.directionalShadow = Internal::BuildDirectionalShadowRenderPlan(
|
||||||
scene,
|
scene,
|
||||||
*camera,
|
*camera,
|
||||||
*mainDirectionalLight,
|
*mainDirectionalLight,
|
||||||
m_directionalShadowPlanningSettings,
|
effectiveShadowSettings,
|
||||||
viewportAspect);
|
viewportAspect);
|
||||||
if (request.directionalShadow.IsValid()) {
|
|
||||||
request.shadowCaster.clearFlags = RenderClearFlags::Depth;
|
|
||||||
request.shadowCaster.hasCameraDataOverride = true;
|
|
||||||
request.shadowCaster.cameraDataOverride = request.directionalShadow.cameraData;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
engine/src/Rendering/Shadow/DirectionalShadowData.cpp
Normal file
33
engine/src/Rendering/Shadow/DirectionalShadowData.cpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#include "Rendering/Shadow/DirectionalShadowData.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
RenderDirectionalShadowData BuildRenderDirectionalShadowData(
|
||||||
|
const DirectionalShadowRenderPlan& plan,
|
||||||
|
RHI::RHIResourceView* shadowMapView) {
|
||||||
|
RenderDirectionalShadowData shadowData = {};
|
||||||
|
if (!plan.IsValid() || shadowMapView == nullptr) {
|
||||||
|
return shadowData;
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowData.enabled = true;
|
||||||
|
shadowData.viewProjection = plan.cameraData.viewProjection;
|
||||||
|
shadowData.shadowMap = shadowMapView;
|
||||||
|
const float texelWorldSize = plan.texelWorldSize > Math::EPSILON
|
||||||
|
? plan.texelWorldSize
|
||||||
|
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
|
||||||
|
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
|
||||||
|
: 0.0f);
|
||||||
|
shadowData.mapMetrics.inverseMapSize = Math::Vector2(
|
||||||
|
1.0f / static_cast<float>(plan.mapWidth),
|
||||||
|
1.0f / static_cast<float>(plan.mapHeight));
|
||||||
|
shadowData.mapMetrics.worldTexelSize = texelWorldSize;
|
||||||
|
shadowData.sampling.enabled = 1.0f;
|
||||||
|
shadowData.sampling.settings = plan.sampling;
|
||||||
|
shadowData.casterBias.settings = plan.casterBias;
|
||||||
|
return shadowData;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
44
engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp
Normal file
44
engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include "Rendering/Shadow/DirectionalShadowRuntime.h"
|
||||||
|
|
||||||
|
#include "Rendering/Execution/CameraFramePlan.h"
|
||||||
|
#include "Rendering/Shadow/DirectionalShadowData.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
bool DirectionalShadowRuntime::ResolveExecutionState(
|
||||||
|
const CameraFramePlan& plan,
|
||||||
|
DirectionalShadowExecutionState& outShadowState) {
|
||||||
|
outShadowState = {};
|
||||||
|
outShadowState.shadowCasterRequest = plan.shadowCaster;
|
||||||
|
|
||||||
|
if (outShadowState.shadowCasterRequest.IsRequested()) {
|
||||||
|
return outShadowState.shadowCasterRequest.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plan.directionalShadow.IsValid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DirectionalShadowSurfaceAllocation* shadowAllocation =
|
||||||
|
m_surfaceCache.Resolve(plan.request.context, plan.directionalShadow);
|
||||||
|
if (shadowAllocation == nullptr || !shadowAllocation->IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outShadowState.shadowCasterRequest.surface = shadowAllocation->surface;
|
||||||
|
outShadowState.shadowCasterRequest.clearFlags = RenderClearFlags::Depth;
|
||||||
|
if (!outShadowState.shadowCasterRequest.hasCameraDataOverride) {
|
||||||
|
outShadowState.shadowCasterRequest.hasCameraDataOverride = true;
|
||||||
|
outShadowState.shadowCasterRequest.cameraDataOverride = plan.directionalShadow.cameraData;
|
||||||
|
}
|
||||||
|
|
||||||
|
outShadowState.shadowData =
|
||||||
|
BuildRenderDirectionalShadowData(
|
||||||
|
plan.directionalShadow,
|
||||||
|
shadowAllocation->depthShaderView);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -124,9 +124,16 @@ target_link_libraries(XCUIEditorLib PUBLIC
|
|||||||
|
|
||||||
add_library(XCUIEditorHost STATIC
|
add_library(XCUIEditorHost STATIC
|
||||||
app/Host/AutoScreenshot.cpp
|
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/D3D12WindowRenderer.cpp
|
||||||
|
app/Host/D3D12WindowSwapChainPresenter.cpp
|
||||||
app/Host/D3D12WindowRenderLoop.cpp
|
app/Host/D3D12WindowRenderLoop.cpp
|
||||||
app/Host/NativeRenderer.cpp
|
app/Host/NativeRenderer.cpp
|
||||||
|
app/Host/WindowMessageDispatcher.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(XCUIEditorHost
|
target_include_directories(XCUIEditorHost
|
||||||
@@ -141,6 +148,7 @@ xcui_editor_apply_common_target_settings(XCUIEditorHost PUBLIC)
|
|||||||
target_link_libraries(XCUIEditorHost PUBLIC
|
target_link_libraries(XCUIEditorHost PUBLIC
|
||||||
XCEngine
|
XCEngine
|
||||||
d2d1.lib
|
d2d1.lib
|
||||||
|
d3d11.lib
|
||||||
d3d12.lib
|
d3d12.lib
|
||||||
d3dcompiler.lib
|
d3dcompiler.lib
|
||||||
dwrite.lib
|
dwrite.lib
|
||||||
@@ -164,6 +172,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP)
|
|||||||
app/Project/ProductProjectBrowserModel.cpp
|
app/Project/ProductProjectBrowserModel.cpp
|
||||||
app/Shell/ProductShellAsset.cpp
|
app/Shell/ProductShellAsset.cpp
|
||||||
app/Viewport/ProductViewportHostService.cpp
|
app/Viewport/ProductViewportHostService.cpp
|
||||||
|
app/Viewport/ProductViewportRenderTargetManager.cpp
|
||||||
app/Viewport/ProductViewportRenderTargets.cpp
|
app/Viewport/ProductViewportRenderTargets.cpp
|
||||||
app/Workspace/ProductEditorWorkspace.cpp
|
app/Workspace/ProductEditorWorkspace.cpp
|
||||||
app/Workspace/ProductEditorWorkspaceEventRouter.cpp
|
app/Workspace/ProductEditorWorkspaceEventRouter.cpp
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,11 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <Host/AutoScreenshot.h>
|
#include <Host/AutoScreenshot.h>
|
||||||
|
#include <Host/BorderlessWindowChrome.h>
|
||||||
|
#include <Host/BorderlessWindowFrame.h>
|
||||||
#include <Host/D3D12WindowRenderer.h>
|
#include <Host/D3D12WindowRenderer.h>
|
||||||
|
#include <Host/D3D12WindowRenderLoop.h>
|
||||||
|
#include <Host/HostRuntimeState.h>
|
||||||
#include <Host/InputModifierTracker.h>
|
#include <Host/InputModifierTracker.h>
|
||||||
#include <Host/NativeRenderer.h>
|
#include <Host/NativeRenderer.h>
|
||||||
|
|
||||||
@@ -23,6 +27,10 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
class WindowMessageDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
namespace XCEngine::UI::Editor {
|
namespace XCEngine::UI::Editor {
|
||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
@@ -32,13 +40,24 @@ public:
|
|||||||
int Run(HINSTANCE hInstance, int nCmdShow);
|
int Run(HINSTANCE hInstance, int nCmdShow);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class ::XCEngine::UI::Editor::Host::WindowMessageDispatcher;
|
||||||
|
|
||||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
bool Initialize(HINSTANCE hInstance, int nCmdShow);
|
bool Initialize(HINSTANCE hInstance, int nCmdShow);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void RenderFrame();
|
void RenderFrame();
|
||||||
|
void OnPaintMessage();
|
||||||
void OnResize(UINT width, UINT height);
|
void OnResize(UINT width, UINT height);
|
||||||
|
void OnEnterSizeMove();
|
||||||
|
void OnExitSizeMove();
|
||||||
void OnDpiChanged(UINT dpi, const RECT& suggestedRect);
|
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;
|
||||||
|
::XCEngine::UI::UIRect ResolveWorkspaceBounds(
|
||||||
|
float clientWidthDips,
|
||||||
|
float clientHeightDips) const;
|
||||||
bool IsPointerInsideClientArea() const;
|
bool IsPointerInsideClientArea() const;
|
||||||
bool ApplyCurrentCursor() const;
|
bool ApplyCurrentCursor() const;
|
||||||
LPCWSTR ResolveCurrentCursorResource() const;
|
LPCWSTR ResolveCurrentCursorResource() const;
|
||||||
@@ -62,22 +81,57 @@ private:
|
|||||||
void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam);
|
void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam);
|
||||||
void QueueCharacterEvent(WPARAM wParam, LPARAM lParam);
|
void QueueCharacterEvent(WPARAM wParam, LPARAM lParam);
|
||||||
void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type);
|
void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type);
|
||||||
|
bool IsBorderlessWindowEnabled() const;
|
||||||
|
bool IsBorderlessWindowMaximized() const;
|
||||||
|
bool HandleBorderlessWindowSystemCommand(WPARAM wParam);
|
||||||
|
bool HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const;
|
||||||
|
LRESULT HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const;
|
||||||
|
Host::BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(LPARAM lParam) const;
|
||||||
|
bool UpdateBorderlessWindowChromeHover(LPARAM lParam);
|
||||||
|
bool HandleBorderlessWindowChromeButtonDown(LPARAM lParam);
|
||||||
|
bool HandleBorderlessWindowChromeButtonUp(LPARAM lParam);
|
||||||
|
bool HandleBorderlessWindowChromeDoubleClick(LPARAM lParam);
|
||||||
|
bool HandleBorderlessWindowChromeDragRestorePointerMove();
|
||||||
|
void ClearBorderlessWindowChromeDragRestoreState();
|
||||||
|
void ClearBorderlessWindowChromeState();
|
||||||
|
Host::BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(LPARAM lParam) const;
|
||||||
|
bool UpdateBorderlessWindowResizeHover(LPARAM lParam);
|
||||||
|
bool HandleBorderlessWindowResizeButtonDown(LPARAM lParam);
|
||||||
|
bool HandleBorderlessWindowResizeButtonUp();
|
||||||
|
bool HandleBorderlessWindowResizePointerMove();
|
||||||
|
void ClearBorderlessWindowResizeState();
|
||||||
|
void ForceClearBorderlessWindowResizeState();
|
||||||
|
void ApplyBorderlessWindowResizeCursorHoverPriority();
|
||||||
|
bool QueryCurrentWindowRect(RECT& outRect) const;
|
||||||
|
bool QueryBorderlessWindowWorkAreaRect(RECT& outRect) const;
|
||||||
|
bool ApplyPredictedWindowRectTransition(const RECT& targetRect);
|
||||||
|
void ToggleBorderlessWindowMaximizeRestore();
|
||||||
|
void AppendBorderlessWindowChrome(
|
||||||
|
::XCEngine::UI::UIDrawList& drawList,
|
||||||
|
float clientWidthDips) const;
|
||||||
|
void ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget target);
|
||||||
|
Host::BorderlessWindowChromeLayout ResolveBorderlessWindowChromeLayout(
|
||||||
|
float clientWidthDips) const;
|
||||||
|
void InvalidateHostWindow() const;
|
||||||
static std::filesystem::path ResolveRepoRootPath();
|
static std::filesystem::path ResolveRepoRootPath();
|
||||||
static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo);
|
static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo);
|
||||||
|
static bool IsVerboseRuntimeTraceEnabled();
|
||||||
|
|
||||||
HWND m_hwnd = nullptr;
|
HWND m_hwnd = nullptr;
|
||||||
HINSTANCE m_hInstance = nullptr;
|
HINSTANCE m_hInstance = nullptr;
|
||||||
ATOM m_windowClassAtom = 0;
|
ATOM m_windowClassAtom = 0;
|
||||||
::XCEngine::UI::Editor::Host::NativeRenderer m_renderer = {};
|
::XCEngine::UI::Editor::Host::NativeRenderer m_renderer = {};
|
||||||
::XCEngine::UI::Editor::Host::D3D12WindowRenderer m_windowRenderer = {};
|
::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::AutoScreenshotController m_autoScreenshot = {};
|
||||||
::XCEngine::UI::Editor::Host::InputModifierTracker m_inputModifierTracker = {};
|
::XCEngine::UI::Editor::Host::InputModifierTracker m_inputModifierTracker = {};
|
||||||
App::ProductEditorContext m_editorContext = {};
|
App::ProductEditorContext m_editorContext = {};
|
||||||
App::ProductEditorWorkspace m_editorWorkspace = {};
|
App::ProductEditorWorkspace m_editorWorkspace = {};
|
||||||
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
|
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
|
||||||
bool m_trackingMouseLeave = false;
|
bool m_trackingMouseLeave = false;
|
||||||
UINT m_windowDpi = 96u;
|
bool m_renderReady = false;
|
||||||
float m_dpiScale = 1.0f;
|
::XCEngine::UI::Editor::Host::BorderlessWindowChromeState m_borderlessWindowChromeState = {};
|
||||||
|
::XCEngine::UI::Editor::Host::HostRuntimeState m_hostRuntime = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow);
|
int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow);
|
||||||
|
|||||||
403
new_editor/app/Host/BorderlessWindowChrome.cpp
Normal file
403
new_editor/app/Host/BorderlessWindowChrome.cpp
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
#include "BorderlessWindowChrome.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <dwmapi.h>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::XCEngine::UI::UIColor;
|
||||||
|
using ::XCEngine::UI::UIDrawList;
|
||||||
|
using ::XCEngine::UI::UIPoint;
|
||||||
|
using ::XCEngine::UI::UIRect;
|
||||||
|
|
||||||
|
bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) {
|
||||||
|
return rect.width > 0.0f &&
|
||||||
|
rect.height > 0.0f &&
|
||||||
|
point.x >= rect.x &&
|
||||||
|
point.x <= rect.x + rect.width &&
|
||||||
|
point.y >= rect.y &&
|
||||||
|
point.y <= rect.y + rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendMinimizeGlyph(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const UIRect& rect,
|
||||||
|
const UIColor& color,
|
||||||
|
float thickness) {
|
||||||
|
const float centerX = rect.x + rect.width * 0.5f;
|
||||||
|
const float centerY = rect.y + rect.height * 0.5f;
|
||||||
|
const float halfWidth = (std::max)(4.0f, rect.height * 0.22f);
|
||||||
|
const float y = centerY + rect.height * 0.12f;
|
||||||
|
drawList.AddLine(
|
||||||
|
UIPoint(centerX - halfWidth, y),
|
||||||
|
UIPoint(centerX + halfWidth, y),
|
||||||
|
color,
|
||||||
|
thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendMaximizeGlyph(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const UIRect& rect,
|
||||||
|
const UIColor& color,
|
||||||
|
float thickness) {
|
||||||
|
const float centerX = rect.x + rect.width * 0.5f;
|
||||||
|
const float centerY = rect.y + rect.height * 0.5f;
|
||||||
|
const float halfExtent = (std::max)(4.0f, rect.height * 0.20f);
|
||||||
|
drawList.AddRectOutline(
|
||||||
|
UIRect(
|
||||||
|
centerX - halfExtent,
|
||||||
|
centerY - halfExtent,
|
||||||
|
halfExtent * 2.0f,
|
||||||
|
halfExtent * 2.0f),
|
||||||
|
color,
|
||||||
|
thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendRestoreGlyph(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const UIRect& rect,
|
||||||
|
const UIColor& color,
|
||||||
|
float thickness) {
|
||||||
|
const float centerX = rect.x + rect.width * 0.5f;
|
||||||
|
const float centerY = rect.y + rect.height * 0.5f;
|
||||||
|
const float halfExtent = (std::max)(4.0f, rect.height * 0.18f);
|
||||||
|
const float offset = 2.0f;
|
||||||
|
drawList.AddRectOutline(
|
||||||
|
UIRect(
|
||||||
|
centerX - halfExtent + offset,
|
||||||
|
centerY - halfExtent - offset,
|
||||||
|
halfExtent * 2.0f,
|
||||||
|
halfExtent * 2.0f),
|
||||||
|
color,
|
||||||
|
thickness);
|
||||||
|
drawList.AddRectOutline(
|
||||||
|
UIRect(
|
||||||
|
centerX - halfExtent - offset,
|
||||||
|
centerY - halfExtent + offset,
|
||||||
|
halfExtent * 2.0f,
|
||||||
|
halfExtent * 2.0f),
|
||||||
|
color,
|
||||||
|
thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendCloseGlyph(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const UIRect& rect,
|
||||||
|
const UIColor& color,
|
||||||
|
float thickness) {
|
||||||
|
const float centerX = rect.x + rect.width * 0.5f;
|
||||||
|
const float centerY = rect.y + rect.height * 0.5f;
|
||||||
|
const float halfWidth = (std::max)(4.0f, rect.height * 0.20f);
|
||||||
|
const float halfHeight = halfWidth;
|
||||||
|
drawList.AddLine(
|
||||||
|
UIPoint(centerX - halfWidth, centerY - halfHeight),
|
||||||
|
UIPoint(centerX + halfWidth, centerY + halfHeight),
|
||||||
|
color,
|
||||||
|
thickness);
|
||||||
|
drawList.AddLine(
|
||||||
|
UIPoint(centerX + halfWidth, centerY - halfHeight),
|
||||||
|
UIPoint(centerX - halfWidth, centerY + halfHeight),
|
||||||
|
color,
|
||||||
|
thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::UI::UIColor ResolveButtonFillColor(
|
||||||
|
BorderlessWindowChromeHitTarget target,
|
||||||
|
const BorderlessWindowChromeState& state,
|
||||||
|
const BorderlessWindowChromePalette& palette) {
|
||||||
|
const bool hovered = state.hoveredTarget == target;
|
||||||
|
const bool pressed = state.pressedTarget == target;
|
||||||
|
if (target == BorderlessWindowChromeHitTarget::CloseButton) {
|
||||||
|
if (pressed) {
|
||||||
|
return palette.closeButtonPressedColor;
|
||||||
|
}
|
||||||
|
if (hovered) {
|
||||||
|
return palette.closeButtonHoverColor;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
return palette.buttonPressedColor;
|
||||||
|
}
|
||||||
|
if (hovered) {
|
||||||
|
return palette.buttonHoverColor;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::UI::UIColor ResolveIconColor(
|
||||||
|
BorderlessWindowChromeHitTarget target,
|
||||||
|
const BorderlessWindowChromeState& state,
|
||||||
|
const BorderlessWindowChromePalette& palette) {
|
||||||
|
if (target == BorderlessWindowChromeHitTarget::CloseButton &&
|
||||||
|
(state.hoveredTarget == target || state.pressedTarget == target)) {
|
||||||
|
return palette.closeIconHoverColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return palette.iconColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QuerySystemMetricForDpi(int index, UINT dpi) {
|
||||||
|
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||||
|
if (user32 != nullptr) {
|
||||||
|
using GetSystemMetricsForDpiFn = int(WINAPI*)(int, UINT);
|
||||||
|
const auto getSystemMetricsForDpi =
|
||||||
|
reinterpret_cast<GetSystemMetricsForDpiFn>(
|
||||||
|
GetProcAddress(user32, "GetSystemMetricsForDpi"));
|
||||||
|
if (getSystemMetricsForDpi != nullptr) {
|
||||||
|
return getSystemMetricsForDpi(index, dpi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetSystemMetrics(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsWindowAlignedToMonitorWorkArea(HWND hwnd) {
|
||||||
|
if (hwnd == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT windowRect = {};
|
||||||
|
if (!GetWindowRect(hwnd, &windowRect)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
if (monitor == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MONITORINFO monitorInfo = {};
|
||||||
|
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||||
|
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT& workArea = monitorInfo.rcWork;
|
||||||
|
return windowRect.left == workArea.left &&
|
||||||
|
windowRect.top == workArea.top &&
|
||||||
|
windowRect.right == workArea.right &&
|
||||||
|
windowRect.bottom == workArea.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyDwmBoolWindowAttribute(HWND hwnd, DWORD attribute, BOOL value) {
|
||||||
|
if (hwnd == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD);
|
||||||
|
static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn {
|
||||||
|
HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll");
|
||||||
|
if (dwmapi == nullptr) {
|
||||||
|
dwmapi = LoadLibraryW(L"dwmapi.dll");
|
||||||
|
}
|
||||||
|
if (dwmapi == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reinterpret_cast<DwmSetWindowAttributeFn>(
|
||||||
|
GetProcAddress(dwmapi, "DwmSetWindowAttribute"));
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (setWindowAttribute != nullptr) {
|
||||||
|
setWindowAttribute(hwnd, attribute, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout(
|
||||||
|
const UIRect& titleBarRect,
|
||||||
|
float leadingOccupiedRight,
|
||||||
|
const BorderlessWindowChromeMetrics& metrics) {
|
||||||
|
BorderlessWindowChromeLayout layout = {};
|
||||||
|
layout.titleBarRect = titleBarRect;
|
||||||
|
if (titleBarRect.width <= 0.0f || titleBarRect.height <= 0.0f) {
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float buttonWidth = (std::max)(metrics.buttonWidth, 0.0f);
|
||||||
|
const float buttonX3 = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX - buttonWidth;
|
||||||
|
const float buttonX2 = buttonX3 - buttonWidth;
|
||||||
|
const float buttonX1 = buttonX2 - buttonWidth;
|
||||||
|
|
||||||
|
layout.minimizeButtonRect = UIRect(buttonX1, titleBarRect.y, buttonWidth, titleBarRect.height);
|
||||||
|
layout.maximizeRestoreButtonRect = UIRect(buttonX2, titleBarRect.y, buttonWidth, titleBarRect.height);
|
||||||
|
layout.closeButtonRect = UIRect(buttonX3, titleBarRect.y, buttonWidth, titleBarRect.height);
|
||||||
|
|
||||||
|
const float dragLeft =
|
||||||
|
(std::max)(titleBarRect.x, leadingOccupiedRight + metrics.dragPaddingLeft);
|
||||||
|
const float dragRight =
|
||||||
|
(std::max)(dragLeft, layout.minimizeButtonRect.x - metrics.dragPaddingRight);
|
||||||
|
layout.dragRect = UIRect(
|
||||||
|
dragLeft,
|
||||||
|
titleBarRect.y,
|
||||||
|
(std::max)(0.0f, dragRight - dragLeft),
|
||||||
|
titleBarRect.height);
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(
|
||||||
|
const BorderlessWindowChromeLayout& layout,
|
||||||
|
const UIPoint& point) {
|
||||||
|
if (IsPointInsideRect(layout.closeButtonRect, point)) {
|
||||||
|
return BorderlessWindowChromeHitTarget::CloseButton;
|
||||||
|
}
|
||||||
|
if (IsPointInsideRect(layout.maximizeRestoreButtonRect, point)) {
|
||||||
|
return BorderlessWindowChromeHitTarget::MaximizeRestoreButton;
|
||||||
|
}
|
||||||
|
if (IsPointInsideRect(layout.minimizeButtonRect, point)) {
|
||||||
|
return BorderlessWindowChromeHitTarget::MinimizeButton;
|
||||||
|
}
|
||||||
|
if (IsPointInsideRect(layout.dragRect, point)) {
|
||||||
|
return BorderlessWindowChromeHitTarget::DragRegion;
|
||||||
|
}
|
||||||
|
return BorderlessWindowChromeHitTarget::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendBorderlessWindowChrome(
|
||||||
|
UIDrawList& drawList,
|
||||||
|
const BorderlessWindowChromeLayout& layout,
|
||||||
|
const BorderlessWindowChromeState& state,
|
||||||
|
bool maximized,
|
||||||
|
const BorderlessWindowChromePalette& palette,
|
||||||
|
const BorderlessWindowChromeMetrics& metrics) {
|
||||||
|
const struct ButtonEntry {
|
||||||
|
BorderlessWindowChromeHitTarget target;
|
||||||
|
UIRect rect;
|
||||||
|
} buttons[] = {
|
||||||
|
{ BorderlessWindowChromeHitTarget::MinimizeButton, layout.minimizeButtonRect },
|
||||||
|
{ BorderlessWindowChromeHitTarget::MaximizeRestoreButton, layout.maximizeRestoreButtonRect },
|
||||||
|
{ BorderlessWindowChromeHitTarget::CloseButton, layout.closeButtonRect }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const ButtonEntry& button : buttons) {
|
||||||
|
const UIColor fill = ResolveButtonFillColor(button.target, state, palette);
|
||||||
|
if (fill.a > 0.0f) {
|
||||||
|
drawList.AddFilledRect(button.rect, fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UIColor iconColor = ResolveIconColor(button.target, state, palette);
|
||||||
|
switch (button.target) {
|
||||||
|
case BorderlessWindowChromeHitTarget::MinimizeButton:
|
||||||
|
AppendMinimizeGlyph(
|
||||||
|
drawList,
|
||||||
|
button.rect,
|
||||||
|
iconColor,
|
||||||
|
metrics.iconThickness);
|
||||||
|
break;
|
||||||
|
case BorderlessWindowChromeHitTarget::MaximizeRestoreButton:
|
||||||
|
if (maximized) {
|
||||||
|
AppendRestoreGlyph(
|
||||||
|
drawList,
|
||||||
|
button.rect,
|
||||||
|
iconColor,
|
||||||
|
metrics.iconThickness);
|
||||||
|
} else {
|
||||||
|
AppendMaximizeGlyph(
|
||||||
|
drawList,
|
||||||
|
button.rect,
|
||||||
|
iconColor,
|
||||||
|
metrics.iconThickness);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BorderlessWindowChromeHitTarget::CloseButton:
|
||||||
|
AppendCloseGlyph(
|
||||||
|
drawList,
|
||||||
|
button.rect,
|
||||||
|
iconColor,
|
||||||
|
metrics.iconThickness);
|
||||||
|
break;
|
||||||
|
case BorderlessWindowChromeHitTarget::DragRegion:
|
||||||
|
case BorderlessWindowChromeHitTarget::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableBorderlessWindowShadow(HWND hwnd) {
|
||||||
|
if (hwnd == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using DwmExtendFrameIntoClientAreaFn = HRESULT(WINAPI*)(HWND, const MARGINS*);
|
||||||
|
static const auto extendFrameIntoClientArea = []() -> DwmExtendFrameIntoClientAreaFn {
|
||||||
|
HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll");
|
||||||
|
if (dwmapi == nullptr) {
|
||||||
|
dwmapi = LoadLibraryW(L"dwmapi.dll");
|
||||||
|
}
|
||||||
|
if (dwmapi == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return reinterpret_cast<DwmExtendFrameIntoClientAreaFn>(
|
||||||
|
GetProcAddress(dwmapi, "DwmExtendFrameIntoClientArea"));
|
||||||
|
}();
|
||||||
|
if (extendFrameIntoClientArea != nullptr) {
|
||||||
|
const bool maximized = IsZoomed(hwnd) || IsWindowAlignedToMonitorWorkArea(hwnd);
|
||||||
|
const MARGINS margins = maximized
|
||||||
|
? MARGINS{ 0, 0, 0, 0 }
|
||||||
|
: MARGINS{ 1, 1, 1, 1 };
|
||||||
|
extendFrameIntoClientArea(hwnd, &margins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RefreshBorderlessWindowDwmDecorations(HWND hwnd) {
|
||||||
|
if (hwnd == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borderless host cannot participate in compositor-driven minimize/maximize
|
||||||
|
// transitions without Windows stretching the last presented client frame.
|
||||||
|
// Disable those transitions for this window, then refresh the shadow state.
|
||||||
|
ApplyDwmBoolWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, TRUE);
|
||||||
|
EnableBorderlessWindowShadow(hwnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HandleBorderlessWindowGetMinMaxInfo(HWND hwnd, LPARAM lParam) {
|
||||||
|
if (hwnd == nullptr || lParam == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* minMaxInfo = reinterpret_cast<MINMAXINFO*>(lParam);
|
||||||
|
if (minMaxInfo == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
if (monitor == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MONITORINFO monitorInfo = {};
|
||||||
|
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||||
|
if (!GetMonitorInfoW(monitor, &monitorInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT& workArea = monitorInfo.rcWork;
|
||||||
|
const RECT& monitorArea = monitorInfo.rcMonitor;
|
||||||
|
minMaxInfo->ptMaxPosition.x = workArea.left - monitorArea.left;
|
||||||
|
minMaxInfo->ptMaxPosition.y = workArea.top - monitorArea.top;
|
||||||
|
minMaxInfo->ptMaxSize.x = workArea.right - workArea.left;
|
||||||
|
minMaxInfo->ptMaxSize.y = workArea.bottom - workArea.top;
|
||||||
|
minMaxInfo->ptMaxTrackSize = minMaxInfo->ptMaxSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT HandleBorderlessWindowNcCalcSize(
|
||||||
|
HWND hwnd,
|
||||||
|
WPARAM wParam,
|
||||||
|
LPARAM lParam,
|
||||||
|
UINT dpi) {
|
||||||
|
(void)hwnd;
|
||||||
|
(void)wParam;
|
||||||
|
(void)lParam;
|
||||||
|
(void)dpi;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
85
new_editor/app/Host/BorderlessWindowChrome.h
Normal file
85
new_editor/app/Host/BorderlessWindowChrome.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <XCEngine/UI/DrawData.h>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
enum class BorderlessWindowChromeHitTarget : std::uint8_t {
|
||||||
|
None = 0,
|
||||||
|
DragRegion,
|
||||||
|
MinimizeButton,
|
||||||
|
MaximizeRestoreButton,
|
||||||
|
CloseButton
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BorderlessWindowChromeMetrics {
|
||||||
|
float buttonWidth = 46.0f;
|
||||||
|
float buttonInsetX = 0.0f;
|
||||||
|
float dragPaddingLeft = 6.0f;
|
||||||
|
float dragPaddingRight = 6.0f;
|
||||||
|
float iconThickness = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BorderlessWindowChromePalette {
|
||||||
|
::XCEngine::UI::UIColor buttonHoverColor =
|
||||||
|
::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f);
|
||||||
|
::XCEngine::UI::UIColor buttonPressedColor =
|
||||||
|
::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f);
|
||||||
|
::XCEngine::UI::UIColor closeButtonHoverColor =
|
||||||
|
::XCEngine::UI::UIColor(0.91f, 0.31f, 0.24f, 1.0f);
|
||||||
|
::XCEngine::UI::UIColor closeButtonPressedColor =
|
||||||
|
::XCEngine::UI::UIColor(0.78f, 0.22f, 0.18f, 1.0f);
|
||||||
|
::XCEngine::UI::UIColor iconColor =
|
||||||
|
::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f);
|
||||||
|
::XCEngine::UI::UIColor closeIconHoverColor =
|
||||||
|
::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BorderlessWindowChromeState {
|
||||||
|
BorderlessWindowChromeHitTarget hoveredTarget = BorderlessWindowChromeHitTarget::None;
|
||||||
|
BorderlessWindowChromeHitTarget pressedTarget = BorderlessWindowChromeHitTarget::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BorderlessWindowChromeLayout {
|
||||||
|
::XCEngine::UI::UIRect titleBarRect = {};
|
||||||
|
::XCEngine::UI::UIRect dragRect = {};
|
||||||
|
::XCEngine::UI::UIRect minimizeButtonRect = {};
|
||||||
|
::XCEngine::UI::UIRect maximizeRestoreButtonRect = {};
|
||||||
|
::XCEngine::UI::UIRect closeButtonRect = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout(
|
||||||
|
const ::XCEngine::UI::UIRect& titleBarRect,
|
||||||
|
float leadingOccupiedRight,
|
||||||
|
const BorderlessWindowChromeMetrics& metrics = {});
|
||||||
|
|
||||||
|
BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(
|
||||||
|
const BorderlessWindowChromeLayout& layout,
|
||||||
|
const ::XCEngine::UI::UIPoint& point);
|
||||||
|
|
||||||
|
void AppendBorderlessWindowChrome(
|
||||||
|
::XCEngine::UI::UIDrawList& drawList,
|
||||||
|
const BorderlessWindowChromeLayout& layout,
|
||||||
|
const BorderlessWindowChromeState& state,
|
||||||
|
bool maximized,
|
||||||
|
const BorderlessWindowChromePalette& palette = {},
|
||||||
|
const BorderlessWindowChromeMetrics& metrics = {});
|
||||||
|
|
||||||
|
void RefreshBorderlessWindowDwmDecorations(HWND hwnd);
|
||||||
|
void EnableBorderlessWindowShadow(HWND hwnd);
|
||||||
|
|
||||||
|
bool HandleBorderlessWindowGetMinMaxInfo(HWND hwnd, LPARAM lParam);
|
||||||
|
|
||||||
|
LRESULT HandleBorderlessWindowNcCalcSize(
|
||||||
|
HWND hwnd,
|
||||||
|
WPARAM wParam,
|
||||||
|
LPARAM lParam,
|
||||||
|
UINT dpi);
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
176
new_editor/app/Host/BorderlessWindowFrame.cpp
Normal file
176
new_editor/app/Host/BorderlessWindowFrame.cpp
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#include "BorderlessWindowFrame.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::XCEngine::UI::UIPoint;
|
||||||
|
using ::XCEngine::UI::UIRect;
|
||||||
|
|
||||||
|
bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) {
|
||||||
|
return rect.width > 0.0f &&
|
||||||
|
rect.height > 0.0f &&
|
||||||
|
point.x >= rect.x &&
|
||||||
|
point.x <= rect.x + rect.width &&
|
||||||
|
point.y >= rect.y &&
|
||||||
|
point.y <= rect.y + rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClampMinimum(int value, int minimum) {
|
||||||
|
return (std::max)(value, minimum);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(
|
||||||
|
const UIRect& clientRect,
|
||||||
|
const UIPoint& point,
|
||||||
|
const BorderlessWindowFrameMetrics& metrics) {
|
||||||
|
const float edge = (std::max)(metrics.resizeBorderThickness, 0.0f);
|
||||||
|
if (edge <= 0.0f || !IsPointInsideRect(clientRect, point)) {
|
||||||
|
return BorderlessWindowResizeEdge::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool left = point.x <= clientRect.x + edge;
|
||||||
|
const bool right = point.x >= clientRect.x + clientRect.width - edge;
|
||||||
|
const bool top = point.y <= clientRect.y + edge;
|
||||||
|
const bool bottom = point.y >= clientRect.y + clientRect.height - edge;
|
||||||
|
|
||||||
|
if (left && top) {
|
||||||
|
return BorderlessWindowResizeEdge::TopLeft;
|
||||||
|
}
|
||||||
|
if (right && top) {
|
||||||
|
return BorderlessWindowResizeEdge::TopRight;
|
||||||
|
}
|
||||||
|
if (left && bottom) {
|
||||||
|
return BorderlessWindowResizeEdge::BottomLeft;
|
||||||
|
}
|
||||||
|
if (right && bottom) {
|
||||||
|
return BorderlessWindowResizeEdge::BottomRight;
|
||||||
|
}
|
||||||
|
if (left) {
|
||||||
|
return BorderlessWindowResizeEdge::Left;
|
||||||
|
}
|
||||||
|
if (right) {
|
||||||
|
return BorderlessWindowResizeEdge::Right;
|
||||||
|
}
|
||||||
|
if (top) {
|
||||||
|
return BorderlessWindowResizeEdge::Top;
|
||||||
|
}
|
||||||
|
if (bottom) {
|
||||||
|
return BorderlessWindowResizeEdge::Bottom;
|
||||||
|
}
|
||||||
|
return BorderlessWindowResizeEdge::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPCWSTR ResolveBorderlessWindowResizeCursor(BorderlessWindowResizeEdge edge) {
|
||||||
|
switch (edge) {
|
||||||
|
case BorderlessWindowResizeEdge::Left:
|
||||||
|
case BorderlessWindowResizeEdge::Right:
|
||||||
|
return IDC_SIZEWE;
|
||||||
|
case BorderlessWindowResizeEdge::Top:
|
||||||
|
case BorderlessWindowResizeEdge::Bottom:
|
||||||
|
return IDC_SIZENS;
|
||||||
|
case BorderlessWindowResizeEdge::TopLeft:
|
||||||
|
case BorderlessWindowResizeEdge::BottomRight:
|
||||||
|
return IDC_SIZENWSE;
|
||||||
|
case BorderlessWindowResizeEdge::TopRight:
|
||||||
|
case BorderlessWindowResizeEdge::BottomLeft:
|
||||||
|
return IDC_SIZENESW;
|
||||||
|
case BorderlessWindowResizeEdge::None:
|
||||||
|
default:
|
||||||
|
return IDC_ARROW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT ComputeBorderlessWindowResizeRect(
|
||||||
|
const RECT& initialRect,
|
||||||
|
const POINT& initialScreenPoint,
|
||||||
|
const POINT& currentScreenPoint,
|
||||||
|
BorderlessWindowResizeEdge edge,
|
||||||
|
int minimumOuterWidth,
|
||||||
|
int minimumOuterHeight) {
|
||||||
|
RECT result = initialRect;
|
||||||
|
const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x;
|
||||||
|
const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y;
|
||||||
|
const int minimumWidth = ClampMinimum(minimumOuterWidth, 1);
|
||||||
|
const int minimumHeight = ClampMinimum(minimumOuterHeight, 1);
|
||||||
|
|
||||||
|
switch (edge) {
|
||||||
|
case BorderlessWindowResizeEdge::Left:
|
||||||
|
case BorderlessWindowResizeEdge::TopLeft:
|
||||||
|
case BorderlessWindowResizeEdge::BottomLeft:
|
||||||
|
result.left += deltaX;
|
||||||
|
if (result.right - result.left < minimumWidth) {
|
||||||
|
result.left = result.right - minimumWidth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BorderlessWindowResizeEdge::None:
|
||||||
|
case BorderlessWindowResizeEdge::Top:
|
||||||
|
case BorderlessWindowResizeEdge::Bottom:
|
||||||
|
case BorderlessWindowResizeEdge::Right:
|
||||||
|
case BorderlessWindowResizeEdge::TopRight:
|
||||||
|
case BorderlessWindowResizeEdge::BottomRight:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (edge) {
|
||||||
|
case BorderlessWindowResizeEdge::Right:
|
||||||
|
case BorderlessWindowResizeEdge::TopRight:
|
||||||
|
case BorderlessWindowResizeEdge::BottomRight:
|
||||||
|
result.right += deltaX;
|
||||||
|
if (result.right - result.left < minimumWidth) {
|
||||||
|
result.right = result.left + minimumWidth;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BorderlessWindowResizeEdge::None:
|
||||||
|
case BorderlessWindowResizeEdge::Left:
|
||||||
|
case BorderlessWindowResizeEdge::Top:
|
||||||
|
case BorderlessWindowResizeEdge::Bottom:
|
||||||
|
case BorderlessWindowResizeEdge::TopLeft:
|
||||||
|
case BorderlessWindowResizeEdge::BottomLeft:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (edge) {
|
||||||
|
case BorderlessWindowResizeEdge::Top:
|
||||||
|
case BorderlessWindowResizeEdge::TopLeft:
|
||||||
|
case BorderlessWindowResizeEdge::TopRight:
|
||||||
|
result.top += deltaY;
|
||||||
|
if (result.bottom - result.top < minimumHeight) {
|
||||||
|
result.top = result.bottom - minimumHeight;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BorderlessWindowResizeEdge::None:
|
||||||
|
case BorderlessWindowResizeEdge::Left:
|
||||||
|
case BorderlessWindowResizeEdge::Right:
|
||||||
|
case BorderlessWindowResizeEdge::Bottom:
|
||||||
|
case BorderlessWindowResizeEdge::BottomLeft:
|
||||||
|
case BorderlessWindowResizeEdge::BottomRight:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (edge) {
|
||||||
|
case BorderlessWindowResizeEdge::Bottom:
|
||||||
|
case BorderlessWindowResizeEdge::BottomLeft:
|
||||||
|
case BorderlessWindowResizeEdge::BottomRight:
|
||||||
|
result.bottom += deltaY;
|
||||||
|
if (result.bottom - result.top < minimumHeight) {
|
||||||
|
result.bottom = result.top + minimumHeight;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BorderlessWindowResizeEdge::None:
|
||||||
|
case BorderlessWindowResizeEdge::Left:
|
||||||
|
case BorderlessWindowResizeEdge::Top:
|
||||||
|
case BorderlessWindowResizeEdge::Right:
|
||||||
|
case BorderlessWindowResizeEdge::TopLeft:
|
||||||
|
case BorderlessWindowResizeEdge::TopRight:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
46
new_editor/app/Host/BorderlessWindowFrame.h
Normal file
46
new_editor/app/Host/BorderlessWindowFrame.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <XCEngine/UI/Types.h>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
enum class BorderlessWindowResizeEdge : std::uint8_t {
|
||||||
|
None = 0,
|
||||||
|
Left,
|
||||||
|
Top,
|
||||||
|
Right,
|
||||||
|
Bottom,
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BorderlessWindowFrameMetrics {
|
||||||
|
float resizeBorderThickness = 6.0f;
|
||||||
|
int minimumOuterWidth = 640;
|
||||||
|
int minimumOuterHeight = 360;
|
||||||
|
};
|
||||||
|
|
||||||
|
BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(
|
||||||
|
const ::XCEngine::UI::UIRect& clientRect,
|
||||||
|
const ::XCEngine::UI::UIPoint& point,
|
||||||
|
const BorderlessWindowFrameMetrics& metrics = {});
|
||||||
|
|
||||||
|
LPCWSTR ResolveBorderlessWindowResizeCursor(BorderlessWindowResizeEdge edge);
|
||||||
|
|
||||||
|
RECT ComputeBorderlessWindowResizeRect(
|
||||||
|
const RECT& initialRect,
|
||||||
|
const POINT& initialScreenPoint,
|
||||||
|
const POINT& currentScreenPoint,
|
||||||
|
BorderlessWindowResizeEdge edge,
|
||||||
|
int minimumOuterWidth,
|
||||||
|
int minimumOuterHeight);
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
296
new_editor/app/Host/D3D12HostDevice.cpp
Normal file
296
new_editor/app/Host/D3D12HostDevice.cpp
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
#include "D3D12HostDevice.h"
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHIFactory.h>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
using ::XCEngine::RHI::CommandListDesc;
|
||||||
|
using ::XCEngine::RHI::CommandQueueDesc;
|
||||||
|
using ::XCEngine::RHI::D3D12CommandList;
|
||||||
|
using ::XCEngine::RHI::D3D12CommandQueue;
|
||||||
|
using ::XCEngine::RHI::D3D12Device;
|
||||||
|
using ::XCEngine::RHI::RHICommandList;
|
||||||
|
using ::XCEngine::RHI::RHICommandQueue;
|
||||||
|
using ::XCEngine::RHI::RHIDevice;
|
||||||
|
using ::XCEngine::RHI::RHIDeviceDesc;
|
||||||
|
using ::XCEngine::RHI::RHIFactory;
|
||||||
|
using ::XCEngine::RHI::RHIType;
|
||||||
|
|
||||||
|
bool D3D12HostDevice::Initialize() {
|
||||||
|
Shutdown();
|
||||||
|
|
||||||
|
m_device = RHIFactory::CreateRHIDevice(RHIType::D3D12);
|
||||||
|
if (m_device == nullptr) {
|
||||||
|
m_lastError = "Failed to create the D3D12 RHI device.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIDeviceDesc deviceDesc = {};
|
||||||
|
#ifdef _DEBUG
|
||||||
|
deviceDesc.enableDebugLayer = true;
|
||||||
|
deviceDesc.enableGPUValidation = true;
|
||||||
|
#endif
|
||||||
|
if (!m_device->Initialize(deviceDesc)) {
|
||||||
|
m_lastError = "Failed to initialize the D3D12 RHI device.";
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandQueueDesc queueDesc = {};
|
||||||
|
queueDesc.queueType = static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
|
||||||
|
m_commandQueue = m_device->CreateCommandQueue(queueDesc);
|
||||||
|
if (m_commandQueue == nullptr || GetD3D12CommandQueue() == nullptr) {
|
||||||
|
m_lastError = "Failed to create the D3D12 command queue.";
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandListDesc commandListDesc = {};
|
||||||
|
commandListDesc.commandListType =
|
||||||
|
static_cast<std::uint32_t>(::XCEngine::RHI::CommandQueueType::Direct);
|
||||||
|
for (std::uint32_t commandListIndex = 0;
|
||||||
|
commandListIndex < kFrameContextCount;
|
||||||
|
++commandListIndex) {
|
||||||
|
m_commandLists[commandListIndex] = m_device->CreateCommandList(commandListDesc);
|
||||||
|
if (m_commandLists[commandListIndex] == nullptr ||
|
||||||
|
GetD3D12CommandList(commandListIndex) == nullptr) {
|
||||||
|
m_lastError = "Failed to create the D3D12 command list.";
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_commandLists[commandListIndex]->Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InitializeFrameCompletionFence()) {
|
||||||
|
m_lastError = "Failed to create the host frame completion fence.";
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_frameFenceValues.fill(0u);
|
||||||
|
m_lastError.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12HostDevice::Shutdown() {
|
||||||
|
WaitForGpuIdle();
|
||||||
|
ReleaseFrameCompletionFence();
|
||||||
|
|
||||||
|
for (auto*& commandList : m_commandLists) {
|
||||||
|
if (commandList != nullptr) {
|
||||||
|
commandList->Shutdown();
|
||||||
|
delete commandList;
|
||||||
|
commandList = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_commandQueue != nullptr) {
|
||||||
|
m_commandQueue->Shutdown();
|
||||||
|
delete m_commandQueue;
|
||||||
|
m_commandQueue = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_device != nullptr) {
|
||||||
|
m_device->Shutdown();
|
||||||
|
delete m_device;
|
||||||
|
m_device = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastError.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12HostDevice::BeginFrame(std::uint32_t frameIndex) {
|
||||||
|
WaitForFrame(frameIndex);
|
||||||
|
|
||||||
|
RHICommandList* commandList = GetCommandList(frameIndex);
|
||||||
|
D3D12CommandList* d3d12CommandList = GetD3D12CommandList(frameIndex);
|
||||||
|
if (commandList == nullptr || d3d12CommandList == nullptr) {
|
||||||
|
m_lastError = "BeginFrame could not resolve the active command list.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandList->Reset();
|
||||||
|
m_lastError.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12HostDevice::SubmitFrame(std::uint32_t frameIndex) {
|
||||||
|
RHICommandList* commandList = GetCommandList(frameIndex);
|
||||||
|
if (commandList == nullptr || m_commandQueue == nullptr) {
|
||||||
|
m_lastError = "SubmitFrame requires an initialized command list and queue.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandList->Close();
|
||||||
|
void* commandLists[] = { commandList };
|
||||||
|
m_commandQueue->ExecuteCommandLists(1, commandLists);
|
||||||
|
m_lastError.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12HostDevice::SignalFrameCompletion(std::uint32_t frameIndex) {
|
||||||
|
ID3D12CommandQueue* commandQueue = GetCommandQueue();
|
||||||
|
if (commandQueue == nullptr ||
|
||||||
|
m_frameCompletionFence == nullptr ||
|
||||||
|
frameIndex >= m_frameFenceValues.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_lastSubmittedFrameValue;
|
||||||
|
const HRESULT hr = commandQueue->Signal(
|
||||||
|
m_frameCompletionFence.Get(),
|
||||||
|
m_lastSubmittedFrameValue);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
m_frameFenceValues[frameIndex] = m_lastSubmittedFrameValue;
|
||||||
|
m_lastError.clear();
|
||||||
|
}
|
||||||
|
return SUCCEEDED(hr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12HostDevice::WaitForFrame(std::uint32_t frameIndex) {
|
||||||
|
if (m_frameCompletionFence == nullptr ||
|
||||||
|
m_frameCompletionEvent == nullptr ||
|
||||||
|
frameIndex >= m_frameFenceValues.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint64_t fenceValue = m_frameFenceValues[frameIndex];
|
||||||
|
if (fenceValue == 0u ||
|
||||||
|
m_frameCompletionFence->GetCompletedValue() >= fenceValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HRESULT hr = m_frameCompletionFence->SetEventOnCompletion(
|
||||||
|
fenceValue,
|
||||||
|
m_frameCompletionEvent);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
WaitForSingleObject(m_frameCompletionEvent, INFINITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12HostDevice::WaitForGpuIdle() {
|
||||||
|
ID3D12CommandQueue* commandQueue = GetCommandQueue();
|
||||||
|
if (commandQueue == nullptr ||
|
||||||
|
m_frameCompletionFence == nullptr ||
|
||||||
|
m_frameCompletionEvent == nullptr) {
|
||||||
|
if (m_commandQueue != nullptr) {
|
||||||
|
m_commandQueue->WaitForIdle();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_lastSubmittedFrameValue;
|
||||||
|
const std::uint64_t fenceValue = m_lastSubmittedFrameValue;
|
||||||
|
const HRESULT signalHr = commandQueue->Signal(
|
||||||
|
m_frameCompletionFence.Get(),
|
||||||
|
fenceValue);
|
||||||
|
if (FAILED(signalHr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_frameCompletionFence->GetCompletedValue() >= fenceValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HRESULT waitHr = m_frameCompletionFence->SetEventOnCompletion(
|
||||||
|
fenceValue,
|
||||||
|
m_frameCompletionEvent);
|
||||||
|
if (SUCCEEDED(waitHr)) {
|
||||||
|
WaitForSingleObject(m_frameCompletionEvent, INFINITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12HostDevice::ResetFrameTracking() {
|
||||||
|
m_frameFenceValues.fill(0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D12Device* D3D12HostDevice::GetDevice() const {
|
||||||
|
const D3D12Device* device = GetD3D12Device();
|
||||||
|
return device != nullptr ? device->GetDevice() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D12CommandQueue* D3D12HostDevice::GetCommandQueue() const {
|
||||||
|
const D3D12CommandQueue* queue = GetD3D12CommandQueue();
|
||||||
|
return queue != nullptr ? queue->GetCommandQueue() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& D3D12HostDevice::GetLastError() const {
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIDevice* D3D12HostDevice::GetRHIDevice() const {
|
||||||
|
return m_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHICommandQueue* D3D12HostDevice::GetRHICommandQueue() const {
|
||||||
|
return m_commandQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHICommandList* D3D12HostDevice::GetCommandList(std::uint32_t frameIndex) const {
|
||||||
|
return frameIndex < m_commandLists.size()
|
||||||
|
? m_commandLists[frameIndex]
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
::XCEngine::Rendering::RenderContext D3D12HostDevice::GetRenderContext(
|
||||||
|
std::uint32_t frameIndex) const {
|
||||||
|
::XCEngine::Rendering::RenderContext context = {};
|
||||||
|
context.device = m_device;
|
||||||
|
context.commandList = GetCommandList(frameIndex);
|
||||||
|
context.commandQueue = m_commandQueue;
|
||||||
|
context.backendType = RHIType::D3D12;
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12Device* D3D12HostDevice::GetD3D12Device() const {
|
||||||
|
return m_device != nullptr ? static_cast<D3D12Device*>(m_device) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12CommandQueue* D3D12HostDevice::GetD3D12CommandQueue() const {
|
||||||
|
return m_commandQueue != nullptr ? static_cast<D3D12CommandQueue*>(m_commandQueue) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12CommandList* D3D12HostDevice::GetD3D12CommandList(std::uint32_t frameIndex) const {
|
||||||
|
RHICommandList* commandList = GetCommandList(frameIndex);
|
||||||
|
return commandList != nullptr ? static_cast<D3D12CommandList*>(commandList) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12HostDevice::InitializeFrameCompletionFence() {
|
||||||
|
ReleaseFrameCompletionFence();
|
||||||
|
|
||||||
|
ID3D12Device* device = GetDevice();
|
||||||
|
if (device == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HRESULT hr = device->CreateFence(
|
||||||
|
0u,
|
||||||
|
D3D12_FENCE_FLAG_NONE,
|
||||||
|
IID_PPV_ARGS(m_frameCompletionFence.ReleaseAndGetAddressOf()));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_frameCompletionEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||||
|
if (m_frameCompletionEvent == nullptr) {
|
||||||
|
m_frameCompletionFence.Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastSubmittedFrameValue = 0u;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12HostDevice::ReleaseFrameCompletionFence() {
|
||||||
|
if (m_frameCompletionEvent != nullptr) {
|
||||||
|
CloseHandle(m_frameCompletionEvent);
|
||||||
|
m_frameCompletionEvent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_frameCompletionFence.Reset();
|
||||||
|
m_frameFenceValues.fill(0u);
|
||||||
|
m_lastSubmittedFrameValue = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
64
new_editor/app/Host/D3D12HostDevice.h
Normal file
64
new_editor/app/Host/D3D12HostDevice.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12CommandList.h>
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12CommandQueue.h>
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
||||||
|
#include <XCEngine/RHI/RHICommandList.h>
|
||||||
|
#include <XCEngine/RHI/RHICommandQueue.h>
|
||||||
|
#include <XCEngine/RHI/RHIDevice.h>
|
||||||
|
|
||||||
|
#include <d3d12.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
class D3D12HostDevice {
|
||||||
|
public:
|
||||||
|
static constexpr std::uint32_t kFrameContextCount = 3u;
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
bool BeginFrame(std::uint32_t frameIndex);
|
||||||
|
bool SubmitFrame(std::uint32_t frameIndex);
|
||||||
|
bool SignalFrameCompletion(std::uint32_t frameIndex);
|
||||||
|
void WaitForFrame(std::uint32_t frameIndex);
|
||||||
|
void WaitForGpuIdle();
|
||||||
|
void ResetFrameTracking();
|
||||||
|
|
||||||
|
ID3D12Device* GetDevice() const;
|
||||||
|
ID3D12CommandQueue* GetCommandQueue() const;
|
||||||
|
const std::string& GetLastError() const;
|
||||||
|
::XCEngine::RHI::RHIDevice* GetRHIDevice() const;
|
||||||
|
::XCEngine::RHI::RHICommandQueue* GetRHICommandQueue() const;
|
||||||
|
::XCEngine::RHI::RHICommandList* GetCommandList(std::uint32_t frameIndex) const;
|
||||||
|
::XCEngine::Rendering::RenderContext GetRenderContext(std::uint32_t frameIndex) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
::XCEngine::RHI::D3D12Device* GetD3D12Device() const;
|
||||||
|
::XCEngine::RHI::D3D12CommandQueue* GetD3D12CommandQueue() const;
|
||||||
|
::XCEngine::RHI::D3D12CommandList* GetD3D12CommandList(std::uint32_t frameIndex) const;
|
||||||
|
bool InitializeFrameCompletionFence();
|
||||||
|
void ReleaseFrameCompletionFence();
|
||||||
|
|
||||||
|
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||||
|
::XCEngine::RHI::RHICommandQueue* m_commandQueue = nullptr;
|
||||||
|
std::array<::XCEngine::RHI::RHICommandList*, kFrameContextCount> m_commandLists = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID3D12Fence> m_frameCompletionFence = {};
|
||||||
|
HANDLE m_frameCompletionEvent = nullptr;
|
||||||
|
std::array<std::uint64_t, kFrameContextCount> m_frameFenceValues = {};
|
||||||
|
std::uint64_t m_lastSubmittedFrameValue = 0u;
|
||||||
|
std::string m_lastError = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
169
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.cpp
Normal file
169
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.cpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "D3D12ShaderResourceDescriptorAllocator.h"
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHITypes.h>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::XCEngine::RHI::D3D12DescriptorHeap;
|
||||||
|
using ::XCEngine::RHI::D3D12Device;
|
||||||
|
using ::XCEngine::RHI::D3D12Texture;
|
||||||
|
using ::XCEngine::RHI::DescriptorHeapType;
|
||||||
|
using ::XCEngine::RHI::DescriptorPoolDesc;
|
||||||
|
using ::XCEngine::RHI::RHIDevice;
|
||||||
|
using ::XCEngine::RHI::RHITexture;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool D3D12ShaderResourceDescriptorAllocator::Initialize(RHIDevice& device, UINT descriptorCount) {
|
||||||
|
Shutdown();
|
||||||
|
|
||||||
|
DescriptorPoolDesc descriptorPoolDesc = {};
|
||||||
|
descriptorPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||||
|
descriptorPoolDesc.descriptorCount = descriptorCount;
|
||||||
|
descriptorPoolDesc.shaderVisible = true;
|
||||||
|
m_descriptorPool = device.CreateDescriptorPool(descriptorPoolDesc);
|
||||||
|
m_descriptorHeap = dynamic_cast<D3D12DescriptorHeap*>(m_descriptorPool);
|
||||||
|
if (m_descriptorPool == nullptr || m_descriptorHeap == nullptr) {
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_device = &device;
|
||||||
|
m_descriptorSize = m_descriptorHeap->GetDescriptorSize();
|
||||||
|
m_descriptorCount = descriptorCount;
|
||||||
|
m_descriptorUsage.assign(descriptorCount, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12ShaderResourceDescriptorAllocator::Shutdown() {
|
||||||
|
if (m_descriptorPool != nullptr) {
|
||||||
|
m_descriptorPool->Shutdown();
|
||||||
|
delete m_descriptorPool;
|
||||||
|
m_descriptorPool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_device = nullptr;
|
||||||
|
m_descriptorHeap = nullptr;
|
||||||
|
m_descriptorUsage.clear();
|
||||||
|
m_descriptorSize = 0u;
|
||||||
|
m_descriptorCount = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12ShaderResourceDescriptorAllocator::IsInitialized() const {
|
||||||
|
return m_device != nullptr &&
|
||||||
|
m_descriptorHeap != nullptr &&
|
||||||
|
m_descriptorSize > 0u &&
|
||||||
|
!m_descriptorUsage.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D12DescriptorHeap* D3D12ShaderResourceDescriptorAllocator::GetDescriptorHeap() const {
|
||||||
|
return m_descriptorHeap != nullptr ? m_descriptorHeap->GetDescriptorHeap() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT D3D12ShaderResourceDescriptorAllocator::GetDescriptorSize() const {
|
||||||
|
return m_descriptorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT D3D12ShaderResourceDescriptorAllocator::GetDescriptorCount() const {
|
||||||
|
return m_descriptorCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12ShaderResourceDescriptorAllocator::Allocate(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||||
|
AllocateInternal(outCpuHandle, outGpuHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12ShaderResourceDescriptorAllocator::CreateTextureDescriptor(
|
||||||
|
RHITexture* texture,
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||||
|
if (m_device == nullptr ||
|
||||||
|
texture == nullptr ||
|
||||||
|
outCpuHandle == nullptr ||
|
||||||
|
outGpuHandle == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outCpuHandle = {};
|
||||||
|
*outGpuHandle = {};
|
||||||
|
|
||||||
|
auto* nativeDevice = dynamic_cast<D3D12Device*>(m_device);
|
||||||
|
auto* nativeTexture = dynamic_cast<D3D12Texture*>(texture);
|
||||||
|
if (nativeDevice == nullptr ||
|
||||||
|
nativeTexture == nullptr ||
|
||||||
|
nativeTexture->GetResource() == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocateInternal(outCpuHandle, outGpuHandle);
|
||||||
|
if (outCpuHandle->ptr == 0 || outGpuHandle->ptr == 0) {
|
||||||
|
*outCpuHandle = {};
|
||||||
|
*outGpuHandle = {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||||
|
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||||
|
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||||
|
srvDesc.Texture2D.MipLevels = 1;
|
||||||
|
nativeDevice->GetDevice()->CreateShaderResourceView(
|
||||||
|
nativeTexture->GetResource(),
|
||||||
|
&srvDesc,
|
||||||
|
*outCpuHandle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12ShaderResourceDescriptorAllocator::Free(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE) {
|
||||||
|
if (m_descriptorHeap == nullptr ||
|
||||||
|
m_descriptorSize == 0u ||
|
||||||
|
cpuHandle.ptr < m_descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIZE_T offset =
|
||||||
|
cpuHandle.ptr - m_descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr;
|
||||||
|
const std::size_t index =
|
||||||
|
static_cast<std::size_t>(offset / m_descriptorSize);
|
||||||
|
if (index < m_descriptorUsage.size()) {
|
||||||
|
m_descriptorUsage[index] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12ShaderResourceDescriptorAllocator::AllocateInternal(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||||
|
if (outCpuHandle == nullptr || outGpuHandle == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outCpuHandle = {};
|
||||||
|
*outGpuHandle = {};
|
||||||
|
if (m_descriptorHeap == nullptr ||
|
||||||
|
m_descriptorSize == 0u ||
|
||||||
|
m_descriptorUsage.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < m_descriptorUsage.size(); ++index) {
|
||||||
|
if (m_descriptorUsage[index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_descriptorUsage[index] = true;
|
||||||
|
outCpuHandle->ptr =
|
||||||
|
m_descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr +
|
||||||
|
static_cast<SIZE_T>(index) * m_descriptorSize;
|
||||||
|
outGpuHandle->ptr =
|
||||||
|
m_descriptorHeap->GetGPUDescriptorHandleForHeapStart().ptr +
|
||||||
|
static_cast<UINT64>(index) * m_descriptorSize;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
55
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.h
Normal file
55
new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12DescriptorHeap.h>
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||||
|
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||||
|
#include <XCEngine/RHI/RHIDevice.h>
|
||||||
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
|
||||||
|
#include <d3d12.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
class D3D12ShaderResourceDescriptorAllocator {
|
||||||
|
public:
|
||||||
|
bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u);
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
bool IsInitialized() const;
|
||||||
|
ID3D12DescriptorHeap* GetDescriptorHeap() const;
|
||||||
|
UINT GetDescriptorSize() const;
|
||||||
|
UINT GetDescriptorCount() const;
|
||||||
|
|
||||||
|
void Allocate(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||||
|
bool CreateTextureDescriptor(
|
||||||
|
::XCEngine::RHI::RHITexture* texture,
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||||
|
void Free(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AllocateInternal(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle);
|
||||||
|
|
||||||
|
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||||
|
::XCEngine::RHI::RHIDescriptorPool* m_descriptorPool = nullptr;
|
||||||
|
::XCEngine::RHI::D3D12DescriptorHeap* m_descriptorHeap = nullptr;
|
||||||
|
std::vector<bool> m_descriptorUsage = {};
|
||||||
|
UINT m_descriptorSize = 0u;
|
||||||
|
UINT m_descriptorCount = 0u;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
444
new_editor/app/Host/D3D12WindowInteropContext.cpp
Normal file
444
new_editor/app/Host/D3D12WindowInteropContext.cpp
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
#include "D3D12WindowInteropContext.h"
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||||
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string HrToInteropString(const char* operation, HRESULT hr) {
|
||||||
|
char buffer[128] = {};
|
||||||
|
sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast<unsigned int>(hr));
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties(
|
||||||
|
DXGI_FORMAT format,
|
||||||
|
D2D1_BITMAP_OPTIONS options) {
|
||||||
|
return D2D1::BitmapProperties1(
|
||||||
|
options,
|
||||||
|
D2D1::PixelFormat(format, D2D1_ALPHA_MODE_IGNORE),
|
||||||
|
96.0f,
|
||||||
|
96.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) {
|
||||||
|
return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView &&
|
||||||
|
texture.resourceHandle != 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectInteropTextureHandles(
|
||||||
|
const ::XCEngine::UI::UIDrawData& drawData,
|
||||||
|
std::vector<::XCEngine::UI::UITextureHandle>& outTextures) {
|
||||||
|
outTextures.clear();
|
||||||
|
std::unordered_set<std::uintptr_t> seenKeys = {};
|
||||||
|
for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||||
|
for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
|
||||||
|
if (!IsInteropTextureHandle(command.texture) ||
|
||||||
|
!seenKeys.insert(command.texture.resourceHandle).second) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
outTextures.push_back(command.texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::Attach(
|
||||||
|
D3D12WindowRenderer& windowRenderer,
|
||||||
|
ID2D1Factory1& d2dFactory) {
|
||||||
|
if (m_windowRenderer != &windowRenderer) {
|
||||||
|
Detach();
|
||||||
|
m_windowRenderer = &windowRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_d2dFactory = &d2dFactory;
|
||||||
|
if (!EnsureInterop()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasBackBufferTargets()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RebuildBackBufferTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12WindowInteropContext::Detach() {
|
||||||
|
ReleaseInteropState();
|
||||||
|
m_windowRenderer = nullptr;
|
||||||
|
m_d2dFactory = nullptr;
|
||||||
|
m_lastError.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12WindowInteropContext::ReleaseBackBufferTargets() {
|
||||||
|
ClearSourceTextures();
|
||||||
|
if (m_d2dDeviceContext != nullptr) {
|
||||||
|
m_d2dDeviceContext->SetTarget(nullptr);
|
||||||
|
}
|
||||||
|
if (m_d3d11DeviceContext != nullptr) {
|
||||||
|
m_d3d11DeviceContext->ClearState();
|
||||||
|
}
|
||||||
|
m_backBufferTargets.clear();
|
||||||
|
if (m_d2dDeviceContext != nullptr) {
|
||||||
|
D2D1_TAG firstTag = 0u;
|
||||||
|
D2D1_TAG secondTag = 0u;
|
||||||
|
m_d2dDeviceContext->Flush(&firstTag, &secondTag);
|
||||||
|
}
|
||||||
|
if (m_d3d11DeviceContext != nullptr) {
|
||||||
|
m_d3d11DeviceContext->Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::RebuildBackBufferTargets() {
|
||||||
|
m_backBufferTargets.clear();
|
||||||
|
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount();
|
||||||
|
m_backBufferTargets.resize(backBufferCount);
|
||||||
|
for (std::uint32_t index = 0u; index < backBufferCount; ++index) {
|
||||||
|
const ::XCEngine::RHI::D3D12Texture* backBufferTexture =
|
||||||
|
m_windowRenderer->GetBackBufferTexture(index);
|
||||||
|
if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) {
|
||||||
|
m_lastError = "Failed to resolve a D3D12 swap chain back buffer.";
|
||||||
|
m_backBufferTargets.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_RESOURCE_FLAGS resourceFlags = {};
|
||||||
|
resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||||
|
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
|
||||||
|
backBufferTexture->GetResource(),
|
||||||
|
&resourceFlags,
|
||||||
|
D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||||
|
D3D12_RESOURCE_STATE_PRESENT,
|
||||||
|
IID_PPV_ARGS(m_backBufferTargets[index].wrappedResource.ReleaseAndGetAddressOf()));
|
||||||
|
if (FAILED(hr) || m_backBufferTargets[index].wrappedResource == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr);
|
||||||
|
m_backBufferTargets.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface = {};
|
||||||
|
hr = m_backBufferTargets[index].wrappedResource.As(&dxgiSurface);
|
||||||
|
if (FAILED(hr) || dxgiSurface == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
|
||||||
|
m_backBufferTargets.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
|
||||||
|
BuildD2DBitmapProperties(
|
||||||
|
backBufferTexture->GetDesc().Format,
|
||||||
|
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW);
|
||||||
|
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
|
||||||
|
dxgiSurface.Get(),
|
||||||
|
&bitmapProperties,
|
||||||
|
m_backBufferTargets[index].targetBitmap.ReleaseAndGetAddressOf());
|
||||||
|
if (FAILED(hr) || m_backBufferTargets[index].targetBitmap == nullptr) {
|
||||||
|
m_lastError = HrToInteropString(
|
||||||
|
"ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)",
|
||||||
|
hr);
|
||||||
|
m_backBufferTargets.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastError.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::HasAttachedWindowRenderer() const {
|
||||||
|
return m_windowRenderer != nullptr &&
|
||||||
|
m_d3d11On12Device != nullptr &&
|
||||||
|
m_d2dDeviceContext != nullptr &&
|
||||||
|
!m_backBufferTargets.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::HasBackBufferTargets() const {
|
||||||
|
return !m_backBufferTargets.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::PrepareSourceTextures(
|
||||||
|
const ::XCEngine::UI::UIDrawData& drawData) {
|
||||||
|
ClearSourceTextures();
|
||||||
|
if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<::XCEngine::UI::UITextureHandle> textureHandles = {};
|
||||||
|
CollectInteropTextureHandles(drawData, textureHandles);
|
||||||
|
m_activeSourceTextures.reserve(textureHandles.size());
|
||||||
|
|
||||||
|
for (const ::XCEngine::UI::UITextureHandle& textureHandle : textureHandles) {
|
||||||
|
auto* texture =
|
||||||
|
reinterpret_cast<::XCEngine::RHI::RHITexture*>(textureHandle.resourceHandle);
|
||||||
|
auto* nativeTexture = dynamic_cast<::XCEngine::RHI::D3D12Texture*>(texture);
|
||||||
|
if (nativeTexture == nullptr || nativeTexture->GetResource() == nullptr) {
|
||||||
|
m_lastError = "Failed to resolve a D3D12 source texture for UI composition.";
|
||||||
|
ClearSourceTextures();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_RESOURCE_FLAGS resourceFlags = {};
|
||||||
|
resourceFlags.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||||
|
|
||||||
|
SourceTextureResource resource = {};
|
||||||
|
resource.key = textureHandle.resourceHandle;
|
||||||
|
HRESULT hr = m_d3d11On12Device->CreateWrappedResource(
|
||||||
|
nativeTexture->GetResource(),
|
||||||
|
&resourceFlags,
|
||||||
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||||
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
|
||||||
|
IID_PPV_ARGS(resource.wrappedResource.ReleaseAndGetAddressOf()));
|
||||||
|
if (FAILED(hr) || resource.wrappedResource == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(source)", hr);
|
||||||
|
ClearSourceTextures();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface = {};
|
||||||
|
hr = resource.wrappedResource.As(&dxgiSurface);
|
||||||
|
if (FAILED(hr) || dxgiSurface == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr);
|
||||||
|
ClearSourceTextures();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const D2D1_BITMAP_PROPERTIES1 bitmapProperties =
|
||||||
|
BuildD2DBitmapProperties(
|
||||||
|
nativeTexture->GetDesc().Format,
|
||||||
|
D2D1_BITMAP_OPTIONS_NONE);
|
||||||
|
hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
|
||||||
|
dxgiSurface.Get(),
|
||||||
|
&bitmapProperties,
|
||||||
|
resource.bitmap.ReleaseAndGetAddressOf());
|
||||||
|
if (FAILED(hr) || resource.bitmap == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(source)", hr);
|
||||||
|
ClearSourceTextures();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_activeBitmaps.emplace(resource.key, resource.bitmap);
|
||||||
|
m_activeSourceTextures.push_back(std::move(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastError.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12WindowInteropContext::ClearSourceTextures() {
|
||||||
|
m_activeBitmaps.clear();
|
||||||
|
m_activeSourceTextures.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::ResolveInteropBitmap(
|
||||||
|
const ::XCEngine::UI::UITextureHandle& texture,
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const {
|
||||||
|
outBitmap.Reset();
|
||||||
|
if (!IsInteropTextureHandle(texture)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto found = m_activeBitmaps.find(texture.resourceHandle);
|
||||||
|
if (found == m_activeBitmaps.end() || found->second == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outBitmap = found->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12WindowRenderer* D3D12WindowInteropContext::GetWindowRenderer() const {
|
||||||
|
return m_windowRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D11On12Device* D3D12WindowInteropContext::GetD3D11On12Device() const {
|
||||||
|
return m_d3d11On12Device.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D11DeviceContext* D3D12WindowInteropContext::GetD3D11DeviceContext() const {
|
||||||
|
return m_d3d11DeviceContext.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ID2D1DeviceContext* D3D12WindowInteropContext::GetD2DDeviceContext() const {
|
||||||
|
return m_d2dDeviceContext.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ID2D1SolidColorBrush* D3D12WindowInteropContext::GetInteropBrush() const {
|
||||||
|
return m_interopBrush.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12WindowInteropContext::BuildAcquiredResources(
|
||||||
|
std::uint32_t backBufferIndex,
|
||||||
|
std::vector<ID3D11Resource*>& outResources) const {
|
||||||
|
outResources.clear();
|
||||||
|
|
||||||
|
ID3D11Resource* backBufferResource = GetWrappedBackBufferResource(backBufferIndex);
|
||||||
|
if (backBufferResource != nullptr) {
|
||||||
|
outResources.push_back(backBufferResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const SourceTextureResource& resource : m_activeSourceTextures) {
|
||||||
|
if (resource.wrappedResource != nullptr) {
|
||||||
|
outResources.push_back(resource.wrappedResource.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D11Resource* D3D12WindowInteropContext::GetWrappedBackBufferResource(std::uint32_t index) const {
|
||||||
|
return index < m_backBufferTargets.size() ? m_backBufferTargets[index].wrappedResource.Get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID2D1Bitmap1* D3D12WindowInteropContext::GetBackBufferTargetBitmap(std::uint32_t index) const {
|
||||||
|
return index < m_backBufferTargets.size() ? m_backBufferTargets[index].targetBitmap.Get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t D3D12WindowInteropContext::GetCurrentBackBufferIndex() const {
|
||||||
|
return m_windowRenderer != nullptr && m_windowRenderer->GetSwapChain() != nullptr
|
||||||
|
? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex()
|
||||||
|
: 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& D3D12WindowInteropContext::GetLastError() const {
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12WindowInteropContext::EnsureInterop() {
|
||||||
|
if (m_windowRenderer == nullptr) {
|
||||||
|
m_lastError = "EnsureInterop requires an attached D3D12 window renderer.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_d2dFactory == nullptr) {
|
||||||
|
m_lastError = "EnsureInterop requires an initialized D2D factory.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_d3d11On12Device != nullptr &&
|
||||||
|
m_d2dDeviceContext != nullptr &&
|
||||||
|
m_interopBrush != nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseInteropState();
|
||||||
|
|
||||||
|
ID3D12Device* d3d12Device = m_windowRenderer->GetDevice();
|
||||||
|
ID3D12CommandQueue* d3d12CommandQueue = m_windowRenderer->GetCommandQueue();
|
||||||
|
if (d3d12Device == nullptr || d3d12CommandQueue == nullptr) {
|
||||||
|
m_lastError = "The attached D3D12 window renderer does not expose a native device/queue.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::array<D3D_FEATURE_LEVEL, 4> featureLevels = {
|
||||||
|
D3D_FEATURE_LEVEL_12_1,
|
||||||
|
D3D_FEATURE_LEVEL_12_0,
|
||||||
|
D3D_FEATURE_LEVEL_11_1,
|
||||||
|
D3D_FEATURE_LEVEL_11_0
|
||||||
|
};
|
||||||
|
const std::array<IUnknown*, 1> commandQueues = {
|
||||||
|
reinterpret_cast<IUnknown*>(d3d12CommandQueue)
|
||||||
|
};
|
||||||
|
|
||||||
|
UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||||
|
#ifdef _DEBUG
|
||||||
|
createFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0;
|
||||||
|
HRESULT hr = D3D11On12CreateDevice(
|
||||||
|
d3d12Device,
|
||||||
|
createFlags,
|
||||||
|
featureLevels.data(),
|
||||||
|
static_cast<UINT>(featureLevels.size()),
|
||||||
|
commandQueues.data(),
|
||||||
|
static_cast<UINT>(commandQueues.size()),
|
||||||
|
0u,
|
||||||
|
m_d3d11Device.ReleaseAndGetAddressOf(),
|
||||||
|
m_d3d11DeviceContext.ReleaseAndGetAddressOf(),
|
||||||
|
&actualFeatureLevel);
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
createFlags &= ~D3D11_CREATE_DEVICE_DEBUG;
|
||||||
|
hr = D3D11On12CreateDevice(
|
||||||
|
d3d12Device,
|
||||||
|
createFlags,
|
||||||
|
featureLevels.data(),
|
||||||
|
static_cast<UINT>(featureLevels.size()),
|
||||||
|
commandQueues.data(),
|
||||||
|
static_cast<UINT>(commandQueues.size()),
|
||||||
|
0u,
|
||||||
|
m_d3d11Device.ReleaseAndGetAddressOf(),
|
||||||
|
m_d3d11DeviceContext.ReleaseAndGetAddressOf(),
|
||||||
|
&actualFeatureLevel);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (FAILED(hr) || m_d3d11Device == nullptr || m_d3d11DeviceContext == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("D3D11On12CreateDevice", hr);
|
||||||
|
ReleaseInteropState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = m_d3d11Device.As(&m_d3d11On12Device);
|
||||||
|
if (FAILED(hr) || m_d3d11On12Device == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID3D11Device::QueryInterface(ID3D11On12Device)", hr);
|
||||||
|
ReleaseInteropState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice = {};
|
||||||
|
hr = m_d3d11Device.As(&dxgiDevice);
|
||||||
|
if (FAILED(hr) || dxgiDevice == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID3D11Device::QueryInterface(IDXGIDevice)", hr);
|
||||||
|
ReleaseInteropState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), m_d2dDevice.ReleaseAndGetAddressOf());
|
||||||
|
if (FAILED(hr) || m_d2dDevice == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID2D1Factory1::CreateDevice", hr);
|
||||||
|
ReleaseInteropState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = m_d2dDevice->CreateDeviceContext(
|
||||||
|
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
|
||||||
|
m_d2dDeviceContext.ReleaseAndGetAddressOf());
|
||||||
|
if (FAILED(hr) || m_d2dDeviceContext == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID2D1Device::CreateDeviceContext", hr);
|
||||||
|
ReleaseInteropState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = m_d2dDeviceContext->CreateSolidColorBrush(
|
||||||
|
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
|
||||||
|
m_interopBrush.ReleaseAndGetAddressOf());
|
||||||
|
if (FAILED(hr) || m_interopBrush == nullptr) {
|
||||||
|
m_lastError = HrToInteropString("ID2D1DeviceContext::CreateSolidColorBrush", hr);
|
||||||
|
ReleaseInteropState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_d2dDeviceContext->SetDpi(96.0f, 96.0f);
|
||||||
|
m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||||
|
m_lastError.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void D3D12WindowInteropContext::ReleaseInteropState() {
|
||||||
|
ReleaseBackBufferTargets();
|
||||||
|
m_interopBrush.Reset();
|
||||||
|
m_d2dDeviceContext.Reset();
|
||||||
|
m_d2dDevice.Reset();
|
||||||
|
m_d3d11On12Device.Reset();
|
||||||
|
m_d3d11DeviceContext.Reset();
|
||||||
|
m_d3d11Device.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
79
new_editor/app/Host/D3D12WindowInteropContext.h
Normal file
79
new_editor/app/Host/D3D12WindowInteropContext.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "D3D12WindowRenderer.h"
|
||||||
|
|
||||||
|
#include <XCEngine/UI/DrawData.h>
|
||||||
|
|
||||||
|
#include <d2d1_1.h>
|
||||||
|
#include <d3d11_4.h>
|
||||||
|
#include <d3d11on12.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
|
class D3D12WindowInteropContext {
|
||||||
|
public:
|
||||||
|
bool Attach(D3D12WindowRenderer& windowRenderer, ID2D1Factory1& d2dFactory);
|
||||||
|
void Detach();
|
||||||
|
void ReleaseBackBufferTargets();
|
||||||
|
bool RebuildBackBufferTargets();
|
||||||
|
bool HasAttachedWindowRenderer() const;
|
||||||
|
bool HasBackBufferTargets() const;
|
||||||
|
bool PrepareSourceTextures(const ::XCEngine::UI::UIDrawData& drawData);
|
||||||
|
void ClearSourceTextures();
|
||||||
|
bool ResolveInteropBitmap(
|
||||||
|
const ::XCEngine::UI::UITextureHandle& texture,
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1Bitmap>& outBitmap) const;
|
||||||
|
|
||||||
|
D3D12WindowRenderer* GetWindowRenderer() const;
|
||||||
|
ID3D11On12Device* GetD3D11On12Device() const;
|
||||||
|
ID3D11DeviceContext* GetD3D11DeviceContext() const;
|
||||||
|
ID2D1DeviceContext* GetD2DDeviceContext() const;
|
||||||
|
ID2D1SolidColorBrush* GetInteropBrush() const;
|
||||||
|
void BuildAcquiredResources(
|
||||||
|
std::uint32_t backBufferIndex,
|
||||||
|
std::vector<ID3D11Resource*>& outResources) const;
|
||||||
|
ID3D11Resource* GetWrappedBackBufferResource(std::uint32_t index) const;
|
||||||
|
ID2D1Bitmap1* GetBackBufferTargetBitmap(std::uint32_t index) const;
|
||||||
|
std::uint32_t GetCurrentBackBufferIndex() const;
|
||||||
|
const std::string& GetLastError() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct BackBufferTarget {
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1Bitmap1> targetBitmap = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SourceTextureResource {
|
||||||
|
std::uintptr_t key = 0u;
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Resource> wrappedResource = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1Bitmap1> bitmap = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool EnsureInterop();
|
||||||
|
void ReleaseInteropState();
|
||||||
|
|
||||||
|
D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||||
|
ID2D1Factory1* m_d2dFactory = nullptr;
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Device> m_d3d11Device = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_d3d11DeviceContext = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11On12Device> m_d3d11On12Device = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dDeviceContext = {};
|
||||||
|
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_interopBrush = {};
|
||||||
|
std::vector<BackBufferTarget> m_backBufferTargets = {};
|
||||||
|
std::vector<SourceTextureResource> m_activeSourceTextures = {};
|
||||||
|
std::unordered_map<std::uintptr_t, Microsoft::WRL::ComPtr<ID2D1Bitmap1>> m_activeBitmaps = {};
|
||||||
|
std::string m_lastError = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
@@ -2,57 +2,140 @@
|
|||||||
|
|
||||||
namespace XCEngine::UI::Editor::Host {
|
namespace XCEngine::UI::Editor::Host {
|
||||||
|
|
||||||
bool RenderD3D12WindowFrame(
|
D3D12WindowRenderLoopAttachResult D3D12WindowRenderLoop::Attach(
|
||||||
D3D12WindowRenderer& windowRenderer,
|
NativeRenderer& uiRenderer,
|
||||||
const float clearColor[4],
|
D3D12WindowRenderer& windowRenderer) {
|
||||||
const D3D12WindowRenderCallback& beforePresent,
|
m_uiRenderer = &uiRenderer;
|
||||||
const D3D12WindowRenderCallback& afterPresent) {
|
m_windowRenderer = &windowRenderer;
|
||||||
const ::XCEngine::Rendering::RenderSurface* renderSurface =
|
|
||||||
windowRenderer.GetCurrentRenderSurface();
|
D3D12WindowRenderLoopAttachResult result = {};
|
||||||
::XCEngine::Rendering::RenderContext renderContext =
|
result.hasViewportSurfacePresentation = m_uiRenderer->AttachWindowRenderer(*m_windowRenderer);
|
||||||
windowRenderer.GetRenderContext();
|
if (!result.hasViewportSurfacePresentation) {
|
||||||
if (!renderContext.IsValid() ||
|
const std::string& interopError = m_uiRenderer->GetLastRenderError();
|
||||||
renderContext.commandList == nullptr ||
|
result.interopWarning = interopError.empty()
|
||||||
renderContext.commandQueue == nullptr ||
|
? "native renderer d3d12 interop unavailable; falling back to hwnd renderer."
|
||||||
windowRenderer.GetSwapChain() == nullptr ||
|
: "native renderer d3d12 interop unavailable; falling back to hwnd renderer: " +
|
||||||
renderSurface == nullptr) {
|
interopError;
|
||||||
return false;
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* d3d12CommandList =
|
void D3D12WindowRenderLoop::Detach() {
|
||||||
static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList);
|
if (m_uiRenderer != nullptr) {
|
||||||
if (d3d12CommandList == nullptr) {
|
m_uiRenderer->DetachWindowRenderer();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& colorAttachments = renderSurface->GetColorAttachments();
|
m_uiRenderer = nullptr;
|
||||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
m_windowRenderer = nullptr;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::XCEngine::RHI::RHIResourceView* renderTargetView = colorAttachments[0];
|
D3D12WindowRenderLoopFrameContext D3D12WindowRenderLoop::BeginFrame() const {
|
||||||
renderContext.commandList->TransitionBarrier(
|
D3D12WindowRenderLoopFrameContext context = {};
|
||||||
renderTargetView,
|
if (!HasViewportSurfacePresentation()) {
|
||||||
::XCEngine::RHI::ResourceStates::Present,
|
return context;
|
||||||
::XCEngine::RHI::ResourceStates::RenderTarget);
|
|
||||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
|
||||||
renderContext.commandList->ClearRenderTarget(renderTargetView, clearColor);
|
|
||||||
|
|
||||||
if (beforePresent) {
|
|
||||||
beforePresent(renderContext, *renderSurface);
|
|
||||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (afterPresent) {
|
if (!m_windowRenderer->BeginFrame()) {
|
||||||
afterPresent(renderContext, *renderSurface);
|
const std::string& frameError = m_windowRenderer->GetLastError();
|
||||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
context.warning = frameError.empty()
|
||||||
|
? "d3d12 frame begin failed"
|
||||||
|
: "d3d12 frame begin failed: " + frameError;
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContext.commandList->TransitionBarrier(
|
context.canRenderViewports = true;
|
||||||
renderTargetView,
|
context.renderContext = m_windowRenderer->GetRenderContext();
|
||||||
::XCEngine::RHI::ResourceStates::RenderTarget,
|
return context;
|
||||||
::XCEngine::RHI::ResourceStates::Present);
|
}
|
||||||
return windowRenderer.SubmitFrame(true);
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_uiRenderer->Resize(width, height);
|
||||||
|
const bool hadViewportSurfacePresentation = m_uiRenderer->HasAttachedWindowRenderer();
|
||||||
|
if (hadViewportSurfacePresentation) {
|
||||||
|
m_uiRenderer->ReleaseWindowRendererBackBufferTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace XCEngine::UI::Editor::Host
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user