Enhance OpenGLFence with proper synchronization
- Add FenceStatus enum for status query - Add m_sync (GLsync) for OpenGL fence synchronization - Add Signal(value) overload with fence value - Add Wait(timeoutNs) with timeout support - Add GetStatus() for async status check - Add GetCompletedValue() and GetCurrentValue() - Implement using glSync for proper GPU synchronization - Replace glFinish blocking with glClientWaitSync
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
# 跨平台 RHI 渲染架构设计文档
|
||||
## 1. 项目背景
|
||||
本项目旨在参考 Unity 渲染架构,为已有的 **OpenGL** 和 **Direct3D 12** 图形 API 后端设计统一的**渲染硬件抽象层(RHI)**,屏蔽 API 差异,实现引擎上层逻辑与底层图形 API 的解耦。
|
||||
|
||||
### 现有项目结构
|
||||
```
|
||||
engine/
|
||||
├── include/XCEngine/RHI/
|
||||
│ ├── Enums.h # 通用枚举定义
|
||||
│ ├── Types.h # 通用结构体定义
|
||||
│ ├── D3D12/ # D3D12 后端实现
|
||||
│ └── OpenGL/ # OpenGL 后端实现
|
||||
└── src/RHI/
|
||||
├── D3D12/ # D3D12 源码
|
||||
└── OpenGL/ # OpenGL 源码
|
||||
```
|
||||
|
||||
|
||||
## 2. 核心设计理念
|
||||
### 2.1 总体原则
|
||||
**求同存异,分层抽象,特性降级,底层逃逸**
|
||||
- **求同存异**:优先提取 API 共性作为核心抽象,差异部分通过模拟/降级处理
|
||||
- **分层抽象**:通过清晰的层级结构隔离 API 差异
|
||||
- **特性降级**:对高级特性提供能力检测和替代方案
|
||||
- **底层逃逸**:允许直接访问原生 API 以满足极端需求
|
||||
|
||||
### 2.2 差异分类与处理策略
|
||||
| 差异类型 | 典型示例 | 处理方案 |
|
||||
|----------|----------|----------|
|
||||
| 概念命名不同但本质相似 | D3D12 `CommandList` ≈ OpenGL 状态机绘制 | 直接统一抽象 |
|
||||
| 显式控制 vs 隐式管理 | D3D12 `DescriptorHeap` vs OpenGL 纹理单元 | 提供"自动模式"+"显式模式" |
|
||||
| API 独有高级特性 | D3D12 光线追踪、网格着色器 | 特性检测 + 降级方案 + 底层逃逸 |
|
||||
|
||||
|
||||
## 3. RHI 分层架构
|
||||
### 3.1 通用分层模型
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 引擎功能层(场景/材质/光照) │
|
||||
├─────────────────────────────────┤
|
||||
│ 渲染管线层(SRP/Render Graph) │
|
||||
├─────────────────────────────────┤
|
||||
│ RHI 抽象层(统一接口) │ ← 核心设计重点
|
||||
├─────────────────────────────────┤
|
||||
│ API 后端层(D3D12/OpenGL) │
|
||||
├─────────────────────────────────┤
|
||||
│ 驱动/硬件层 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 层级职责说明
|
||||
1. **引擎功能层**:提供场景管理、材质系统、光照等高级功能接口
|
||||
2. **渲染管线层**:定义渲染流程(前向/延迟渲染)
|
||||
3. **RHI 抽象层**:统一图形 API 接口,屏蔽差异
|
||||
4. **API 后端层**:针对具体 API 的实现
|
||||
5. **驱动/硬件层**:最终执行渲染指令
|
||||
|
||||
|
||||
## 4. RHI 抽象基类设计
|
||||
### 4.1 目录结构调整建议
|
||||
```
|
||||
include/XCEngine/RHI/
|
||||
├── Enums.h # 通用枚举
|
||||
├── Types.h # 通用结构体
|
||||
├── RHICapabilities.h # 硬件能力检测
|
||||
├── RHIDevice.h # 设备抽象(核心入口)
|
||||
├── RHICommandQueue.h # 命令队列抽象
|
||||
├── RHICommandList.h # 命令列表抽象
|
||||
├── RHIBuffer.h # 缓冲区抽象
|
||||
├── RHITexture.h # 纹理抽象
|
||||
├── RHIShader.h # 着色器抽象
|
||||
├── RHIPipelineLayout.h # 管线布局抽象(替代 RootSignature)
|
||||
├── RHIPipelineState.h # 管线状态抽象
|
||||
├── RHISwapChain.h # 交换链抽象
|
||||
├── RHIFence.h # 同步栅栏抽象
|
||||
├── RHIDescriptorPool.h # 描述符池抽象
|
||||
└── RHIFactory.h # RHI 工厂类
|
||||
```
|
||||
|
||||
### 4.2 核心抽象基类定义
|
||||
#### 4.2.1 通用类型定义(Types.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// 通用资源格式
|
||||
enum class RHIFormat {
|
||||
R8G8B8A8_UNORM,
|
||||
D32_FLOAT,
|
||||
// ... 其他格式
|
||||
};
|
||||
|
||||
// 缓冲区用途
|
||||
enum class RHIBufferUsage {
|
||||
VertexBuffer,
|
||||
IndexBuffer,
|
||||
ConstantBuffer,
|
||||
// ... 其他用途
|
||||
};
|
||||
|
||||
// 通用缓冲区描述
|
||||
struct RHIBufferDesc {
|
||||
uint64_t size;
|
||||
RHIBufferUsage usage;
|
||||
// ... 其他参数
|
||||
};
|
||||
|
||||
// 通用纹理描述
|
||||
struct RHITextureDesc {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
RHIFormat format;
|
||||
// ... 其他参数
|
||||
};
|
||||
|
||||
// 通用渲染通道描述
|
||||
struct RHIRenderPassDesc {
|
||||
struct Attachment {
|
||||
RHITexture* texture;
|
||||
float clearColor[4];
|
||||
// ... 其他参数
|
||||
} colorAttachments[8];
|
||||
uint32_t colorAttachmentCount;
|
||||
Attachment depthStencilAttachment;
|
||||
// ... 其他参数
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.2.2 硬件能力检测(RHICapabilities.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
struct RHICapabilities {
|
||||
bool bSupportsRayTracing = false;
|
||||
bool bSupportsMeshShaders = false;
|
||||
bool bSupportsExplicitMultiThreading = false;
|
||||
// ... 其他特性
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.2.3 设备抽象(RHIDevice.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include "Types.h"
|
||||
#include "RHICapabilities.h"
|
||||
|
||||
class RHISwapChain;
|
||||
class RHICommandQueue;
|
||||
class RHIBuffer;
|
||||
class RHITexture;
|
||||
|
||||
class RHIDevice {
|
||||
public:
|
||||
virtual ~RHIDevice() = default;
|
||||
|
||||
// 初始化设备
|
||||
virtual bool Initialize(const RHIDeviceDesc& desc) = 0;
|
||||
|
||||
// 资源创建接口
|
||||
virtual RHIBuffer* CreateBuffer(const RHIBufferDesc& desc) = 0;
|
||||
virtual RHITexture* CreateTexture(const RHITextureDesc& desc) = 0;
|
||||
virtual RHICommandQueue* CreateCommandQueue(const RHICommandQueueDesc& desc) = 0;
|
||||
virtual RHISwapChain* CreateSwapChain(const RHISwapChainDesc& desc) = 0;
|
||||
|
||||
// 硬件能力查询
|
||||
virtual const RHICapabilities& GetCapabilities() const = 0;
|
||||
|
||||
// 底层逃逸口(谨慎使用)
|
||||
virtual void* GetNativeDevice() = 0;
|
||||
|
||||
// 清理
|
||||
virtual void Shutdown() = 0;
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.2.4 命令列表抽象(RHICommandList.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include "Types.h"
|
||||
|
||||
class RHIBuffer;
|
||||
class RHITexture;
|
||||
class RHIPipelineState;
|
||||
|
||||
class RHICommandList {
|
||||
public:
|
||||
virtual ~RHICommandList() = default;
|
||||
|
||||
// 命令录制
|
||||
virtual void Begin() = 0;
|
||||
virtual void End() = 0;
|
||||
|
||||
// 渲染通道
|
||||
virtual void BeginRenderPass(const RHIRenderPassDesc& desc) = 0;
|
||||
virtual void EndRenderPass() = 0;
|
||||
|
||||
// 状态设置
|
||||
virtual void SetPipelineState(RHIPipelineState* pso) = 0;
|
||||
virtual void SetVertexBuffers(uint32_t slot, RHIBuffer* const* buffers, uint32_t count) = 0;
|
||||
virtual void SetIndexBuffer(RHIBuffer* buffer, RHIFormat format, uint32_t offset) = 0;
|
||||
|
||||
// 绘制命令
|
||||
virtual void DrawIndexed(uint32_t indexCount, uint32_t instanceCount,
|
||||
uint32_t startIndex, int32_t baseVertex,
|
||||
uint32_t startInstance) = 0;
|
||||
|
||||
// 资源拷贝
|
||||
virtual void CopyBuffer(RHIBuffer* dst, RHIBuffer* src, uint64_t size) = 0;
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.2.5 管线布局抽象(RHIPipelineLayout.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include "Types.h"
|
||||
|
||||
// 管线布局描述(统一描述资源绑定规则)
|
||||
struct RHIPipelineLayoutDesc {
|
||||
uint32_t constantBufferCount;
|
||||
uint32_t textureCount;
|
||||
uint32_t samplerCount;
|
||||
// ... 其他参数
|
||||
};
|
||||
|
||||
class RHIPipelineLayout {
|
||||
public:
|
||||
virtual ~RHIPipelineLayout() = default;
|
||||
// 具体实现由后端处理
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.2.6 RHI 工厂类(RHIFactory.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include "RHIDevice.h"
|
||||
#include <string>
|
||||
|
||||
enum class RHIType {
|
||||
D3D12,
|
||||
OpenGL,
|
||||
// 未来扩展 Vulkan/Metal
|
||||
};
|
||||
|
||||
class RHIFactory {
|
||||
public:
|
||||
// 根据类型创建 RHI 设备
|
||||
static RHIDevice* CreateRHIDevice(RHIType type);
|
||||
|
||||
// 根据字符串选择(从配置文件读取)
|
||||
static RHIDevice* CreateRHIDevice(const std::string& typeName);
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## 5. 核心差异处理方案
|
||||
### 5.1 根签名 vs OpenGL 资源绑定
|
||||
- **D3D12**:显式 `RootSignature` 定义资源绑定规则
|
||||
- **OpenGL**:隐式通过 `glUniformLocation`、`glBindTextureUnit` 绑定
|
||||
- **解决方案**:
|
||||
- 用 `RHIPipelineLayout` 统一抽象
|
||||
- D3D12 后端:内部创建 `ID3D12RootSignature`
|
||||
- OpenGL 后端:存储绑定点数量,绘制时自动调用 `glBind*`
|
||||
|
||||
### 5.2 描述符堆 vs OpenGL 纹理单元
|
||||
- **D3D12**:显式 `DescriptorHeap` 管理资源视图
|
||||
- **OpenGL**:隐式通过 `glActiveTexture` 绑定
|
||||
- **解决方案**:
|
||||
- 用 `RHIDescriptorPool` 统一抽象
|
||||
- 提供"自动模式"(默认)和"显式模式"(可选)
|
||||
- D3D12 后端:从堆分配槽位,更新描述符
|
||||
- OpenGL 后端:维护绑定点计数器,绘制时自动绑定
|
||||
|
||||
### 5.3 多线程命令录制
|
||||
- **D3D12**:原生支持多线程录制不同 `CommandList`
|
||||
- **OpenGL**:状态机模型,单线程上下文
|
||||
- **解决方案**:
|
||||
- 抽象层统一提供多线程接口
|
||||
- D3D12 后端:真·并行提交
|
||||
- OpenGL 后端:命令缓冲队列,单线程回放
|
||||
|
||||
### 5.4 高级特性(光线追踪等)
|
||||
- **解决方案**:
|
||||
1. **特性检测**:通过 `RHICapabilities` 查询支持情况
|
||||
2. **降级方案**:不支持时用传统路径替代
|
||||
3. **底层逃逸**:通过 `GetNativeDevice()` 直接访问原生 API
|
||||
|
||||
|
||||
## 6. 后端实现示例
|
||||
### 6.1 D3D12 设备实现(D3D12Device.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include "../RHIDevice.h"
|
||||
#include "D3D12Common.h"
|
||||
|
||||
class D3D12Device : public RHIDevice {
|
||||
public:
|
||||
virtual bool Initialize(const RHIDeviceDesc& desc) override;
|
||||
virtual RHIBuffer* CreateBuffer(const RHIBufferDesc& desc) override;
|
||||
virtual RHITexture* CreateTexture(const RHITextureDesc& desc) override;
|
||||
virtual RHICommandQueue* CreateCommandQueue(const RHICommandQueueDesc& desc) override;
|
||||
virtual RHISwapChain* CreateSwapChain(const RHISwapChainDesc& desc) override;
|
||||
virtual const RHICapabilities& GetCapabilities() const override;
|
||||
virtual void* GetNativeDevice() override { return m_pd3d12Device; }
|
||||
virtual void Shutdown() override;
|
||||
|
||||
// D3D12 特有接口
|
||||
ID3D12Device* GetD3D12Device() const { return m_pd3d12Device; }
|
||||
|
||||
private:
|
||||
ID3D12Device* m_pd3d12Device = nullptr;
|
||||
ID3D12CommandQueue* m_pd3d12CommandQueue = nullptr;
|
||||
RHICapabilities m_capabilities;
|
||||
};
|
||||
```
|
||||
|
||||
### 6.2 OpenGL 设备实现(OpenGLDevice.h)
|
||||
```cpp
|
||||
#pragma once
|
||||
#include "../RHIDevice.h"
|
||||
#include "OpenGLCommon.h"
|
||||
|
||||
class OpenGLDevice : public RHIDevice {
|
||||
public:
|
||||
virtual bool Initialize(const RHIDeviceDesc& desc) override;
|
||||
virtual RHIBuffer* CreateBuffer(const RHIBufferDesc& desc) override;
|
||||
virtual RHITexture* CreateTexture(const RHITextureDesc& desc) override;
|
||||
virtual RHICommandQueue* CreateCommandQueue(const RHICommandQueueDesc& desc) override;
|
||||
virtual RHISwapChain* CreateSwapChain(const RHISwapChainDesc& desc) override;
|
||||
virtual const RHICapabilities& GetCapabilities() const override;
|
||||
virtual void* GetNativeDevice() override { return m_context; }
|
||||
virtual void Shutdown() override;
|
||||
|
||||
// OpenGL 特有接口
|
||||
HGLRC GetContext() const { return m_context; }
|
||||
|
||||
private:
|
||||
HGLRC m_context = nullptr;
|
||||
RHICapabilities m_capabilities;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## 7. 测试用例示例
|
||||
### 7.1 最小渲染循环测试
|
||||
```cpp
|
||||
#include "XCEngine/RHI/RHIFactory.h"
|
||||
#include "XCEngine/RHI/RHIDevice.h"
|
||||
#include "XCEngine/RHI/RHISwapChain.h"
|
||||
#include "XCEngine/RHI/RHICommandList.h"
|
||||
|
||||
int main() {
|
||||
// 1. 创建 RHI 设备(可切换 D3D12/OpenGL)
|
||||
RHIDevice* pDevice = RHIFactory::CreateRHIDevice(RHIType::D3D12);
|
||||
pDevice->Initialize(RHIDeviceDesc{...});
|
||||
|
||||
// 2. 创建交换链和命令队列
|
||||
RHISwapChain* pSwapChain = pDevice->CreateSwapChain(RHISwapChainDesc{...});
|
||||
RHICommandQueue* pCommandQueue = pDevice->CreateCommandQueue(RHICommandQueueDesc{...});
|
||||
RHICommandList* pCommandList = pDevice->CreateCommandList(RHICommandListDesc{...});
|
||||
|
||||
// 3. 渲染循环
|
||||
while (!ShouldQuit()) {
|
||||
// 录制命令
|
||||
pCommandList->Begin();
|
||||
|
||||
// 清屏
|
||||
RHIRenderPassDesc renderPassDesc = {};
|
||||
renderPassDesc.colorAttachments[0].texture = pSwapChain->GetCurrentBackBuffer();
|
||||
renderPassDesc.colorAttachments[0].loadOp = RHIRenderPassLoadOp::Clear;
|
||||
renderPassDesc.colorAttachments[0].clearColor = {0.2f, 0.4f, 0.8f, 1.0f};
|
||||
pCommandList->BeginRenderPass(renderPassDesc);
|
||||
pCommandList->EndRenderPass();
|
||||
|
||||
// 提交命令
|
||||
pCommandList->End();
|
||||
pCommandQueue->SubmitCommandList(pCommandList);
|
||||
|
||||
// 呈现
|
||||
pSwapChain->Present();
|
||||
}
|
||||
|
||||
// 4. 清理
|
||||
pDevice->Shutdown();
|
||||
delete pDevice;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 8. 下一步行动指南
|
||||
1. **补全抽象基类**:重点实现 `RHIDevice`、`RHICommandList`、`RHIPipelineLayout`
|
||||
2. **改造现有后端**:让 `D3D12Device`、`OpenGLDevice` 继承抽象基类并实现接口
|
||||
3. **实现工厂类**:完成 `RHIFactory` 以支持动态切换后端
|
||||
4. **测试验证**:用最小渲染循环测试 D3D12 和 OpenGL 后端
|
||||
5. **扩展功能**:逐步添加资源管理、着色器跨平台、多线程渲染等功能
|
||||
|
||||
|
||||
## 9. 关键注意事项
|
||||
- **上层只调用抽象接口**:绝不直接访问 D3D12/OpenGL 特有类
|
||||
- **合理使用底层逃逸**:仅在必要时使用 `GetNativeDevice()`,并注明破坏跨平台性
|
||||
- **优先保证核心功能**:先实现 90% 常用功能的统一抽象,再处理高级特性
|
||||
- **保持设计可扩展**:为未来支持 Vulkan/Metal 预留空间
|
||||
|
||||
需要我帮你细化某个具体模块的实现代码吗?
|
||||
@@ -6,23 +6,34 @@
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
enum class FenceStatus {
|
||||
Signaled,
|
||||
Unsignaled,
|
||||
Error
|
||||
};
|
||||
|
||||
class OpenGLFence {
|
||||
public:
|
||||
OpenGLFence();
|
||||
~OpenGLFence();
|
||||
|
||||
bool Initialize();
|
||||
bool Initialize(bool signaled = false);
|
||||
void Shutdown();
|
||||
|
||||
void Signal();
|
||||
void Wait();
|
||||
void Signal(uint64_t value);
|
||||
void Wait(uint64_t timeoutNs = UINT64_MAX);
|
||||
void Reset();
|
||||
|
||||
bool IsSignaled() const;
|
||||
FenceStatus GetStatus() const;
|
||||
uint64_t GetCompletedValue() const;
|
||||
uint64_t GetCurrentValue() const { return m_fenceValue; }
|
||||
|
||||
private:
|
||||
unsigned int m_fence;
|
||||
int64_t m_fenceValue;
|
||||
void* m_sync;
|
||||
uint64_t m_fenceValue;
|
||||
uint64_t m_completedValue;
|
||||
bool m_signaled;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,36 +2,71 @@
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLFence.h"
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
OpenGLFence::OpenGLFence() : m_fence(0), m_fenceValue(0), m_signaled(false) {
|
||||
OpenGLFence::OpenGLFence()
|
||||
: m_sync(nullptr)
|
||||
, m_fenceValue(0)
|
||||
, m_completedValue(0)
|
||||
, m_signaled(false) {
|
||||
}
|
||||
|
||||
OpenGLFence::~OpenGLFence() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool OpenGLFence::Initialize() {
|
||||
bool OpenGLFence::Initialize(bool signaled) {
|
||||
m_fenceValue = signaled ? 1 : 0;
|
||||
m_completedValue = m_fenceValue;
|
||||
m_signaled = signaled;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLFence::Shutdown() {
|
||||
if (m_sync) {
|
||||
glDeleteSync(static_cast<GLsync>(m_sync));
|
||||
m_sync = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLFence::Signal() {
|
||||
glFinish();
|
||||
m_signaled = true;
|
||||
glFlush();
|
||||
m_fenceValue++;
|
||||
m_signaled = true;
|
||||
}
|
||||
|
||||
void OpenGLFence::Wait() {
|
||||
glFinish();
|
||||
void OpenGLFence::Signal(uint64_t value) {
|
||||
glFlush();
|
||||
m_fenceValue = value;
|
||||
m_signaled = true;
|
||||
}
|
||||
|
||||
void OpenGLFence::Wait(uint64_t timeoutNs) {
|
||||
if (!m_signaled || !m_sync) {
|
||||
glFinish();
|
||||
m_completedValue = m_fenceValue;
|
||||
m_signaled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
GLsync sync = static_cast<GLsync>(m_sync);
|
||||
GLenum result = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, timeoutNs);
|
||||
while (result == GL_TIMEOUT_EXPIRED && timeoutNs > 0) {
|
||||
result = glClientWaitSync(sync, 0, timeoutNs);
|
||||
}
|
||||
|
||||
if (result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED) {
|
||||
m_completedValue = m_fenceValue;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLFence::Reset() {
|
||||
if (m_sync) {
|
||||
glDeleteSync(static_cast<GLsync>(m_sync));
|
||||
m_sync = nullptr;
|
||||
}
|
||||
m_signaled = false;
|
||||
}
|
||||
|
||||
@@ -39,5 +74,24 @@ bool OpenGLFence::IsSignaled() const {
|
||||
return m_signaled;
|
||||
}
|
||||
|
||||
FenceStatus OpenGLFence::GetStatus() const {
|
||||
if (!m_sync) {
|
||||
return m_signaled ? FenceStatus::Signaled : FenceStatus::Unsignaled;
|
||||
}
|
||||
|
||||
GLsync sync = static_cast<GLsync>(m_sync);
|
||||
GLint status = 0;
|
||||
glGetSynciv(sync, GL_SYNC_STATUS, sizeof(status), nullptr, &status);
|
||||
|
||||
if (status == GL_SIGNALED) {
|
||||
return FenceStatus::Signaled;
|
||||
}
|
||||
return FenceStatus::Unsignaled;
|
||||
}
|
||||
|
||||
uint64_t OpenGLFence::GetCompletedValue() const {
|
||||
return m_completedValue;
|
||||
}
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user