当前位置:首页 > 技术 > 正文内容

DX12开发中的常用工具类设计

访客 技术 2026年9月13日 10

可控生命周期的单例模板

在图形引擎开发中,许多核心系统需要全局唯一实例,但又希望精确控制其创建和销毁时机。以下模板实现了显式初始化的单例模式:

template<typename T>
class Singleton
{
public:
    template<typename... Args>
    static void Initialize(Args&&... params)
    {
        if (!s_pInstance)
        {
            s_pInstance = new T(std::forward<Args>(params)...);
        }
    }

    static void Terminate() noexcept
    {
        delete s_pInstance;
        s_pInstance = nullptr;
    }

    static T& GetInstance()
    {
        return *s_pInstance;
    }

protected:
    Singleton() = default;
    ~Singleton() = default;

private:
    inline static T* s_pInstance = nullptr;
};

使用注意:由于基类无法访问派生类的保护构造函数,非抽象派生类需将基类声明为友元:

class LightSystem : public Singleton<LightSystem>
{
protected:
    friend class Singleton<LightSystem>;
    LightSystem() = default;
};

ImGui渲染管理框架

基于CRTP(奇异递归模板模式)设计的ImGui管理基类,使单例能够访问派生类的专属接口:

template<typename Derived>
class ImGuiRenderer : public Singleton<Derived>
{
public:
    bool Setup(ID3D12Device* pDevice, HWND hwnd, 
               int swapChainBuffers, DXGI_FORMAT renderFormat)
    {
        return static_cast<Derived*>(this)->OnSetup(
            pDevice, hwnd, swapChainBuffers, renderFormat);
    }

    void Tick(const FrameTimer& deltaTime)
    {
        ImGui::NewFrame();
        static_cast<Derived*>(this)->OnRenderUI(deltaTime);
        ImGui::Render();
    }

    void Draw(ID3D12GraphicsCommandList* pCmdList)
    {
        ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), pCmdList);
    }

protected:
    virtual bool OnSetup(ID3D12Device*, HWND, int, DXGI_FORMAT) = 0;
    virtual void OnRenderUI(const FrameTimer&) = 0;
};

光源管理系统

管理场景光源数据,支持多帧资源的CPU/GPU同步:

class LightSystem : public Singleton<LightSystem>
{
public:
    LightSystem(ID3D12Device* pDevice, UINT frameBufferCount,
                int maxDirectional = 8, 
                int maxOmni = 16, 
                int maxProjector = 8);

    void SetDirectionalLight(size_t slot, const DirectionalLight& data);
    void SetPointLight(size_t slot, const PointLight& data);
    void SetSpotLight(size_t slot, const SpotLight& data);

    D3D12_GPU_VIRTUAL_ADDRESS GetCurrentFrameAddress() const;
    
    std::vector<D3D_SHADER_MACRO> GenerateShaderDefines(
        const char* dirMacro, 
        const char* pointMacro, 
        const char* spotMacro) const;

    void FlushChanges();

private:
    void AllocateLightBuffer(ID3D12Device* pDevice);

    ComPtr<ID3D12Resource> m_pLightUploadBuffer;
    const UINT m_FrameCount;
    UINT m_DirtyFrameCounter = 0;
    bool m_bLightsChanged = false;
};

核心机制:m_DirtyFrameCounter用于追踪数据变更,仅在有修改时执行GPU上传,避免每帧拷贝。

几何数据容器

内存中的原始网格数据,支持多种索引格式:

struct RawMesh
{
    struct VertexLayout
    {
        XMFLOAT3 position;
        XMFLOAT3 normal;
        XMFLOAT4 tangent;
        XMFLOAT3 bitangent;
        XMFLOAT2 uv;
    };

    std::vector<VertexLayout> vertices;
    std::vector<uint32_t> indices32;
    std::vector<uint16_t> indices16;

    BoundingBox localBounds;
};

模型导入数据结构

Assimp导入后的中间表示,关联材质与几何:

struct SubMeshInfo
{
    std::string identifier;
    RawMesh geometryData;
    BoundingBox bounds;
    UINT materialSlot;
};

struct ModelAsset
{
    std::string assetName;
    std::unordered_map<std::string, SubMeshInfo> subMeshes;
    std::vector<PbrMaterial> materials;
};

GPU就绪的网格资源

上传至显存后的最终渲染数据,包含绘制参数:

struct GpuMesh
{
    std::string resourceName;

    // CPU端备份(用于动态更新或调试)
    ComPtr<ID3DBlob> vertexDataCPU;
    ComPtr<ID3lob> indexDataCPU;

    // GPU资源
    ComPtr<ID3D12Resource> vertexBuffer;
    ComPtr<ID3D12Resource> indexBuffer;
    ComPtr<ID3D12Resource> vertexUploader; // 可释放
    ComPtr<ID3D12Resource> indexUploader;  // 可释放

    // 视图参数
    UINT vertexStride = 0;
    UINT vertexBufferSize = 0;
    DXGI_FORMAT indexFormat = DXGI_FORMAT_R16_UINT;
    UINT indexBufferSize = 0;

    // 子网格绘制参数(支持多部分模型)
    struct DrawCommand
    {
        UINT indexCount;
        UINT startIndex;
        INT baseVertex;
    };
    std::unordered_map<std::string, DrawCommand> drawCommands;

    D3D12_VERTEX_BUFFER_VIEW GetVertexView() const
    {
        return { vertexBuffer->GetGPUVirtualAddress(), 
                 vertexBufferSize, vertexStride };
    }

    D3D12_INDEX_BUFFER_VIEW GetIndexView() const
    {
        return { indexBuffer->GetGPUVirtualAddress(), 
                 indexBufferSize, indexFormat };
    }

    void ReleaseUploaders()
    {
        vertexUploader.Reset();
        indexUploader.Reset();
    }
};

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。