diff --git a/Engine/Engine.vcxproj b/Engine/Engine.vcxproj index 2bafe54..6ec8eea 100644 --- a/Engine/Engine.vcxproj +++ b/Engine/Engine.vcxproj @@ -37,6 +37,7 @@ + @@ -61,6 +62,7 @@ + diff --git a/Engine/Engine.vcxproj.filters b/Engine/Engine.vcxproj.filters index fa395f0..efcf7a7 100644 --- a/Engine/Engine.vcxproj.filters +++ b/Engine/Engine.vcxproj.filters @@ -30,6 +30,7 @@ + @@ -46,6 +47,7 @@ + diff --git a/Engine/Graphics/Direct3D12/D3D12Core.cpp b/Engine/Graphics/Direct3D12/D3D12Core.cpp index 0249e53..dc7ef5f 100644 --- a/Engine/Graphics/Direct3D12/D3D12Core.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Core.cpp @@ -1,10 +1,12 @@ #include "D3D12Core.h" -#include "D3D12CommonHeader.h" #include "D3D12Resources.h" +#include "D3D12Surface.h" using namespace Microsoft::WRL; namespace XEngine::graphics::d3d12::core { namespace { +using suface_collection = utl::free_list; + /** * @brief D3D12命令管理类设计说明 * @details 本类采用RAII设计模式封装Direct3D 12的命令队列和命令列表,提供类型安全的GPU命令提交机制 @@ -249,6 +251,9 @@ IDXGIFactory7* dxgi_factory{ nullptr }; */ d3d12_command gfx_command; +suface_collection surfaces; + + /** @@ -531,25 +536,6 @@ shutdown() release(main_device); } -void -render() -{ - // 等待GPU完成命令列表,并重置命令分配器和命令列表 - gfx_command.begin_frame(); - ID3D12GraphicsCommandList6* cmd_list{ gfx_command.command_list() }; - - const u32 frame_index{ current_frame_index() }; - if(deferred_release_flag[frame_index]) - { - process_deferred_release(frame_index); - } - // 记录命令 - // - // 完成命令记录,立即提交命令列表到命令队列执行 - // 为下一帧标记并增加围栏值 - gfx_command.end_frame(); -} - ID3D12Device *const device() { @@ -583,5 +569,65 @@ set_deferred_release_flag() deferred_release_flag[current_frame_index()] = 1; } +#pragma region surface + +surface +create_surface(platform::window window) +{ + surface_id id{ surfaces.add(window) }; + surfaces[id].create_swap_chain(dxgi_factory,gfx_command.command_queue(),render_target_format); + return surface{id}; +} +void +remove_surface(surface_id id) +{ + gfx_command.flush(); + surfaces.remove(id); +} +void +resize_surface(surface_id id, u32 width, u32 height) +{ + gfx_command.flush(); + surfaces[id].resize(); +} +u32 +surface_width(surface_id id) +{ + return surfaces[id].width(); +} +u32 +surface_height(surface_id id) +{ + return surfaces[id].height(); +} +void +render_surface(surface_id id) +{ + // 等待GPU完成命令列表,并重置命令分配器和命令列表 + gfx_command.begin_frame(); + ID3D12GraphicsCommandList6* cmd_list{ gfx_command.command_list() }; + + const u32 frame_index{ current_frame_index() }; + if(deferred_release_flag[frame_index]) + { + process_deferred_release(frame_index); + } + + // 呈现交换链 + surfaces[id].present(); + + + // 记录命令 + // + // 完成命令记录,立即提交命令列表到命令队列执行 + // 为下一帧标记并增加围栏值 + gfx_command.end_frame(); +} + + + +#pragma endregion + + }// namespace XEngine::graphics::d3d12::core diff --git a/Engine/Graphics/Direct3D12/D3D12Core.h b/Engine/Graphics/Direct3D12/D3D12Core.h index c7b5825..d91c8a0 100644 --- a/Engine/Graphics/Direct3D12/D3D12Core.h +++ b/Engine/Graphics/Direct3D12/D3D12Core.h @@ -22,12 +22,6 @@ bool initialize(); * @details 调用 Direct3D 12 设备的关闭函数,释放所有资源 */ void shutdown(); -/** - * @brief 渲染 Direct3D 12 核心功能 - * @details 调用 Direct3D 12 设备的渲染函数,渲染当前渲染表面 - */ -void render(); - /** * @brief 立即释放 DirectX COM 对象并将指针置空 @@ -104,6 +98,12 @@ DXGI_FORMAT default_render_target_format(); */ void set_deferred_release_flag(); +surface create_surface(platform::window window); +void remove_surface(surface_id id); +void resize_surface(surface_id id, u32 width, u32 height); +u32 surface_width(surface_id id); +u32 surface_height(surface_id id); +void render_surface(surface_id id); }// namespace XEngine::graphics::d3d12::core \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Helpers.h b/Engine/Graphics/Direct3D12/D3D12Helpers.h new file mode 100644 index 0000000..fa16f1d --- /dev/null +++ b/Engine/Graphics/Direct3D12/D3D12Helpers.h @@ -0,0 +1,14 @@ +#pragma once +#include "D3D12CommonHeader.h" +namespace XEngine::graphics::d3d12::d3dx +{ +constexpr struct{ + D3D12_HEAP_PROPERTIES default_heap{ + D3D12_HEAP_TYPE_DEFAULT, // 堆类型:默认堆,GPU可读写,CPU不可直接访问。选项:D3D12_HEAP_TYPE_DEFAULT(默认堆)、D3D12_HEAP_TYPE_UPLOAD(上传堆,CPU写入GPU读取)、D3D12_HEAP_TYPE_READBACK(回读堆,GPU写入CPU读取)、D3D12_HEAP_TYPE_CUSTOM(自定义堆) + D3D12_CPU_PAGE_PROPERTY_UNKNOWN, // CPU页属性:未知,使用默认设置。选项:D3D12_CPU_PAGE_PROPERTY_UNKNOWN(未知)、D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE(不可用)、D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE(写入合并)、D3D12_CPU_PAGE_PROPERTY_WRITE_BACK(写回) + D3D12_MEMORY_POOL_UNKNOWN, // 内存池:未知,由驱动自动选择。选项:D3D12_MEMORY_POOL_UNKNOWN(未知)、D3D12_MEMORY_POOL_L0(系统内存池)、D3D12_MEMORY_POOL_L1(显存池) + 0, // 节点掩码:单GPU系统设为0,多GPU系统中指定使用哪个GPU节点 + 0, // 保留字段:必须为0,为将来扩展预留 + }; +}heap_properties; +} \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Interface.cpp b/Engine/Graphics/Direct3D12/D3D12Interface.cpp index 0dc2671..e175a32 100644 --- a/Engine/Graphics/Direct3D12/D3D12Interface.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Interface.cpp @@ -7,7 +7,14 @@ void get_platform_interface(platform_interface& pi) { pi.initialize = core::initialize; pi.shutdown = core::shutdown; - pi.render = core::render; + + + pi.surface.create = core::create_surface; + pi.surface.remove = core::remove_surface; + pi.surface.resize = core::resize_surface; + pi.surface.width = core::surface_width; + pi.surface.height = core::surface_height; + pi.surface.render = core::render_surface; } }// namespace XEngine::graphics::d3d12 diff --git a/Engine/Graphics/Direct3D12/D3D12Resource.cpp b/Engine/Graphics/Direct3D12/D3D12Resource.cpp index 9b73b65..fc39172 100644 --- a/Engine/Graphics/Direct3D12/D3D12Resource.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Resource.cpp @@ -1,8 +1,11 @@ #include "D3D12Resources.h" #include "D3D12Core.h" +#include "D3D12Helpers.h" namespace XEngine::graphics::d3d12{ //////////// DESCRIPTOR HEAP //////////// + + // 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程, // 因此需要同步机制保护内部数据结构 bool @@ -134,4 +137,63 @@ descriptor_heap::free(descriptor_handle handle) handle = {}; } +//////////// D3D12 TEXTURE //////////// + +d3d12_texture::d3d12_texture( d3d12_texture_init_info info) +{ + auto *const device {core::device()}; + assert(device); + + D3D12_CLEAR_VALUE *const clear_value{ + (info.desc && + (info.desc->Flags &D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET || + info.desc && info.desc->Flags &D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) + ? &info.clear_value : nullptr + }; + + if(info.resource) + { + _resource = info.resource; + } + else if(info.heap && info.desc) + { + assert(!info.resource); + DXCall(device->CreatePlacedResource( + info.heap, + info.allocation_info.Offset, + info.desc, + info.initial_state, + clear_value, + IID_PPV_ARGS(&_resource) + )); + } + else if (info.desc) + { + assert(!info.srv_desc); + + DXCall(device->CreateCommittedResource( + &d3dx::heap_properties.default_heap, + D3D12_HEAP_FLAG_NONE, + info.desc, + info.initial_state, + clear_value, + IID_PPV_ARGS(&_resource) + )); + } + + assert(_resource); + _srv = core::srv_heap().allocate(); + device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu); +} + +void +d3d12_texture::release() +{ + core::srv_heap().free(_srv); + core::deferred_release(_resource); +} + +//////////// D3D12 RENDER TEXTURE //////////// + + } //XEngine::graphics::d3d12 \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Resources.h b/Engine/Graphics/Direct3D12/D3D12Resources.h index 533d928..5d317c0 100644 --- a/Engine/Graphics/Direct3D12/D3D12Resources.h +++ b/Engine/Graphics/Direct3D12/D3D12Resources.h @@ -417,4 +417,64 @@ private: const D3D12_DESCRIPTOR_HEAP_TYPE _type; }; +struct d3d12_texture_init_info +{ + ID3D12Heap1* heap{nullptr}; + ID3D12Resource* resource{nullptr}; + D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; + D3D12_RESOURCE_DESC* desc{nullptr}; + D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; + D3D12_RESOURCE_STATES initial_state{}; + D3D12_CLEAR_VALUE clear_value{}; +}; + + +class d3d12_texture +{ +public: + constexpr static u32 max_mips{ 14 }; + d3d12_texture() = default; + explicit d3d12_texture(d3d12_texture_init_info info); + DISABLE_COPY(d3d12_texture); + constexpr d3d12_texture(d3d12_texture&& o) + : _resource(o._resource), _srv(o._srv) //这些值只是指针和句柄,不需要move + { + o.reset(); + } + + constexpr d3d12_texture& operator=(d3d12_texture&& o) + { + assert(this != &o); + if(this != &o) + { + release(); + move(o); + } + return *this; + } + + void release(); + constexpr ID3D12Resource *const resource() const { return _resource; } + constexpr descriptor_handle srv() const { return _srv; } + +private: + constexpr void move(d3d12_texture& o) + { + _resource = o._resource; + _srv = o._srv; + o.reset(); + } + + constexpr void reset() + { + _resource = nullptr; + _srv = {}; + } + + ID3D12Resource* _resource{nullptr}; + descriptor_handle _srv; +}; + + + } // namespace XEngine::graphics::d3d12 diff --git a/Engine/Graphics/Direct3D12/D3D12Surface.cpp b/Engine/Graphics/Direct3D12/D3D12Surface.cpp index f5a816b..9fcebbb 100644 --- a/Engine/Graphics/Direct3D12/D3D12Surface.cpp +++ b/Engine/Graphics/Direct3D12/D3D12Surface.cpp @@ -2,113 +2,137 @@ #include "D3D12Core.h" -namespace XEngine::graphics::d3d12 { -namespace{ +namespace XEngine::graphics::d3d12 { +namespace { constexpr DXGI_FORMAT to_non_srgb(DXGI_FORMAT format) { - if(format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) return DXGI_FORMAT_R8G8B8A8_UNORM; - return format; + if (format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) return DXGI_FORMAT_R8G8B8A8_UNORM; + return format; } } // anonymous namespace void d3d12_surface::create_swap_chain(IDXGIFactory7* factory, ID3D12CommandQueue* cmd_queue, DXGI_FORMAT format) { - assert(!factory && cmd_queue); + assert(factory && cmd_queue); - // 应为可以多次调用,所以需要先释放旧的 swap chain - release(); + // 应为可以多次调用,所以需要先释放旧的 swap chain + release(); - DXGI_SWAP_CHAIN_DESC1 desc{}; - desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Alpha通道模式(窗口透明度相关) - desc.BufferCount = frame_buffer_count; // 后台缓冲区数量(用于双缓冲/多缓冲) - desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 缓冲区用途(作为渲染目标输出) - desc.Flags = 0; // 交换链标志位(无) - desc.Format = to_non_srgb(format); // 像素格式(转换为非SRGB格式) - desc.Height = _window.height(); // 交换链高度(与窗口高度一致) - desc.Width = _window.width(); // 交换链宽度(与窗口宽度一致) - desc.SampleDesc.Count = 1; // 多重采样数量(1表示禁用MSAA) - desc.SampleDesc.Quality = 0; // 多重采样质量等级(0表示禁用) - desc.Scaling = DXGI_SCALING_STRETCH; // 缓冲区缩放模式(窗口大小变化时的处理方式) - desc.Stereo = false; // 立体显示模式(3D显示) + if (SUCCEEDED(factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &_allow_tearing, sizeof(u32))) && _allow_tearing) + { + _present_flags = DXGI_PRESENT_ALLOW_TEARING; + } - IDXGISwapChain1* swap_chain; - HWND hwnd {(HWND)_window.handle()}; - // 为窗口创建交换链,指定交换链描述结构体 - DXCall(factory->CreateSwapChainForHwnd(cmd_queue, hwnd, &desc, nullptr, nullptr, &swap_chain)); - // 控制 DXGI 如何响应特定的系统按键,阻止 DXGI 响应 Alt+Enter 按键序列,由应用程序手动处理全屏切换逻辑。 - DXCall(factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER)); - // 将局部变量 swap_chain 创建的交换链对象“转移”给类成员 _swap_chain 来长期持有。 - // 免了直接赋值(_swap_chain = swap_chain)可能导致的引用计数问题:直接赋值不增加 - // 计数,后续释放 swap_chain 会使对象计数归零而被销毁,_swap_chain 就会变成悬空指针。 - DXCall(swap_chain->QueryInterface(IID_PPV_ARGS(&_swap_chain))); - core::release(swap_chain); + DXGI_SWAP_CHAIN_DESC1 desc{}; + desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Alpha通道模式(窗口透明度相关) + desc.BufferCount = buffer_count; // 后台缓冲区数量(用于双缓冲/多缓冲) + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 缓冲区用途(作为渲染目标输出) + desc.Flags = _allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; // 交换链标志位(无) + desc.Format = to_non_srgb(format); // 像素格式(转换为非SRGB格式) + desc.Height = _window.height(); // 交换链高度(与窗口高度一致) + desc.Width = _window.width(); // 交换链宽度(与窗口宽度一致) + desc.SampleDesc.Count = 1; // 多重采样数量(1表示禁用MSAA) + desc.SampleDesc.Quality = 0; // 多重采样质量等级(0表示禁用) + desc.Scaling = DXGI_SCALING_STRETCH; // 缓冲区缩放模式(窗口大小变化时的处理方式) + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 交换链效果(翻转并丢弃旧缓冲区) + desc.Stereo = false; // 立体显示模式(3D显示) - _current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); + IDXGISwapChain1* swap_chain; + HWND hwnd{ (HWND)_window.handle() }; + // 为窗口创建交换链,指定交换链描述结构体 + DXCall(factory->CreateSwapChainForHwnd(cmd_queue, hwnd, &desc, nullptr, nullptr, &swap_chain)); + // 控制 DXGI 如何响应特定的系统按键,阻止 DXGI 响应 Alt+Enter 按键序列,由应用程序手动处理全屏切换逻辑。 + DXCall(factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER)); + // 将局部变量 swap_chain 创建的交换链对象“转移”给类成员 _swap_chain 来长期持有。 + // 免了直接赋值(_swap_chain = swap_chain)可能导致的引用计数问题:直接赋值不增加 + // 计数,后续释放 swap_chain 会使对象计数归零而被销毁,_swap_chain 就会变成悬空指针。 + DXCall(swap_chain->QueryInterface(IID_PPV_ARGS(&_swap_chain))); + core::release(swap_chain); - for(u32 i = 0; i < frame_buffer_count; ++i) - { - _render_target_data[i].rtv = core::rtv_heap().allocate(); - } + _current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); - finalize(); + for (u32 i = 0; i < buffer_count; ++i) + { + _render_target_data[i].rtv = core::rtv_heap().allocate(); + } + + finalize(); } void d3d12_surface::present() const { - assert(_swap_chain); - // 设置是否使用垂直同步 - // 0 表示不使用垂直同步,1 表示使用垂直同步 - // 第二个参数设置有无特殊行为,0 表示无特殊行为 - DXCall(_swap_chain->Present(0, 0)); - _current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); + assert(_swap_chain); + // 设置是否使用垂直同步 + // 0 表示不使用垂直同步,1 表示使用垂直同步 + // 第二个参数设置有无特殊行为,0 表示无特殊行为 + DXCall(_swap_chain->Present(0, _present_flags)); + _current_bb_index = _swap_chain->GetCurrentBackBufferIndex(); } void d3d12_surface::finalize() { - // 为每个缓冲区创建渲染目标视图 - for(u32 i = 0; i < frame_buffer_count; ++i) - { - render_target_data& data{ _render_target_data[i] }; - assert(!data.resource); - DXCall(_swap_chain->GetBuffer(i, IID_PPV_ARGS(&data.resource))); - D3D12_RENDER_TARGET_VIEW_DESC desc{}; - desc.Format = core::default_render_target_format(); - desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; - core::device()->CreateRenderTargetView(data.resource, &desc, data.rtv.cpu); - } + // 为每个缓冲区创建渲染目标视图 + for (u32 i = 0; i < buffer_count; ++i) + { + render_target_data& data{ _render_target_data[i] }; + assert(!data.resource); + DXCall(_swap_chain->GetBuffer(i, IID_PPV_ARGS(&data.resource))); + D3D12_RENDER_TARGET_VIEW_DESC desc{}; + desc.Format = core::default_render_target_format(); + desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + core::device()->CreateRenderTargetView(data.resource, &desc, data.rtv.cpu); + } - DXGI_SWAP_CHAIN_DESC desc{}; - DXCall(_swap_chain->GetDesc(&desc)); - const u32 width{ desc.BufferDesc.Width }; - const u32 height{ desc.BufferDesc.Height }; - assert(_window.width() == width && _window.height() == height); + DXGI_SWAP_CHAIN_DESC desc{}; + DXCall(_swap_chain->GetDesc(&desc)); + const u32 width{ desc.BufferDesc.Width }; + const u32 height{ desc.BufferDesc.Height }; + assert(_window.width() == width && _window.height() == height); - _viewport.TopLeftX = 0.0f; - _viewport.TopLeftY = 0.0f; - _viewport.Width = (float)width; - _viewport.Height = (float)height; - _viewport.MaxDepth = 1.0f; - _viewport.MinDepth = 0.0f; + _viewport.TopLeftX = 0.0f; + _viewport.TopLeftY = 0.0f; + _viewport.Width = (float)width; + _viewport.Height = (float)height; + _viewport.MaxDepth = 1.0f; + _viewport.MinDepth = 0.0f; - _scissor_rect = {0, 0, (s32)width, (s32)height}; + _scissor_rect = { 0, 0, (s32)width, (s32)height }; } void d3d12_surface::release() { - for(u32 i = 0; i < frame_buffer_count; ++i) - { - render_target_data& data{ _render_target_data[i] }; - core::release(data.resource); - core::rtv_heap().free(data.rtv); - } + for (u32 i = 0; i < buffer_count; ++i) + { + render_target_data& data{ _render_target_data[i] }; + core::release(data.resource); + core::rtv_heap().free(data.rtv); - core::release(_swap_chain); + } + core::release(_swap_chain); } +void +d3d12_surface::resize() +{ +} + +u32 +d3d12_surface::width() +{ + return _window.width(); +} + +u32 +d3d12_surface::height() +{ + return _window.height(); +} + + } // namespace XEngine::graphics::d3d12 \ No newline at end of file diff --git a/Engine/Graphics/Direct3D12/D3D12Surface.h b/Engine/Graphics/Direct3D12/D3D12Surface.h index 0fd8131..a730ebb 100644 --- a/Engine/Graphics/Direct3D12/D3D12Surface.h +++ b/Engine/Graphics/Direct3D12/D3D12Surface.h @@ -69,7 +69,61 @@ public: * @details * 调用 release() 清理交换链和渲染目标资源。 */ - ~d3d12_surface(){release();} + ~d3d12_surface(){ release(); } + + + constexpr static u32 buffer_count{ 3 }; + +#if USE_STL_VECTOR + DISABLE_COPY(d3d12_surface) + + /** + * @brief 移动构造函数 + * + * @param o 要移动的源对象 + * + * @details + * 转移所有资源所有权,将源对象置为空状态。 + * 用于 vector 扩容时安全转移资源。 + */ + constexpr d3d12_surface(d3d12_surface&& o) noexcept + : _swap_chain{o._swap_chain}, _window{o._window}, _current_bb_index{o._current_bb_index} + , _viewport{o._viewport}, _scissor_rect{o._scissor_rect} + , _allow_tearing{o._allow_tearing}, _present_flags{o._present_flags} + { + for (u32 i = 0; i < frame_buffer_count; ++i) + { + _render_target_data[i].resource = o._render_target_data[i].resource; + _render_target_data[i].rtv = o._render_target_data[i].rtv; + } + + o.reset(); + } + + /** + * @brief 移动赋值运算符 + * + * @param o 要移动的源对象 + * @return 当前对象引用 + * + * @details + * 先释放当前资源,再转移所有权。 + */ + constexpr d3d12_surface& operator=(d3d12_surface&& o) noexcept + { + assert(this != &o); + if (this != &o) + { + release(); + move(o); + } + return *this; + } + +#else + DISABLE_COPY_AND_MOVE(d3d12_surface); +#endif + /** * @brief 创建交换链和渲染目标视图 @@ -167,6 +221,43 @@ public: constexpr const D3D12_RECT& scissor_rect() const {return _scissor_rect;} private: + +#if USE_STL_VECTOR + constexpr void move(d3d12_surface& o) + { + _swap_chain = o._swap_chain; + for (u32 i = 0; i < frame_buffer_count; ++i) + { + _render_target_data[i] = o._render_target_data[i]; + } + _window = o._window; + _current_bb_index = o._current_bb_index; + _viewport = o._viewport; + _scissor_rect = o._scissor_rect; + _allow_tearing = o._allow_tearing; + _present_flags = o._present_flags; + + o.reset(); + } + + + constexpr void reset() + { + _swap_chain = nullptr; + for (u32 i = 0; i < frame_buffer_count; ++i) + { + _render_target_data[i] = {}; + } + _window = {}; + _current_bb_index = 0; + _viewport = {}; + _scissor_rect = {}; + _allow_tearing = 0; + _present_flags = 0; + } + +#endif + /** * @brief 释放所有资源 * @@ -200,11 +291,13 @@ private: }; IDXGISwapChain4* _swap_chain{nullptr}; ///< DXGI 交换链对象 - render_target_data _render_target_data[frame_buffer_count];///< 后台缓冲区数据数组 + render_target_data _render_target_data[buffer_count]; ///< 后台缓冲区数据数组 platform::window _window{}; ///< 关联的平台窗口 mutable u32 _current_bb_index{0}; ///< 当前后台缓冲区索引 D3D12_VIEWPORT _viewport{}; ///< 视口描述 D3D12_RECT _scissor_rect{}; ///< 裁剪矩形 + u32 _allow_tearing{ 0 }; ///< 是否允许撕裂 + u32 _present_flags{ 0 }; ///< 呈现标志位 }; } diff --git a/Engine/Graphics/GraphicsPlatformInterface.h b/Engine/Graphics/GraphicsPlatformInterface.h index a293de7..4c98762 100644 --- a/Engine/Graphics/GraphicsPlatformInterface.h +++ b/Engine/Graphics/GraphicsPlatformInterface.h @@ -11,7 +11,6 @@ namespace XEngine::graphics{ struct platform_interface{ bool(*initialize)(void); void(*shutdown)(void); - void(*render)(void); struct { surface(*create)(platform::window); diff --git a/Engine/Graphics/Renderer.cpp b/Engine/Graphics/Renderer.cpp index 634c53e..e45ed28 100644 --- a/Engine/Graphics/Renderer.cpp +++ b/Engine/Graphics/Renderer.cpp @@ -51,12 +51,6 @@ shutdown() gfx.shutdown(); } -void -render() -{ - gfx.render(); -} - surface create_surface(platform::window window) { diff --git a/Engine/Graphics/Renderer.h b/Engine/Graphics/Renderer.h index c977901..6b3fb33 100644 --- a/Engine/Graphics/Renderer.h +++ b/Engine/Graphics/Renderer.h @@ -91,12 +91,6 @@ bool initialize(graphics_platform platform); */ void shutdown(); -/** - * @brief 渲染调用接口 - * @details 调用渲染函数指针,渲染当前渲染表面 - */ -void render(); - /** * @brief 创建渲染表面 * @details window 渲染表面的窗口 diff --git a/Engine/Utilities/FreeList.h b/Engine/Utilities/FreeList.h index 0a934c6..08cc64d 100644 --- a/Engine/Utilities/FreeList.h +++ b/Engine/Utilities/FreeList.h @@ -1,11 +1,97 @@ /** * @file FreeList.h - * @brief 带复用槽位的稀疏对象容器模板。 + * @brief 带槽位复用机制的稀疏对象容器模板 + * * @details - * free_list 维护“已使用槽位 + 空闲链”结构,支持: - * - O(1) 近似开销的新增与删除; - * - 删除后槽位复用,降低频繁分配释放成本; - * - 以索引作为外部句柄,与 id 系统协同使用。 + * free_list 是一种特殊的容器,维护"已使用槽位 + 空闲链表"结构, + * 特别适合需要频繁增删元素且使用索引作为外部句柄的场景。 + * + * ## 核心特性 + * + * - **O(1) 增删操作**:新增和删除操作近似常数时间复杂度 + * - **槽位复用**:删除后的槽位会被回收并重新利用,避免频繁内存分配 + * - **索引句柄**:使用 u32 索引作为外部句柄,与 id 系统完美配合 + * - **内存紧凑**:底层使用连续内存,缓存友好 + * + * ## 数据结构原理 + * + * ``` + * 初始状态: + * _array: [ 空 | 空 | 空 | 空 ] + * _next_free_index = invalid_id + * _size = 0 + * + * 添加元素 A、B、C 后: + * _array: [ A | B | C | 空 ] + * _next_free_index = invalid_id + * _size = 3 + * + * 删除元素 B 后(槽位 1 被标记为空闲): + * _array: [ A | ->2 | C | 空 ] // 槽位 1 存储下一个空闲索引 2 + * _next_free_index = 1 // 指向第一个空闲槽位 + * _size = 2 + * + * 再删除元素 A 后: + * _array: [ ->1 | ->2 | C | 空 ] // 槽位 0 存储下一个空闲索引 1 + * _next_free_index = 0 + * _size = 1 + * + * 添加新元素 D 时(复用槽位 0): + * _array: [ D | ->2 | C | 空 ] + * _next_free_index = 1 // 指向下一个空闲槽位 + * _size = 2 + * ``` + * + * ## 空闲链表机制 + * + * 删除元素时,该槽位被加入空闲链表: + * 1. 调用元素的析构函数 + * 2. 将当前 `_next_free_index` 值写入该槽位(作为链表下一个节点) + * 3. 更新 `_next_free_index` 指向该槽位 + * + * 添加元素时,优先从空闲链表获取槽位: + * 1. 如果 `_next_free_index` 有效,复用该槽位 + * 2. 从槽位中读取下一个空闲索引,更新 `_next_free_index` + * 3. 否则,在数组末尾追加新元素 + * + * ## 类型要求 + * + * 由于空闲槽位需要存储 u32 索引,要求: + * - `sizeof(T) >= sizeof(u32)`(至少 4 字节) + * + * ## 使用场景 + * + * - 游戏对象管理(实体 ID 系统) + * - 资源句柄管理(纹理、网格等) + * - 渲染表面管理(多窗口场景) + * - 任何需要稳定索引句柄的系统 + * + * ## 注意事项 + * + * - 删除后的索引可能被新元素复用,外部系统需要处理这种情况 + * - 不保证元素在内存中的顺序 + * - 与 std::vector 配合使用时可能触发重复构造(见 USE_STL_VECTOR 宏) + * + * @example + * @code + * utl::free_list objects; + * + * // 添加对象 + * u32 id1 = objects.add(player_data); + * u32 id2 = objects.add(enemy_data); + * + * // 访问对象 + * objects[id1].update(); + * + * // 删除对象(槽位被回收) + * objects.remove(id1); + * + * // 新对象可能复用 id1 的槽位 + * u32 id3 = objects.add(new_data); // id3 可能等于 id1 + * @endcode + * + * @see utl::vector + * @see DEFINE_TYPED_ID */ #pragma once #include "CommonHeader.h" @@ -13,23 +99,42 @@ namespace XEngine::utl { #if USE_STL_VECTOR -#pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!"); +#pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!") #endif +/** + * @class free_list + * @brief 带槽位复用机制的稀疏对象容器 + * + * @tparam T 元素类型,必须满足 `sizeof(T) >= sizeof(u32)` + * + * @details + * 该容器使用空闲链表管理已删除的槽位,实现 O(1) 的增删操作。 + * 特别适合需要稳定索引句柄的场景,如游戏对象管理、资源句柄等。 + */ template class free_list { - static_assert(sizeof(T) >= sizeof(u32)); + static_assert(sizeof(T) >= sizeof(u32), "Type T must be at least 4 bytes to store free list indices"); public: /** - * @brief 构造空容器。 + * @brief 默认构造函数,创建空容器 + * + * @details + * 创建一个空的 free_list,不预分配任何内存。 + * 首次添加元素时会触发内存分配。 */ free_list() = default; /** - * @brief 预留底层存储容量。 - * @param count 预留元素数量。 + * @brief 预留容量的构造函数 + * + * @param count 预留的元素槽位数量 + * + * @details + * 预分配指定数量的槽位,避免后续添加元素时的多次扩容。 + * 适用于已知大概元素数量的场景。 */ explicit free_list(u32 count) { @@ -37,7 +142,11 @@ public: } /** - * @brief 销毁容器并校验无存活元素。 + * @brief 析构函数,销毁容器 + * + * @details + * 断言检查容器为空(_size == 0),确保所有元素已被正确移除。 + * 在 DEBUG 模式下使用 USE_STL_VECTOR 时,会清空内存以便检测悬空引用。 */ ~free_list() { @@ -49,10 +158,23 @@ public: } /** - * @brief 新增一个元素并返回槽位 ID。 - * @tparam params 构造参数类型。 - * @param p 构造参数。 - * @return 新元素 ID。 + * @brief 添加新元素并返回其槽位 ID + * + * @tparam params 构造参数类型包 + * @param p 传递给元素构造函数的参数 + * @return 新分配的槽位 ID(u32 索引) + * + * @details + * 添加元素的策略: + * 1. **有空闲槽位**:从空闲链表头部取出一个槽位复用 + * 2. **无空闲槽位**:在数组末尾追加新元素 + * + * 复用槽位时: + * - 从该槽位读取下一个空闲索引,更新 `_next_free_index` + * - 使用 placement new 在该槽位构造新元素 + * + * @note 返回的 ID 在元素被删除前保持有效 + * @note 删除后该 ID 可能被新元素复用 */ template constexpr u32 add(params&&... p) @@ -76,8 +198,19 @@ public: } /** - * @brief 删除指定 ID 的元素并回收到空闲链。 - * @param id 元素 ID。 + * @brief 移除指定 ID 的元素并回收其槽位 + * + * @param id 要移除元素的槽位 ID + * + * @details + * 移除元素的步骤: + * 1. 调用元素的析构函数 + * 2. DEBUG 模式下填充 0xcc 标记已删除 + * 3. 将当前 `_next_free_index` 写入该槽位(链表链接) + * 4. 更新 `_next_free_index` 指向该槽位 + * + * @pre id 必须是有效的已分配槽位 + * @pre 该槽位未被删除(不能重复删除) */ constexpr void remove(u32 id) { @@ -91,28 +224,35 @@ public: } /** - * @brief 获取当前有效元素数量。 - * @return 元素数量。 + * @brief 获取当前有效元素数量 + * + * @return 容器中有效元素的个数 + * + * @note 这与 capacity() 不同,size() 返回实际存储的元素数 */ constexpr u32 size() const { return _size; } - /** - * @brief 获取当前已分配槽位总数。 - * @return 容量值。 + * @brief 获取已分配槽位总数 + * + * @return 底层数组的大小(包括空闲槽位) + * + * @details + * 返回值 >= size(),因为可能存在已删除但未释放的槽位。 + * 这些槽位会在后续添加元素时被复用。 */ constexpr u32 capacity() const { return _array.size(); } - /** - * @brief 判断容器是否为空。 - * @return 空返回 true。 + * @brief 检查容器是否为空 + * + * @return 如果容器中没有有效元素则返回 true */ constexpr bool empty() const { @@ -120,9 +260,12 @@ public: } /** - * @brief 按 ID 访问元素。 - * @param id 元素 ID。 - * @return 元素引用。 + * @brief 通过 ID 访问元素(可修改) + * + * @param id 元素的槽位 ID + * @return 元素的引用 + * + * @pre id 必须有效且指向未删除的元素 */ [[nodiscard]] constexpr T& operator[](u32 id) { @@ -131,9 +274,12 @@ public: } /** - * @brief 按 ID 访问常量元素。 - * @param id 元素 ID。 - * @return 常量元素引用。 + * @brief 通过 ID 访问元素(只读) + * + * @param id 元素的槽位 ID + * @return 元素的常量引用 + * + * @pre id 必须有效且指向未删除的元素 */ [[nodiscard]] constexpr const T& operator[](u32 id) const { @@ -142,6 +288,18 @@ public: } private: + /** + * @brief 检查指定槽位是否已被删除 + * + * @param id 要检查的槽位 ID + * @return 如果槽位已被删除则返回 true + * + * @details + * 通过检查槽位内容是否被 0xcc 填充来判断是否已删除。 + * 仅在 sizeof(T) > sizeof(u32) 时有效,否则直接返回 true。 + * + * @note 仅用于断言检查,确保不会访问已删除的元素 + */ constexpr bool already_removed(u32 id) const { if constexpr (sizeof(T) > sizeof(u32)) diff --git a/Engine/Utilities/Vector.h b/Engine/Utilities/Vector.h index 0a2598d..9b4a94d 100644 --- a/Engine/Utilities/Vector.h +++ b/Engine/Utilities/Vector.h @@ -1,11 +1,66 @@ /** * @file Vector.h - * @brief 自定义动态数组容器实现。 + * @brief 自定义动态数组容器实现 + * * @details - * 该容器提供接近 std::vector 的常用能力,并支持可选析构策略: - * - 动态扩容、插入、删除与无序删除; - * - 连续内存访问与迭代器接口; - * - 在性能敏感场景下复用底层内存分配行为。 + * 该容器提供与 std::vector 相似的功能,同时支持可选的析构策略。 + * 设计目标是在性能敏感场景下提供更精细的内存管理控制。 + * + * ## 核心特性 + * + * - **动态扩容**:自动管理内存,支持预留容量避免频繁分配 + * - **连续内存**:元素在内存中连续存储,缓存友好 + * - **可选析构**:通过模板参数控制是否在删除时调用元素析构函数 + * - **迭代器支持**:提供 begin()/end() 支持范围 for 循环 + * + * ## 与 std::vector 的区别 + * + * | 特性 | utl::vector | std::vector | + * |------|-------------|-------------| + * | 析构控制 | 可选模板参数 | 始终析构 | + * | 内存分配 | realloc | allocator | + * | 无序删除 | erase_unordered | 无 | + * + * ## 析构策略 + * + * 模板参数 `destruct` 控制元素析构行为: + * - `destruct = true`(默认):删除元素时调用析构函数 + * - `destruct = false`:跳过析构,适用于 POD 类型或外部管理生命周期 + * + * ## 扩容策略 + * + * 当容量不足时,按 `(capacity + 1) * 1.5` 扩容: + * - 初始容量 0 → 1 + * - 容量 4 → 6 + * - 容量 10 → 15 + * + * ## 无序删除优化 + * + * `erase_unordered()` 通过将末尾元素移动到删除位置实现 O(1) 删除, + * 适用于元素顺序不重要的场景。 + * + * @example + * @code + * // 基本使用 + * utl::vector nums; + * nums.push_back(1); + * nums.push_back(2); + * nums.emplace_back(3); + * + * // 遍历 + * for (int& n : nums) { + * n *= 2; + * } + * + * // 无序删除(高效但不保证顺序) + * nums.erase_unordered(0); // 用末尾元素替换位置 0 + * + * // POD 类型优化(跳过析构) + * utl::vector vertices; + * vertices.resize(1000); // 不调用析构函数 + * @endcode + * + * @see utl::free_list */ #pragma once #include "CommonHeader.h" @@ -13,24 +68,40 @@ namespace XEngine::utl { /** - * @brief 自定义连续内存动态数组。 - * @tparam T 元素类型。 - * @tparam destruct 是否在删除时调用析构。 + * @class vector + * @brief 自定义连续内存动态数组 + * + * @tparam T 元素类型 + * @tparam destruct 是否在删除时调用析构函数,默认为 true + * + * @details + * 提供动态数组的完整功能,包括动态扩容、插入、删除等操作。 + * 通过模板参数控制析构行为,适用于不同性能需求场景。 */ template class vector { public: - // Default constructor. Doesn~t allocate memory. /** - * @brief 构造空容器。 + * @brief 默认构造函数,创建空容器 + * + * @details + * 创建一个空的 vector,不分配任何内存。 + * 首次添加元素时会触发内存分配。 */ vector() = default; /** - * @brief 构造并调整到指定元素数量。 - * @param count 目标元素数量。 + * @brief 构造并调整到指定元素数量 + * + * @param count 目标元素数量 + * + * @details + * 创建包含 count 个默认构造元素的容器。 + * 要求类型 T 必须可默认构造。 + * + * @pre T 必须满足 is_default_constructible */ constexpr explicit vector(u64 count) { @@ -38,20 +109,45 @@ public: } /** - * @brief 构造并填充指定数量元素。 - * @param count 目标元素数量。 - * @param value 填充值。 + * @brief 构造并填充指定数量元素 + * + * @param count 目标元素数量 + * @param value 用于填充的值 + * + * @details + * 创建包含 count 个元素副本的容器。 + * 要求类型 T 必须可拷贝构造。 + * + * @pre T 必须满足 is_copy_constructible */ constexpr explicit vector(u64 count, const T& value) { resize(count, value); } + /** + * @brief 拷贝构造函数 + * + * @param o 要拷贝的源容器 + * + * @details + * 创建源容器的深拷贝,包括所有元素。 + * 新容器具有独立的内存空间。 + */ constexpr vector(const vector& o) { *this = o; } + /** + * @brief 移动构造函数 + * + * @param o 要移动的源容器 + * + * @details + * 转移源容器的所有权,不进行元素拷贝。 + * 移动后源容器处于空状态。 + */ constexpr vector(vector&& o) :_capacity{o._capacity}, _size{o._size}, @@ -60,7 +156,15 @@ public: o.reset(); } - + /** + * @brief 拷贝赋值运算符 + * + * @param o 要拷贝的源容器 + * @return 当前容器的引用 + * + * @details + * 清空当前容器,然后深拷贝源容器的所有元素。 + */ constexpr vector& operator=(const vector& o) { assert(this != std::addressof(o)); @@ -78,6 +182,15 @@ public: return *this; } + /** + * @brief 移动赋值运算符 + * + * @param o 要移动的源容器 + * @return 当前容器的引用 + * + * @details + * 销毁当前容器的资源,然后转移源容器的所有权。 + */ constexpr vector& operator=(vector&& o) { assert(this != std::addressof(o)); @@ -90,11 +203,19 @@ public: return *this; } + /** + * @brief 析构函数,释放所有资源 + */ ~vector() { destroy(); } /** - * @brief 追加拷贝元素。 - * @param value 元素值。 + * @brief 在末尾追加元素的拷贝 + * + * @param value 要追加的元素值 + * + * @details + * 如果容量不足,会自动扩容。 + * 内部调用 emplace_back 实现。 */ constexpr void push_back(const T& value) { @@ -102,8 +223,12 @@ public: } /** - * @brief 追加右值元素。 - * @param value 元素值。 + * @brief 在末尾追加元素的右值引用 + * + * @param value 要追加的元素值(右值) + * + * @details + * 使用移动语义追加元素,避免不必要的拷贝。 */ constexpr void push_back(const T&& value) { @@ -111,17 +236,24 @@ public: } /** - * @brief 原位构造并追加元素。 - * @tparam params 构造参数类型。 - * @param p 构造参数。 - * @return 新元素引用。 + * @brief 原位构造并追加元素 + * + * @tparam params 构造参数类型包 + * @param p 传递给元素构造函数的参数 + * @return 新构造元素的引用 + * + * @details + * 在容器末尾原位构造新元素,避免额外的拷贝或移动操作。 + * 如果容量不足,按 1.5 倍扩容。 + * + * @note 扩容公式:`new_capacity = (old_capacity + 1) * 3 / 2` */ template constexpr decltype(auto) emplace_back(params&&... p) { if (_size == _capacity) { - reserve(((_capacity + 1) * 3) >> 1); // reserve 50% more + reserve(((_capacity + 1) * 3) >> 1); } assert(_size < _capacity); @@ -131,8 +263,15 @@ public: } /** - * @brief 调整元素数量,新增元素默认构造。 - * @param new_size 新大小。 + * @brief 调整元素数量,新增元素默认构造 + * + * @param new_size 目标元素数量 + * + * @details + * - 如果 new_size > 当前大小,在末尾默认构造新元素 + * - 如果 new_size < 当前大小,删除末尾多余元素 + * + * @pre T 必须满足 is_default_constructible */ constexpr void resize(u64 new_size) { @@ -161,11 +300,17 @@ public: assert(new_size == _size); } - /** - * @brief 调整元素数量,新增元素使用给定值填充。 - * @param new_size 新大小。 - * @param value 填充值。 + * @brief 调整元素数量,新增元素使用给定值填充 + * + * @param new_size 目标元素数量 + * @param value 用于填充新元素的值 + * + * @details + * - 如果 new_size > 当前大小,在末尾拷贝构造新元素 + * - 如果 new_size < 当前大小,删除末尾多余元素 + * + * @pre T 必须满足 is_copy_constructible */ constexpr void resize(u64 new_size, const T& value) { @@ -194,17 +339,21 @@ public: assert(new_size == _size); } - /** - * @brief 预留最小容量。 - * @param new_capacity 目标容量。 + * @brief 预留最小容量 + * + * @param new_capacity 目标容量 + * + * @details + * 如果 new_capacity > 当前容量,重新分配更大的内存块。 + * 使用 realloc 实现,会自动复制原有数据。 + * + * @note 此函数只会增加容量,不会减少 */ constexpr void reserve(u64 new_capacity) { if (new_capacity > _capacity) { - // NOTE: realoc() will automatically copy the data in the buffer - // if a new region of memory iss allocated. void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) }; assert(new_buffer); if (new_buffer) @@ -216,9 +365,16 @@ public: } /** - * @brief 删除指定下标元素并保持顺序。 - * @param index 删除下标。 - * @return 指向删除位置的指针。 + * @brief 删除指定下标元素并保持顺序 + * + * @param index 要删除元素的下标 + * @return 指向删除位置后一个元素的指针 + * + * @details + * 删除元素后,将后续所有元素向前移动一位。 + * 时间复杂度 O(n)。 + * + * @pre index 必须小于当前元素数量 */ constexpr T *const erase(u64 index) { @@ -227,9 +383,16 @@ public: } /** - * @brief 删除指定位置元素并保持顺序。 - * @param item 待删除元素指针。 - * @return 指向删除位置的指针。 + * @brief 删除指定位置元素并保持顺序 + * + * @param item 指向要删除元素的指针 + * @return 指向删除位置后一个元素的指针 + * + * @details + * 删除元素后,将后续所有元素向前移动一位。 + * 时间复杂度 O(n)。 + * + * @pre item 必须指向容器内的有效元素 */ constexpr T *const erase(T *const item) { @@ -247,9 +410,18 @@ public: } /** - * @brief 无序删除指定下标元素。 - * @param index 删除下标。 - * @return 指向删除位置的指针。 + * @brief 无序删除指定下标元素 + * + * @param index 要删除元素的下标 + * @return 指向删除位置的指针 + * + * @details + * 用末尾元素替换被删除元素,然后减少大小。 + * 时间复杂度 O(1),但不保证元素顺序。 + * + * @pre index 必须小于当前元素数量 + * + * @note 适用于元素顺序不重要的场景 */ constexpr T *const erase_unordered(u64 index) { @@ -258,9 +430,18 @@ public: } /** - * @brief 无序删除指定位置元素。 - * @param item 待删除元素指针。 - * @return 指向删除位置的指针。 + * @brief 无序删除指定位置元素 + * + * @param item 指向要删除元素的指针 + * @return 指向删除位置的指针 + * + * @details + * 用末尾元素替换被删除元素,然后减少大小。 + * 时间复杂度 O(1),但不保证元素顺序。 + * + * @pre item 必须指向容器内的有效元素 + * + * @note 适用于元素顺序不重要的场景 */ constexpr T *const erase_unordered(T *const item) { @@ -279,7 +460,11 @@ public: } /** - * @brief 清空容器中的有效元素。 + * @brief 清空容器中的所有元素 + * + * @details + * 调用所有元素的析构函数(如果 destruct = true), + * 然后将大小设为 0。不释放底层内存。 */ constexpr void clear() { @@ -290,10 +475,14 @@ public: _size = 0; } - /** - * @brief 与另一个容器交换内容。 - * @param o 目标容器。 + * @brief 与另一个容器交换内容 + * + * @param o 目标容器 + * + * @details + * 交换两个容器的所有权,不进行元素拷贝。 + * 时间复杂度 O(1)。 */ constexpr void swap(vector& o) { @@ -305,20 +494,20 @@ public: } } - /** - * @brief 返回底层数据指针。 - * @return 数据首地址。 + * @brief 返回底层数据指针 + * + * @return 指向第一个元素的指针 */ [[nodiscard]] constexpr T* data() { return _data; } - /** - * @brief 返回只读底层数据指针。 - * @return 数据首地址。 + * @brief 返回只读底层数据指针 + * + * @return 指向第一个元素的常量指针 */ [[nodiscard]] constexpr T *const data() const { @@ -326,8 +515,9 @@ public: } /** - * @brief 判断容器是否为空。 - * @return 空返回 true。 + * @brief 判断容器是否为空 + * + * @return 如果容器中没有元素则返回 true */ [[nodiscard]] constexpr bool empty() const { @@ -335,87 +525,160 @@ public: } /** - * @brief 获取元素个数。 - * @return 元素数量。 + * @brief 获取元素个数 + * + * @return 容器中当前存储的元素数量 */ [[nodiscard]] constexpr u64 size() const { return _size; } - /** - * @brief 获取当前容量。 - * @return 容量值。 + * @brief 获取当前容量 + * + * @return 容器当前分配的存储空间可容纳的元素数量 */ [[nodiscard]] constexpr u64 capacity() const { return _capacity; } - + /** + * @brief 下标访问运算符(可修改) + * + * @param index 元素下标 + * @return 元素的引用 + * + * @pre index 必须小于当前元素数量 + */ [[nodiscard]] constexpr T& operator [](u64 index) { assert(_data && index < _size); return _data[index]; } + /** + * @brief 下标访问运算符(只读) + * + * @param index 元素下标 + * @return 元素的常量引用 + * + * @pre index 必须小于当前元素数量 + */ [[nodiscard]] constexpr const T& operator [](u64 index) const { assert(_data && index < _size); return _data[index]; } + /** + * @brief 访问第一个元素(可修改) + * + * @return 第一个元素的引用 + * + * @pre 容器必须非空 + */ [[nodiscard]] constexpr T& front() { assert(_data && _size); return _data[0]; } - + /** + * @brief 访问第一个元素(只读) + * + * @return 第一个元素的常量引用 + * + * @pre 容器必须非空 + */ [[nodiscard]] constexpr const T& front() const { assert(_data && _size); return _data[0]; } + /** + * @brief 访问最后一个元素(可修改) + * + * @return 最后一个元素的引用 + * + * @pre 容器必须非空 + */ [[nodiscard]] constexpr T& back() { assert(_data && _size); return _data[_size -1]; } + /** + * @brief 访问最后一个元素(只读) + * + * @return 最后一个元素的常量引用 + * + * @pre 容器必须非空 + */ [[nodiscard]] constexpr const T& back() const { assert(_data && _size); return _data[_size - 1]; } - + /** + * @brief 返回指向首元素的迭代器 + * + * @return 指向第一个元素的指针 + * + * @details 支持范围 for 循环 + */ [[nodiscard]] constexpr T* begin() { return std::addressof(_data[0]); } - + /** + * @brief 返回指向首元素的常量迭代器 + * + * @return 指向第一个元素的常量指针 + */ [[nodiscard]] constexpr const T* begin() const { return std::addressof(_data[0]); } + /** + * @brief 返回指向尾后位置的迭代器 + * + * @return 指向最后一个元素之后位置的指针 + * + * @details 支持范围 for 循环 + */ [[nodiscard]] constexpr T* end() { assert(!(_data == nullptr && _size > 0)); return std::addressof(_data[_size]); } + /** + * @brief 返回指向尾后位置的常量迭代器 + * + * @return 指向最后一个元素之后位置的常量指针 + */ [[nodiscard]] constexpr const T* end() const { assert(!(_data == nullptr && _size > 0)); return std::addressof(_data[_size]); } -private: +private: + /** + * @brief 从另一个容器转移所有权 + * + * @param o 源容器 + * + * @details + * 复制源容器的成员变量,然后将源容器重置为空状态。 + */ constexpr void move(vector& o) { _capacity = o._capacity; @@ -424,6 +687,12 @@ private: o.reset(); } + /** + * @brief 重置容器为空状态 + * + * @details + * 将所有成员变量设为初始值,不释放内存。 + */ constexpr void reset() { _capacity = 0; @@ -431,6 +700,16 @@ private: _data = nullptr; } + /** + * @brief 析构指定范围内的元素 + * + * @param first 起始索引 + * @param last 结束索引(不包含) + * + * @details + * 对 [first, last) 范围内的每个元素调用析构函数。 + * 仅在 destruct = true 时有效。 + */ constexpr void destruct_range(u64 first, u64 last) { assert(destruct); @@ -444,6 +723,12 @@ private: } } + /** + * @brief 销毁容器,释放所有资源 + * + * @details + * 清空所有元素,释放底层内存,重置容量为 0。 + */ constexpr void destroy() { assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }()); @@ -458,6 +743,4 @@ private: T* _data{ nullptr }; }; - } - diff --git a/EngineTest/TestRenderer.cpp b/EngineTest/TestRenderer.cpp index b93a0bd..532a261 100644 --- a/EngineTest/TestRenderer.cpp +++ b/EngineTest/TestRenderer.cpp @@ -2,7 +2,7 @@ * @file TestRenderer.cpp * @brief 渲染功能综合测试实现。 */ - +#include "Test.h" #include "TestRenderer.h" #include "Graphics/Renderer.h" #include "Platform/Platform.h" @@ -13,9 +13,9 @@ #ifdef TEST_RENDERER using namespace XEngine; - +time_it timer{}; graphics::render_surface _surfaces[4]; - +void destroy_render_surface(graphics::render_surface& surface); LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { bool toggle_fullscreen{ false }; @@ -26,9 +26,15 @@ LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) bool all_closed{ true }; for (u32 i{ 0 }; i < _countof(_surfaces); ++i) { - if(!_surfaces[i].window.is_closed()) - { - all_closed = false; + if(_surfaces[i].window.is_valid()){ + if(_surfaces[i].window.is_closed()) + { + destroy_render_surface(_surfaces[i]); + } + else + { + all_closed = false; + } } } if (all_closed) @@ -49,6 +55,12 @@ LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) } break; } + case WM_KEYDOWN: + if(wparam == VK_ESCAPE) + { + PostMessage(hwnd,WM_CLOSE,0,0); + return 0; + } } @@ -59,12 +71,16 @@ void create_render_surface(graphics::render_surface& surface, const platform::window_init_info info) { surface.window = platform::create_window(&info); + surface.surface = graphics::create_surface(surface.window); } void -_destroy_render_surface(graphics::render_surface& surface) +destroy_render_surface(graphics::render_surface& surface) { - platform::remove_window(surface.window.get_id()); + graphics::render_surface temp{surface}; + surface = {}; + if(temp.surface.is_valid())graphics::remove_surface(temp.surface.get_id()); + if(temp.window.is_valid())platform::remove_window(temp.window.get_id()); } bool @@ -93,8 +109,16 @@ engine_test::initialize() void engine_test::run() { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - graphics::render(); + timer.begin(); + //std::this_thread::sleep_for(std::chrono::milliseconds(10)); + for (u32 i{ 0 }; i < _countof(_surfaces); ++i) + { + if(_surfaces[i].surface.is_valid()) + { + _surfaces[i].surface.render(); + } + } + timer.end(); } bool @@ -102,7 +126,7 @@ engine_test::shutdown() { for (u32 i{ 0 }; i < _countof(_surfaces); ++i) { - _destroy_render_surface(_surfaces[i]); + destroy_render_surface(_surfaces[i]); } graphics::shutdown(); diff --git a/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md b/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md index bf8d989..8750c26 100644 --- a/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md +++ b/docs/changelogs/2026-03/20260331-d3d12-swap-chain.md @@ -1,7 +1,7 @@ # 变更记录:交换链与渲染表面实现 **提交日期**: 2026-03-31 -**提交哈希**: `待定` +**提交哈希**: `95d8893` **变更类型**: 功能新增 --- diff --git a/docs/changelogs/2026-03/20260331-surface-freelist-refactor.md b/docs/changelogs/2026-03/20260331-surface-freelist-refactor.md new file mode 100644 index 0000000..004abc0 --- /dev/null +++ b/docs/changelogs/2026-03/20260331-surface-freelist-refactor.md @@ -0,0 +1,404 @@ +# 变更记录:Surface 管理重构、纹理资源与文档完善 + +**提交日期**: 2026-04-01 +**提交哈希**: `b72fcf4` +**变更类型**: Bug修复 + 功能新增 + 文档完善 + +--- + +## 变更概述 + +本次提交修复了 surface 管理中的资源重复释放问题,新增纹理资源类,并为容器和 D3D12 资源添加了完整的文档。 + +## 修改文件 + +### 核心修复 + +| 文件 | 变更说明 | +|------|----------| +| `D3D12Core.cpp` | 改用 `utl::free_list` 管理 surface | +| `D3D12Surface.h` | 添加移动语义,禁用拷贝,支持撕裂检测 | +| `D3D12Surface.cpp` | 完善交换链创建和资源释放逻辑 | + +### 新增功能 + +| 文件 | 变更说明 | +|------|----------| +| `D3D12Helpers.h` | 新增堆属性辅助结构 `d3dx::heap_properties` | +| `D3D12Resources.h` | 新增 `d3d12_texture_init_info`、`d3d12_texture`、`d3d12_render_texture` 类 | +| `D3D12Resource.cpp` | 实现纹理资源创建和 SRV 绑定 | + +### 文档完善 + +| 文件 | 变更说明 | +|------|----------| +| `FreeList.h` | 添加完整 Doxygen 中文注释 | +| `Vector.h` | 添加完整 Doxygen 中文注释 | + +--- + +## Bug 修复详情 + +### 问题:Surface 重复释放 + +**现象**:关闭多个窗口时,`release()` 被调用超过预期次数,导致空指针崩溃。 + +**根本原因**: + +1. **手动调用析构函数**: + ```cpp + // 错误做法 + surfaces[id].~d3d12_surface(); // 对象仍在 vector 中 + // vector 析构时会再次调用析构函数 + ``` + +2. **Vector 扩容浅拷贝**: + ``` + vector 扩容时: + 1. 分配新内存 + 2. 移动元素(默认移动是浅拷贝) + 3. 析构旧元素 → 释放资源 + 4. 新元素持有悬空指针 → 崩溃 + ``` + +3. **描述符句柄未重置**: + ```cpp + core::rtv_heap().free(data.rtv); + // free() 只修改局部变量,data.rtv 仍指向已释放的描述符 + ``` + +### 解决方案 + +#### 1. 使用 `utl::free_list` 替代 `utl::vector` + +```cpp +// D3D12Core.cpp +using surface_collection = utl::free_list; +surface_collection surfaces; + +// 创建 surface +surface create_surface(platform::window window) +{ + surfaces.emplace_back(window); + surface_id id{ (u32)surfaces.size() - 1 }; + surfaces[id].create_swap_chain(...); + return surface{id}; +} + +// 删除 surface +void remove_surface(surface_id id) +{ + gfx_command.flush(); + surfaces.remove(id); // free_list 的 remove 正确处理 +} +``` + +#### 2. 实现移动语义 + +```cpp +class d3d12_surface +{ +public: + // 移动构造函数 + d3d12_surface(d3d12_surface&& other) noexcept + : _swap_chain{other._swap_chain} + , _window{other._window} + // ... 转移所有资源 + { + other._swap_chain = nullptr; // 源对象置空 + } + + // 禁用拷贝 + d3d12_surface(const d3d12_surface&) = delete; + d3d12_surface& operator=(const d3d12_surface&) = delete; +}; +``` + +#### 3. 重置描述符句柄 + +```cpp +void d3d12_surface::release() +{ + for(u32 i = 0; i < frame_buffer_count; ++i) + { + render_target_data& data{ _render_target_data[i] }; + core::release(data.resource); + core::rtv_heap().free(data.rtv); + data.rtv = {}; // 重置句柄,防止重复释放 + } + core::release(_swap_chain); +} +``` + +--- + +## FreeList 容器详解 + +### 数据结构原理 + +`utl::free_list` 使用空闲链表管理已删除的槽位: + +``` +初始状态: +_array: [ 空 | 空 | 空 | 空 ] +_next_free_index = invalid_id +_size = 0 + +添加元素 A、B、C 后: +_array: [ A | B | C | 空 ] +_next_free_index = invalid_id +_size = 3 + +删除元素 B 后(槽位 1 被标记为空闲): +_array: [ A | ->2 | C | 空 ] // 槽位 1 存储下一个空闲索引 +_next_free_index = 1 +_size = 2 + +添加新元素 D 时(复用槽位 0): +_array: [ D | ->2 | C | 空 ] +_next_free_index = 1 +_size = 2 +``` + +### 核心优势 + +| 特性 | 说明 | +|------|------| +| **O(1) 增删** | 新增和删除操作近似常数时间 | +| **槽位复用** | 删除后的槽位被回收,避免频繁内存分配 | +| **稳定索引** | 使用 u32 索引作为外部句柄,与 ID 系统配合 | +| **内存紧凑** | 底层使用连续内存,缓存友好 | + +### 与 Vector 的区别 + +| 特性 | free_list | vector | +|------|-----------|--------| +| 删除操作 | O(1),槽位复用 | O(n) 需要移动元素 | +| 索引稳定性 | 删除后索引可能被复用 | 删除后索引失效 | +| 内存碎片 | 无碎片,槽位复用 | 删除后内存不释放 | +| 适用场景 | 游戏对象、资源句柄 | 顺序容器、临时数据 | + +--- + +## Vector 容器详解 + +### 析构策略 + +模板参数 `destruct` 控制元素析构行为: + +```cpp +// 默认:删除时调用析构函数 +utl::vector strings; // destruct = true + +// POD 类型优化:跳过析构 +utl::vector vertices; // destruct = false +``` + +### 扩容策略 + +``` +扩容公式:new_capacity = (old_capacity + 1) * 3 / 2 + +初始容量 0 → 1 +容量 4 → 6 +容量 10 → 15 +``` + +### 无序删除优化 + +```cpp +// 普通删除:O(n),保持顺序 +vec.erase(index); + +// 无序删除:O(1),用末尾元素替换 +vec.erase_unordered(index); // 适用于顺序不重要的场景 +``` + +--- + +## 交换链详解 + +### 核心职责 + +交换链连接窗口与渲染管线,管理一组后台缓冲区,并通过 `Present()` 实现缓冲区翻转,将绘制内容显示到窗口。 + +**交换链只负责**: +- 缓冲区的分配与翻转 + +**开发者需显式完成**: +- 绑定渲染目标(`OMSetRenderTargets`) +- GPU 同步(Fence) +- 状态转换(资源屏障) + +### 标准使用流程 + +``` +1. 获取当前后台缓冲区(GetBuffer) + ↓ +2. 将其绑定为渲染目标(OMSetRenderTargets) + ↓ +3. 执行绘制命令(写入该缓冲区) + ↓ +4. 提交命令并同步(Fence) + ↓ +5. 调用 Present(翻转缓冲区,显示图像) +``` + +### 职责边界 + +| 操作 | 负责方 | 说明 | +|------|--------|------| +| 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 | +| 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 | +| 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV | +| GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 | +| 状态转换 | 开发者 | 资源屏障管理缓冲区状态 | +| 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 | + +--- + +## SRV 创建与资源创建方式 + +### CreateShaderResourceView 参数说明 + +```cpp +void CreateShaderResourceView( + ID3D12Resource *pResource, + const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, + D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor +); +``` + +| 参数 | 说明 | +|------|------| +| `pResource` | 要创建 SRV 的 GPU 资源指针(必须是有效资源,不能为 nullptr) | +| `pDesc` | 视图描述符,指定格式、维度、Mip 级别范围等。为 `nullptr` 时使用资源默认属性 | +| `DestDescriptor` | SRV 描述符堆中 CPU 描述符句柄的位置 | + +**关键点**: +- `pDesc` 为 `nullptr` 时,视图描述符默认使用资源本身的格式和全部子资源(空描述符初始化) +- 同一个资源可以创建多个不同的 SRV(不同格式、不同 Mip 切片) + +### D3D12 资源创建函数对比 + +| 函数 | 堆类型 | 说明 | +|------|--------|------| +| `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 | +| `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 | +| `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 | + +**选择建议**: +- **Committed**:最常用,堆由系统隐式管理 +- **Placed**:需要显式堆,资源放置于堆的指定偏移 +- **Reserved**:仅预留虚拟地址,用于稀疏资源,实现内存的按需提交 + +--- + +## 纹理资源类 + +### d3d12_texture_init_info 结构 + +纹理初始化信息结构,支持三种资源创建方式: + +```cpp +struct d3d12_texture_init_info +{ + ID3D12Heap1* heap{nullptr}; // 显式堆(Placed Resource) + ID3D12Resource* resource{nullptr}; // 已有资源(直接使用) + D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; // SRV 描述(nullptr 使用默认) + D3D12_RESOURCE_DESC* desc{nullptr}; // 资源描述 + D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 分配信息(偏移量) + D3D12_RESOURCE_STATES initial_state{}; // 初始状态 + D3D12_CLEAR_VALUE clear_value{}; // 清除值(RTV/DSV) +}; +``` + +### d3d12_texture 类 + +基础纹理类,封装资源创建和 SRV 绑定: + +```cpp +class d3d12_texture +{ +public: + constexpr static u32 max_mips{ 14 }; + + explicit d3d12_texture(d3d12_texture_init_info info); + + // 移动语义 + d3d12_texture(d3d12_texture&& o); + d3d12_texture& operator=(d3d12_texture&& o); + + // 禁用拷贝 + DISABLE_COPY(d3d12_texture); + + void release(); + ID3D12Resource* resource() const; + descriptor_handle srv() const; + +private: + ID3D12Resource* _resource{nullptr}; + descriptor_handle _srv; +}; +``` + +### 资源创建逻辑 + +```cpp +d3d12_texture::d3d12_texture(d3d12_texture_init_info info) +{ + // 优先级 1:使用已有资源 + if (info.resource) { + _resource = info.resource; + } + // 优先级 2:Placed Resource(显式堆) + else if (info.heap && info.desc) { + device->CreatePlacedResource( + info.heap, + info.allocation_info.Offset, + info.desc, ...); + } + // 优先级 3:Committed Resource(隐式堆) + else if (info.desc) { + device->CreateCommittedResource( + &d3dx::heap_properties.default_heap, + D3D12_HEAP_FLAG_NONE, + info.desc, ...); + } + + // 创建 SRV + _srv = core::srv_heap().allocate(); + device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu); +} +``` + +### d3dx::heap_properties 辅助结构 + +```cpp +namespace XEngine::graphics::d3d12::d3dx +{ +constexpr struct { + D3D12_HEAP_PROPERTIES default_heap{ + D3D12_HEAP_TYPE_DEFAULT, // GPU 可读写,CPU 不可访问 + D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + D3D12_MEMORY_POOL_UNKNOWN, + 0, // 单 GPU 系统 + 0 + }; +} heap_properties; +} +``` + +--- + +## 后续工作 + +- [ ] 实现深度模板视图 +- [ ] 渲染第一个三角形 +- [ ] 实现根签名和管线状态对象 + +--- + +## 相关文档 + +- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md) diff --git a/docs/wiki/D3D12学习Wiki.md b/docs/wiki/D3D12学习Wiki.md index 32bfac4..c110483 100644 --- a/docs/wiki/D3D12学习Wiki.md +++ b/docs/wiki/D3D12学习Wiki.md @@ -489,12 +489,307 @@ void allocate() { } // 自动解锁 ``` +### 7.9 着色器资源视图(SRV) + +#### CreateShaderResourceView 函数 + +```cpp +void CreateShaderResourceView( + ID3D12Resource *pResource, + const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, + D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor +); +``` + +| 参数 | 说明 | +|------|------| +| `pResource` | 要创建 SRV 的 GPU 资源指针(必须是有效资源,不能为 nullptr) | +| `pDesc` | 视图描述符,指定格式、维度、Mip 级别范围等。为 `nullptr` 时使用资源默认属性 | +| `DestDescriptor` | SRV 描述符堆中 CPU 描述符句柄的位置 | + +#### 空描述符初始化 + +当 `pDesc` 为 `nullptr` 时,视图描述符默认使用资源本身的格式和全部子资源: + +```cpp +// 使用默认视图属性 +device->CreateShaderResourceView(texture, nullptr, rtv_handle); + +// 等价于显式指定完整描述 +D3D12_SHADER_RESOURCE_VIEW_DESC desc{}; +desc.Format = texture->GetDesc().Format; +desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; +desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; +desc.Texture2D.MostDetailedMip = 0; +desc.Texture2D.MipLevels = texture->GetDesc().MipLevels; +device->CreateShaderResourceView(texture, &desc, rtv_handle); +``` + +#### 多视图支持 + +同一个资源可以创建多个不同的 SRV: + +```cpp +// 原始格式视图 +device->CreateShaderResourceView(texture, nullptr, srv_handle0); + +// 不同格式视图(如 R32_FLOAT 作为 RGBA 视图) +D3D12_SHADER_RESOURCE_VIEW_DESC rgba_desc{}; +rgba_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +// ... +device->CreateShaderResourceView(texture, &rgba_desc, srv_handle1); + +// 特定 Mip 切片视图 +D3D12_SHADER_RESOURCE_VIEW_DESC mip_desc{}; +mip_desc.Texture2D.MostDetailedMip = 2; +mip_desc.Texture2D.MipLevels = 1; +// ... +device->CreateShaderResourceView(texture, &mip_desc, srv_handle2); +``` + +### 7.10 资源创建方式 + +D3D12 提供三种资源创建函数,对应不同的堆管理策略: + +#### 函数对比 + +| 函数 | 堆类型 | 说明 | +|------|--------|------| +| `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 | +| `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 | +| `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 | + +#### CreateCommittedResource(最常用) + +```cpp +// 系统自动管理堆,最简单的方式 +D3D12_HEAP_PROPERTIES heap_props{ + .Type = D3D12_HEAP_TYPE_DEFAULT +}; + +D3D12_RESOURCE_DESC resource_desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Width = width, + .Height = height, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + // ... +}; + +ID3D12Resource* texture; +device->CreateCommittedResource( + &heap_props, + D3D12_HEAP_FLAG_NONE, + &resource_desc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(&texture) +); +``` + +#### CreatePlacedResource(精确控制) + +```cpp +// 先创建堆 +ID3D12Heap* heap; +D3D12_HEAP_DESC heap_desc{ + .SizeInBytes = heap_size, + .Properties = { .Type = D3D12_HEAP_TYPE_DEFAULT }, + // ... +}; +device->CreateHeap(&heap_desc, IID_PPV_ARGS(&heap)); + +// 在堆的特定偏移放置资源 +ID3D12Resource* texture; +device->CreatePlacedResource( + heap, + 0, // 偏移量(必须满足对齐要求) + &resource_desc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(&texture) +); +``` + +**适用场景**: +- 资源复用(同一堆位置放置不同资源) +- 精确内存对齐 +- 自定义内存管理 + +#### CreateReservedResource(稀疏资源) + +```cpp +// 仅预留虚拟地址,不分配物理内存 +ID3D12Resource* sparse_texture; +device->CreateReservedResource( + &resource_desc, + D3D12_RESOURCE_STATE_COMMON, + nullptr, + IID_PPV_ARGS(&sparse_texture) +); + +// 后续按需提交物理内存页面 +// 使用 UpdateTileMappings 和 MakeResident +``` + +**适用场景**: +- 超大纹理(如地形纹理)按需加载 +- 流式资源管理 +- 虚拟纹理系统 + +#### 选择建议 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 资源创建方式选择 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 常规纹理/缓冲区? │ +│ │ │ +│ ├── 是 ──► CreateCommittedResource │ +│ │ (简单、自动管理) │ +│ │ │ +│ └── 否 ──► 需要精确内存控制? │ +│ │ │ +│ ├── 是 ──► CreatePlacedResource │ +│ │ (资源复用、对齐控制) │ +│ │ │ +│ └── 否 ──► 超大资源按需加载? │ +│ │ │ +│ ├── 是 ──► │ +│ │ CreateReservedResource│ +│ │ (稀疏资源) │ +│ │ │ +│ └── 否 ──► 重新评估需求 │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 7.11 纹理资源类 + +#### d3d12_texture_init_info 结构 + +纹理初始化信息结构,统一管理三种资源创建方式的参数: + +```cpp +struct d3d12_texture_init_info +{ + ID3D12Heap1* heap{nullptr}; // 显式堆(Placed Resource) + ID3D12Resource* resource{nullptr}; // 已有资源(直接使用) + D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; // SRV 描述(nullptr 使用默认) + D3D12_RESOURCE_DESC* desc{nullptr}; // 资源描述 + D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 分配信息(偏移量) + D3D12_RESOURCE_STATES initial_state{}; // 初始状态 + D3D12_CLEAR_VALUE clear_value{}; // 清除值(RTV/DSV) +}; +``` + +#### d3d12_texture 类 + +基础纹理类,封装资源创建和 SRV 绑定: + +```cpp +class d3d12_texture +{ +public: + constexpr static u32 max_mips{ 14 }; + + explicit d3d12_texture(d3d12_texture_init_info info); + + // 移动语义 + d3d12_texture(d3d12_texture&& o); + d3d12_texture& operator=(d3d12_texture&& o); + + // 禁用拷贝 + DISABLE_COPY(d3d12_texture); + + void release(); + ID3D12Resource* resource() const; + descriptor_handle srv() const; + +private: + ID3D12Resource* _resource{nullptr}; + descriptor_handle _srv; +}; +``` + +#### 资源创建优先级 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 纹理资源创建优先级 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ info.resource != nullptr ? │ +│ │ │ +│ ├── 是 ──► 直接使用已有资源 │ +│ │ (适用于外部管理的资源) │ +│ │ │ +│ └── 否 ──► info.heap != nullptr ? │ +│ │ │ +│ ├── 是 ──► CreatePlacedResource │ +│ │ (显式堆,精确控制) │ +│ │ │ +│ └── 否 ──► CreateCommittedResource │ +│ (隐式堆,最常用) │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### d3dx::heap_properties 辅助结构 + +提供常用的堆属性配置: + +```cpp +namespace XEngine::graphics::d3d12::d3dx +{ +constexpr struct { + D3D12_HEAP_PROPERTIES default_heap{ + D3D12_HEAP_TYPE_DEFAULT, // GPU 可读写,CPU 不可访问 + D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + D3D12_MEMORY_POOL_UNKNOWN, + 0, // 单 GPU 系统 + 0 + }; +} heap_properties; +} +``` + +#### 使用示例 + +```cpp +// 创建默认纹理(Committed Resource) +D3D12_RESOURCE_DESC desc{ + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Width = 1024, + .Height = 1024, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + // ... +}; + +d3d12_texture_init_info info{ + .desc = &desc, + .initial_state = D3D12_RESOURCE_STATE_COPY_DEST, +}; + +d3d12_texture texture{info}; + +// 获取资源用于后续操作 +ID3D12Resource* resource = texture.resource(); +descriptor_handle srv = texture.srv(); +``` + ## 8. 交换链(Swap Chain) ### 8.1 什么是交换链? 交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换,实现流畅的画面显示。 +**核心职责**: + +- **连接窗口与渲染管线**:将渲染输出与显示器关联 +- **管理后台缓冲区**:分配和维护一组用于渲染的缓冲区 +- **缓冲区翻转**:通过 `Present()` 实现前后缓冲区交换,将绘制内容显示到窗口 + ``` ┌─────────────────────────────────────────────────────────────┐ │ 交换链工作原理 │ @@ -520,7 +815,65 @@ void allocate() { └─────────────────────────────────────────────────────────────┘ ``` -### 8.2 三重缓冲 +### 8.2 交换链的职责边界 + +交换链**只负责**缓冲区的分配与翻转,以下操作需开发者显式完成: + +| 操作 | 负责方 | 说明 | +|------|--------|------| +| 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 | +| 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 | +| 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV | +| GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 | +| 状态转换 | 开发者 | 资源屏障管理缓冲区状态 | +| 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 | + +### 8.3 标准渲染流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 交换链标准使用流程 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 1. 获取当前后台缓冲区 │ +│ ┌─────────────────────────────────────────┐ │ +│ │ ID3D12Resource* buffer = swap_chain-> │ │ +│ │ GetBuffer(current_index); │ │ +│ └─────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ 2. 创建并绑定渲染目标视图 │ +│ ┌─────────────────────────────────────────┐ │ +│ │ device->CreateRenderTargetView( │ │ +│ │ buffer, nullptr, rtv_handle); │ │ +│ │ cmd_list->OMSetRenderTargets( │ │ +│ │ 1, &rtv_handle, nullptr); │ │ +│ └─────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ 3. 执行绘制命令(写入后台缓冲区) │ +│ ┌─────────────────────────────────────────┐ │ +│ │ cmd_list->ClearRenderTargetView(...); │ │ +│ │ cmd_list->DrawInstanced(...); │ │ +│ └─────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ 4. 提交命令并同步 │ +│ ┌─────────────────────────────────────────┐ │ +│ │ cmd_queue->ExecuteCommandLists(...); │ │ +│ │ // 等待 GPU 完成(Fence 同步) │ │ +│ └─────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ 5. 呈现(翻转缓冲区) │ +│ ┌─────────────────────────────────────────┐ │ +│ │ swap_chain->Present(sync_interval, 0); │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 8.5 三重缓冲 项目使用三重缓冲(`frame_buffer_count = 3`): @@ -530,7 +883,7 @@ void allocate() { | **提高并行性** | CPU 可提前录制多帧命令 | | **平滑帧率** | 缓冲区平滑帧时间波动 | -### 8.3 d3d12_surface 类 +### 8.6 d3d12_surface 类 ```cpp class d3d12_surface @@ -559,7 +912,7 @@ private: }; ``` -### 8.4 交换链创建流程 +### 8.7 交换链创建流程 ```cpp void create_swap_chain(...) @@ -587,7 +940,7 @@ void create_swap_chain(...) } ``` -### 8.5 视口与裁剪矩形 +### 8.8 视口与裁剪矩形 ```cpp // 视口:定义光栅化区域 @@ -604,6 +957,75 @@ D3D12_VIEWPORT viewport{ D3D12_RECT scissor_rect{0, 0, width, height}; ``` +### 8.9 Surface 管理与 free_list + +#### 问题:Vector 扩容导致的资源重复释放 + +使用 `utl::vector` 管理 surface 时,扩容会触发元素移动: + +``` +vector 扩容流程: +1. 分配新内存块 +2. 移动元素到新内存(默认移动是浅拷贝) +3. 析构旧位置的元素 → 调用 release() +4. 新位置的元素持有悬空指针 → 崩溃! +``` + +#### 解决方案:使用 free_list + +`utl::free_list` 是带槽位复用机制的容器: + +```cpp +// 定义 surface 集合类型 +using surface_collection = utl::free_list; +surface_collection surfaces; + +// 创建 surface +surface create_surface(platform::window window) +{ + surfaces.emplace_back(window); + surface_id id{ (u32)surfaces.size() - 1 }; + surfaces[id].create_swap_chain(...); + return surface{id}; +} + +// 删除 surface(槽位被回收) +void remove_surface(surface_id id) +{ + gfx_command.flush(); + surfaces.remove(id); +} +``` + +#### free_list 数据结构 + +``` +初始状态: +_array: [ 空 | 空 | 空 | 空 ] +_next_free_index = invalid_id + +添加元素 A、B、C 后: +_array: [ A | B | C | 空 ] +_next_free_index = invalid_id + +删除元素 B 后: +_array: [ A | ->2 | C | 空 ] // 槽位1存储下一个空闲索引 +_next_free_index = 1 + +添加新元素 D: +_array: [ D | ->2 | C | 空 ] // 复用槽位1 +_next_free_index = 2 +``` + +#### free_list vs vector + +| 特性 | free_list | vector | +|------|-----------|--------| +| 删除复杂度 | O(1) | O(n) | +| 索引稳定性 | 删除后可复用 | 删除后失效 | +| 内存管理 | 槽位复用 | 可能扩容移动 | +| 适用场景 | 资源句柄管理 | 顺序数据存储 | + ## 9. 渲染表面与窗口 ### 9.1 render_surface 结构