583 lines
33 KiB
C++
583 lines
33 KiB
C++
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
|
|
|
#include "XCUIBackend/XCUIRHICommandCompiler.h"
|
|
|
|
#include <XCEngine/Core/Math/Vector4.h>
|
|
#include <XCEngine/RHI/RHICommandList.h>
|
|
#include <XCEngine/RHI/RHIDevice.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <optional>
|
|
|
|
namespace XCEngine {
|
|
namespace Editor {
|
|
namespace XCUIBackend {
|
|
|
|
namespace {
|
|
|
|
using CommandCompiler = XCUIRHICommandCompiler;
|
|
|
|
struct OverlayConstants { Math::Vector4 viewportSize = Math::Vector4::Zero(); };
|
|
|
|
class TextGlyphProvider final : public CommandCompiler::TextGlyphProvider {
|
|
public:
|
|
TextGlyphProvider(
|
|
const IXCUITextAtlasProvider& atlasProvider,
|
|
IXCUITextAtlasProvider::FontHandle fontHandle,
|
|
const UI::UITextureHandle& textureHandle)
|
|
: m_atlasProvider(atlasProvider), m_fontHandle(fontHandle), m_textureHandle(textureHandle) {}
|
|
|
|
bool BeginText(float requestedFontSize, CommandCompiler::TextRunContext& outContext) const override {
|
|
outContext = {};
|
|
if (!m_fontHandle.IsValid() || !m_textureHandle.IsValid()) return false;
|
|
IXCUITextAtlasProvider::FontInfo fontInfo = {};
|
|
if (!m_atlasProvider.GetFontInfo(m_fontHandle, fontInfo)) return false;
|
|
const float resolvedFontSize = requestedFontSize > 0.0f
|
|
? requestedFontSize
|
|
: (fontInfo.nominalSize > 0.0f ? fontInfo.nominalSize : 16.0f);
|
|
IXCUITextAtlasProvider::BakedFontInfo baked = {};
|
|
if (!m_atlasProvider.GetBakedFontInfo(m_fontHandle, resolvedFontSize, baked)) return false;
|
|
outContext.requestedFontSize = requestedFontSize;
|
|
outContext.resolvedFontSize = resolvedFontSize;
|
|
outContext.lineHeight = baked.lineHeight;
|
|
outContext.texture = m_textureHandle;
|
|
return true;
|
|
}
|
|
|
|
bool ResolveGlyph(
|
|
const CommandCompiler::TextRunContext& context,
|
|
std::uint32_t codepoint,
|
|
CommandCompiler::TextGlyph& outGlyph) const override {
|
|
outGlyph = {};
|
|
IXCUITextAtlasProvider::GlyphInfo glyph = {};
|
|
if (!m_atlasProvider.FindGlyph(m_fontHandle, context.resolvedFontSize, codepoint, glyph)) return false;
|
|
outGlyph.x0 = glyph.x0; outGlyph.y0 = glyph.y0; outGlyph.x1 = glyph.x1; outGlyph.y1 = glyph.y1;
|
|
outGlyph.u0 = glyph.u0; outGlyph.v0 = glyph.v0; outGlyph.u1 = glyph.u1; outGlyph.v1 = glyph.v1;
|
|
outGlyph.advanceX = glyph.advanceX; outGlyph.visible = glyph.visible;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const IXCUITextAtlasProvider& m_atlasProvider;
|
|
IXCUITextAtlasProvider::FontHandle m_fontHandle = {};
|
|
UI::UITextureHandle m_textureHandle = {};
|
|
};
|
|
|
|
constexpr char kColorShader[] =
|
|
"cbuffer OverlayConstants : register(b0) { float4 gViewportSize; };"
|
|
"struct VSInput { float2 position : POSITION; float4 color : COLOR0; };"
|
|
"struct VSOutput { float4 position : SV_POSITION; float4 color : COLOR0; };"
|
|
"VSOutput MainVS(VSInput input) { VSOutput output;"
|
|
" float2 viewport = max(gViewportSize.xy, float2(1.0, 1.0));"
|
|
" float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0, 1.0 - input.position.y / viewport.y * 2.0);"
|
|
" output.position = float4(ndc, 0.0, 1.0); output.color = input.color; return output; }"
|
|
"float4 MainPS(VSOutput input) : SV_TARGET0 { return input.color; }";
|
|
|
|
constexpr char kTexturedShader[] =
|
|
"cbuffer OverlayConstants : register(b0) { float4 gViewportSize; };"
|
|
"Texture2D gOverlayTexture : register(t0); SamplerState gOverlaySampler : register(s0);"
|
|
"struct VSInput { float2 position : POSITION; float2 uv : TEXCOORD0; float4 color : COLOR0; };"
|
|
"struct VSOutput { float4 position : SV_POSITION; float2 uv : TEXCOORD0; float4 color : COLOR0; };"
|
|
"VSOutput MainVS(VSInput input) { VSOutput output;"
|
|
" float2 viewport = max(gViewportSize.xy, float2(1.0, 1.0));"
|
|
" float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0, 1.0 - input.position.y / viewport.y * 2.0);"
|
|
" output.position = float4(ndc, 0.0, 1.0); output.uv = input.uv; output.color = input.color; return output; }"
|
|
"float4 MainPS(VSOutput input) : SV_TARGET0 {"
|
|
" const float4 sampled = gOverlayTexture.Sample(gOverlaySampler, input.uv);"
|
|
" float4 color = sampled * input.color; if (color.a <= 0.001f) { discard; } return color; }";
|
|
|
|
RHI::Rect MakeSurfaceScissorRect(const ::XCEngine::Rendering::RenderSurface& surface) {
|
|
return RHI::Rect{
|
|
0,
|
|
0,
|
|
static_cast<int32_t>(surface.GetWidth()),
|
|
static_cast<int32_t>(surface.GetHeight())
|
|
};
|
|
}
|
|
|
|
RHI::Rect ClampBatchClipRect(
|
|
const ::XCEngine::Rendering::RenderSurface& surface,
|
|
const UI::UIRect& clipRect) {
|
|
const float surfaceWidth = static_cast<float>(surface.GetWidth());
|
|
const float surfaceHeight = static_cast<float>(surface.GetHeight());
|
|
const float minX = (std::max)(0.0f, (std::min)(clipRect.x, surfaceWidth));
|
|
const float minY = (std::max)(0.0f, (std::min)(clipRect.y, surfaceHeight));
|
|
const float maxX = (std::max)(minX, (std::min)(clipRect.x + clipRect.width, surfaceWidth));
|
|
const float maxY = (std::max)(minY, (std::min)(clipRect.y + clipRect.height, surfaceHeight));
|
|
return RHI::Rect{
|
|
static_cast<int32_t>(std::floor(minX)),
|
|
static_cast<int32_t>(std::floor(minY)),
|
|
static_cast<int32_t>(std::ceil(maxX)),
|
|
static_cast<int32_t>(std::ceil(maxY))
|
|
};
|
|
}
|
|
|
|
bool RectEquals(const RHI::Rect& lhs, const RHI::Rect& rhs) {
|
|
return lhs.left == rhs.left &&
|
|
lhs.top == rhs.top &&
|
|
lhs.right == rhs.right &&
|
|
lhs.bottom == rhs.bottom;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void XCUIRHIRenderBackend::SetTextAtlasProvider(const IXCUITextAtlasProvider* provider) {
|
|
if (m_textAtlasProvider == provider) return;
|
|
m_textAtlasProvider = provider;
|
|
ResetFontAtlasResources();
|
|
}
|
|
|
|
void XCUIRHIRenderBackend::Shutdown() { DestroyResources(); }
|
|
void XCUIRHIRenderBackend::ResetStats() { m_lastOverlayStats = {}; }
|
|
|
|
bool XCUIRHIRenderBackend::Render(
|
|
const ::XCEngine::Rendering::RenderContext& renderContext,
|
|
const ::XCEngine::Rendering::RenderSurface& surface,
|
|
const ::XCEngine::UI::UIDrawData& drawData) {
|
|
ResetStats();
|
|
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) return false;
|
|
const auto& colorAttachments = surface.GetColorAttachments();
|
|
if (colorAttachments.empty() || colorAttachments[0] == nullptr || renderContext.commandList == nullptr) return false;
|
|
if (!EnsureInitialized(renderContext)) return false;
|
|
|
|
bool fontAtlasReady = false;
|
|
const IXCUITextAtlasProvider* atlasProvider = ResolveActiveTextAtlasProvider(fontAtlasReady);
|
|
return RenderCompiledDrawData(
|
|
*renderContext.commandList,
|
|
colorAttachments[0],
|
|
surface,
|
|
drawData,
|
|
atlasProvider,
|
|
fontAtlasReady);
|
|
}
|
|
|
|
const IXCUITextAtlasProvider* XCUIRHIRenderBackend::ResolveActiveTextAtlasProvider(
|
|
bool& outFontAtlasReady) {
|
|
outFontAtlasReady = false;
|
|
if (m_textAtlasProvider == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
outFontAtlasReady = EnsureFontAtlasResources(*m_textAtlasProvider);
|
|
return m_textAtlasProvider;
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::EnsureInitialized(const ::XCEngine::Rendering::RenderContext& renderContext) {
|
|
if (m_overlayPipelineState != nullptr &&
|
|
m_overlayPipelineLayout != nullptr &&
|
|
m_overlayConstantPool != nullptr &&
|
|
m_overlayConstantSet != nullptr &&
|
|
m_texturedOverlayPipelineState != nullptr &&
|
|
m_texturedOverlayPipelineLayout != nullptr &&
|
|
m_overlayTexturePool != nullptr &&
|
|
m_overlaySamplerPool != nullptr &&
|
|
m_overlaySamplerSet != nullptr &&
|
|
m_overlaySampler != nullptr &&
|
|
m_device == renderContext.device &&
|
|
m_backendType == renderContext.backendType) {
|
|
return true;
|
|
}
|
|
DestroyResources();
|
|
return CreateResources(renderContext);
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::CreateResources(const ::XCEngine::Rendering::RenderContext& renderContext) {
|
|
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) return false;
|
|
m_device = renderContext.device;
|
|
m_backendType = renderContext.backendType;
|
|
return CreateOverlayResources() && CreateTexturedOverlayResources();
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::CreateOverlayResources() {
|
|
RHI::DescriptorSetLayoutBinding binding = { 0, static_cast<uint32_t>(RHI::DescriptorType::CBV), 1 };
|
|
RHI::DescriptorSetLayoutDesc layout = { &binding, 1 };
|
|
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
pipelineLayoutDesc.setLayouts = &layout;
|
|
pipelineLayoutDesc.setLayoutCount = 1;
|
|
m_overlayPipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
|
if (m_overlayPipelineLayout == nullptr) { DestroyResources(); return false; }
|
|
|
|
RHI::DescriptorPoolDesc poolDesc = {};
|
|
poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
poolDesc.descriptorCount = 1;
|
|
poolDesc.shaderVisible = false;
|
|
m_overlayConstantPool = m_device->CreateDescriptorPool(poolDesc);
|
|
if (m_overlayConstantPool == nullptr) { DestroyResources(); return false; }
|
|
m_overlayConstantSet = m_overlayConstantPool->AllocateSet(layout);
|
|
if (m_overlayConstantSet == nullptr) { DestroyResources(); return false; }
|
|
|
|
RHI::GraphicsPipelineDesc desc = {};
|
|
desc.pipelineLayout = m_overlayPipelineLayout;
|
|
desc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
|
desc.renderTargetCount = 1;
|
|
desc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
|
desc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
|
desc.sampleCount = 1;
|
|
desc.inputLayout.elements = {
|
|
{ "POSITION", 0, static_cast<uint32_t>(RHI::Format::R32G32_Float), 0, 0, 0, 0 },
|
|
{ "COLOR", 0, static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float), 0, 8, 0, 0 }
|
|
};
|
|
desc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
|
desc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
|
desc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
|
desc.rasterizerState.depthClipEnable = true;
|
|
desc.blendState.blendEnable = true;
|
|
desc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
|
desc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
desc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
|
desc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
desc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
desc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
desc.blendState.colorWriteMask = 0xF;
|
|
desc.depthStencilState.depthTestEnable = false;
|
|
desc.depthStencilState.depthWriteEnable = false;
|
|
desc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
|
desc.vertexShader.source.assign(kColorShader, kColorShader + sizeof(kColorShader) - 1);
|
|
desc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
|
desc.vertexShader.entryPoint = L"MainVS";
|
|
desc.vertexShader.profile = L"vs_5_0";
|
|
desc.fragmentShader.source.assign(kColorShader, kColorShader + sizeof(kColorShader) - 1);
|
|
desc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
|
desc.fragmentShader.entryPoint = L"MainPS";
|
|
desc.fragmentShader.profile = L"ps_5_0";
|
|
m_overlayPipelineState = m_device->CreatePipelineState(desc);
|
|
if (m_overlayPipelineState == nullptr || !m_overlayPipelineState->IsValid()) { DestroyResources(); return false; }
|
|
return true;
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::CreateTexturedOverlayResources() {
|
|
RHI::DescriptorSetLayoutBinding constantBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::CBV), 1 };
|
|
RHI::DescriptorSetLayoutBinding textureBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::SRV), 1 };
|
|
RHI::DescriptorSetLayoutBinding samplerBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::Sampler), 1 };
|
|
RHI::DescriptorSetLayoutDesc setLayouts[3] = { { &constantBinding, 1 }, { &textureBinding, 1 }, { &samplerBinding, 1 } };
|
|
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
pipelineLayoutDesc.setLayouts = setLayouts;
|
|
pipelineLayoutDesc.setLayoutCount = 3;
|
|
m_texturedOverlayPipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
|
if (m_texturedOverlayPipelineLayout == nullptr) { DestroyResources(); return false; }
|
|
|
|
RHI::DescriptorPoolDesc texturePoolDesc = {};
|
|
texturePoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
texturePoolDesc.descriptorCount = 64;
|
|
texturePoolDesc.shaderVisible = true;
|
|
m_overlayTexturePool = m_device->CreateDescriptorPool(texturePoolDesc);
|
|
if (m_overlayTexturePool == nullptr) { DestroyResources(); return false; }
|
|
|
|
RHI::DescriptorPoolDesc samplerPoolDesc = {};
|
|
samplerPoolDesc.type = RHI::DescriptorHeapType::Sampler;
|
|
samplerPoolDesc.descriptorCount = 1;
|
|
samplerPoolDesc.shaderVisible = true;
|
|
m_overlaySamplerPool = m_device->CreateDescriptorPool(samplerPoolDesc);
|
|
if (m_overlaySamplerPool == nullptr) { DestroyResources(); return false; }
|
|
m_overlaySamplerSet = m_overlaySamplerPool->AllocateSet(setLayouts[2]);
|
|
if (m_overlaySamplerSet == nullptr) { DestroyResources(); return false; }
|
|
|
|
RHI::SamplerDesc samplerDesc = {};
|
|
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
|
|
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
|
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
|
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
|
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
|
samplerDesc.maxLod = 1000.0f;
|
|
m_overlaySampler = m_device->CreateSampler(samplerDesc);
|
|
if (m_overlaySampler == nullptr) { DestroyResources(); return false; }
|
|
m_overlaySamplerSet->UpdateSampler(0, m_overlaySampler);
|
|
|
|
RHI::GraphicsPipelineDesc desc = {};
|
|
desc.pipelineLayout = m_texturedOverlayPipelineLayout;
|
|
desc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
|
desc.renderTargetCount = 1;
|
|
desc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
|
desc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
|
desc.sampleCount = 1;
|
|
desc.inputLayout.elements = {
|
|
{ "POSITION", 0, static_cast<uint32_t>(RHI::Format::R32G32_Float), 0, 0, 0, 0 },
|
|
{ "TEXCOORD", 0, static_cast<uint32_t>(RHI::Format::R32G32_Float), 0, 8, 0, 0 },
|
|
{ "COLOR", 0, static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float), 0, 16, 0, 0 }
|
|
};
|
|
desc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
|
desc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
|
desc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
|
desc.rasterizerState.depthClipEnable = true;
|
|
desc.blendState.blendEnable = true;
|
|
desc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
|
desc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
desc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
|
desc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
desc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
desc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
desc.blendState.colorWriteMask = 0xF;
|
|
desc.depthStencilState.depthTestEnable = false;
|
|
desc.depthStencilState.depthWriteEnable = false;
|
|
desc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
|
desc.vertexShader.source.assign(kTexturedShader, kTexturedShader + sizeof(kTexturedShader) - 1);
|
|
desc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
|
desc.vertexShader.entryPoint = L"MainVS";
|
|
desc.vertexShader.profile = L"vs_5_0";
|
|
desc.fragmentShader.source.assign(kTexturedShader, kTexturedShader + sizeof(kTexturedShader) - 1);
|
|
desc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
|
desc.fragmentShader.entryPoint = L"MainPS";
|
|
desc.fragmentShader.profile = L"ps_5_0";
|
|
m_texturedOverlayPipelineState = m_device->CreatePipelineState(desc);
|
|
if (m_texturedOverlayPipelineState == nullptr || !m_texturedOverlayPipelineState->IsValid()) { DestroyResources(); return false; }
|
|
return true;
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::EnsureOverlayVertexBufferCapacity(std::size_t requiredVertexCount) {
|
|
const std::uint64_t requiredBytes = static_cast<std::uint64_t>(requiredVertexCount * sizeof(CommandCompiler::ColorVertex));
|
|
if (requiredBytes == 0u) return true;
|
|
if (m_overlayVertexBuffer != nullptr && m_overlayVertexBufferCapacity >= requiredBytes) return true;
|
|
if (m_overlayVertexBufferView != nullptr) { m_overlayVertexBufferView->Shutdown(); delete m_overlayVertexBufferView; m_overlayVertexBufferView = nullptr; }
|
|
if (m_overlayVertexBuffer != nullptr) { m_overlayVertexBuffer->Shutdown(); delete m_overlayVertexBuffer; m_overlayVertexBuffer = nullptr; }
|
|
m_overlayVertexBufferCapacity = (std::max<std::uint64_t>)(requiredBytes, 4096u);
|
|
RHI::BufferDesc bufferDesc = {};
|
|
bufferDesc.size = m_overlayVertexBufferCapacity;
|
|
bufferDesc.stride = static_cast<std::uint32_t>(sizeof(CommandCompiler::ColorVertex));
|
|
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Vertex);
|
|
m_overlayVertexBuffer = m_device->CreateBuffer(bufferDesc);
|
|
if (m_overlayVertexBuffer == nullptr) { m_overlayVertexBufferCapacity = 0u; return false; }
|
|
m_overlayVertexBuffer->SetStride(bufferDesc.stride);
|
|
m_overlayVertexBuffer->SetBufferType(RHI::BufferType::Vertex);
|
|
RHI::ResourceViewDesc viewDesc = {};
|
|
viewDesc.dimension = RHI::ResourceViewDimension::Buffer;
|
|
viewDesc.structureByteStride = bufferDesc.stride;
|
|
m_overlayVertexBufferView = m_device->CreateVertexBufferView(m_overlayVertexBuffer, viewDesc);
|
|
return m_overlayVertexBufferView != nullptr;
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::EnsureTexturedOverlayVertexBufferCapacity(std::size_t requiredVertexCount) {
|
|
const std::uint64_t requiredBytes = static_cast<std::uint64_t>(requiredVertexCount * sizeof(CommandCompiler::TexturedVertex));
|
|
if (requiredBytes == 0u) return true;
|
|
if (m_texturedOverlayVertexBuffer != nullptr && m_texturedOverlayVertexBufferCapacity >= requiredBytes) return true;
|
|
if (m_texturedOverlayVertexBufferView != nullptr) { m_texturedOverlayVertexBufferView->Shutdown(); delete m_texturedOverlayVertexBufferView; m_texturedOverlayVertexBufferView = nullptr; }
|
|
if (m_texturedOverlayVertexBuffer != nullptr) { m_texturedOverlayVertexBuffer->Shutdown(); delete m_texturedOverlayVertexBuffer; m_texturedOverlayVertexBuffer = nullptr; }
|
|
m_texturedOverlayVertexBufferCapacity = (std::max<std::uint64_t>)(requiredBytes, 4096u);
|
|
RHI::BufferDesc bufferDesc = {};
|
|
bufferDesc.size = m_texturedOverlayVertexBufferCapacity;
|
|
bufferDesc.stride = static_cast<std::uint32_t>(sizeof(CommandCompiler::TexturedVertex));
|
|
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Vertex);
|
|
m_texturedOverlayVertexBuffer = m_device->CreateBuffer(bufferDesc);
|
|
if (m_texturedOverlayVertexBuffer == nullptr) { m_texturedOverlayVertexBufferCapacity = 0u; return false; }
|
|
m_texturedOverlayVertexBuffer->SetStride(bufferDesc.stride);
|
|
m_texturedOverlayVertexBuffer->SetBufferType(RHI::BufferType::Vertex);
|
|
RHI::ResourceViewDesc viewDesc = {};
|
|
viewDesc.dimension = RHI::ResourceViewDimension::Buffer;
|
|
viewDesc.structureByteStride = bufferDesc.stride;
|
|
m_texturedOverlayVertexBufferView = m_device->CreateVertexBufferView(m_texturedOverlayVertexBuffer, viewDesc);
|
|
return m_texturedOverlayVertexBufferView != nullptr;
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::EnsureFontAtlasResources(const IXCUITextAtlasProvider& atlasProvider) {
|
|
if (m_overlayTexturePool == nullptr) return false;
|
|
IXCUITextAtlasProvider::AtlasTextureView atlasView = {};
|
|
if (!atlasProvider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView) ||
|
|
!atlasView.IsValid() || atlasView.bytesPerPixel != 4) {
|
|
return false;
|
|
}
|
|
const TextFontHandle font = atlasProvider.GetDefaultFont();
|
|
if (!font.IsValid()) return false;
|
|
if (m_overlayFontTexture != nullptr && m_overlayFontTextureView != nullptr && m_overlayFontTextureSet != nullptr &&
|
|
m_overlayFont.value == font.value &&
|
|
m_fontAtlasStorageKey == atlasView.atlasStorageKey &&
|
|
m_fontAtlasPixelDataKey == atlasView.pixelDataKey) {
|
|
return true;
|
|
}
|
|
ResetFontAtlasResources();
|
|
RHI::TextureDesc textureDesc = {};
|
|
textureDesc.width = static_cast<std::uint32_t>(atlasView.width);
|
|
textureDesc.height = static_cast<std::uint32_t>(atlasView.height);
|
|
textureDesc.depth = 1; textureDesc.mipLevels = 1; textureDesc.arraySize = 1;
|
|
textureDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
|
textureDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
|
|
textureDesc.sampleCount = 1;
|
|
m_overlayFontTexture = m_device->CreateTexture(
|
|
textureDesc,
|
|
atlasView.pixels,
|
|
static_cast<std::size_t>(atlasView.width) * static_cast<std::size_t>(atlasView.height) * static_cast<std::size_t>(atlasView.bytesPerPixel),
|
|
static_cast<std::uint32_t>(atlasView.stride));
|
|
if (m_overlayFontTexture == nullptr) { ResetFontAtlasResources(); return false; }
|
|
RHI::ResourceViewDesc viewDesc = {};
|
|
viewDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
|
viewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
|
viewDesc.mipLevel = 0;
|
|
m_overlayFontTextureView = m_device->CreateShaderResourceView(m_overlayFontTexture, viewDesc);
|
|
if (m_overlayFontTextureView == nullptr) { ResetFontAtlasResources(); return false; }
|
|
RHI::DescriptorSetLayoutBinding textureBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::SRV), 1 };
|
|
RHI::DescriptorSetLayoutDesc textureLayout = { &textureBinding, 1 };
|
|
m_overlayFontTextureSet = m_overlayTexturePool->AllocateSet(textureLayout);
|
|
if (m_overlayFontTextureSet == nullptr) { ResetFontAtlasResources(); return false; }
|
|
m_overlayFontTextureSet->Update(0, m_overlayFontTextureView);
|
|
m_overlayFont = font;
|
|
m_overlayFontTextureHandle.nativeHandle = reinterpret_cast<std::uintptr_t>(m_overlayFontTextureView);
|
|
m_overlayFontTextureHandle.width = static_cast<std::uint32_t>(atlasView.width);
|
|
m_overlayFontTextureHandle.height = static_cast<std::uint32_t>(atlasView.height);
|
|
m_overlayFontTextureHandle.kind = UI::UITextureHandleKind::ShaderResourceView;
|
|
m_fontAtlasStorageKey = atlasView.atlasStorageKey;
|
|
m_fontAtlasPixelDataKey = atlasView.pixelDataKey;
|
|
return true;
|
|
}
|
|
|
|
::XCEngine::RHI::RHIDescriptorSet* XCUIRHIRenderBackend::ResolveTextureSet(
|
|
const ::XCEngine::UI::UITextureHandle& texture,
|
|
bool* outCacheHit) {
|
|
if (outCacheHit != nullptr) {
|
|
*outCacheHit = false;
|
|
}
|
|
if (!texture.IsValid() || texture.kind != UI::UITextureHandleKind::ShaderResourceView || m_overlayTexturePool == nullptr) return nullptr;
|
|
if (m_overlayFontTextureSet != nullptr && m_overlayFontTextureHandle.IsValid() && texture.nativeHandle == m_overlayFontTextureHandle.nativeHandle) {
|
|
if (outCacheHit != nullptr) {
|
|
*outCacheHit = true;
|
|
}
|
|
return m_overlayFontTextureSet;
|
|
}
|
|
for (const ExternalTextureBinding& binding : m_externalTextureBindings) {
|
|
if (binding.handleKey == texture.nativeHandle && binding.textureSet != nullptr && binding.shaderView != nullptr) {
|
|
if (outCacheHit != nullptr) {
|
|
*outCacheHit = true;
|
|
}
|
|
return binding.textureSet;
|
|
}
|
|
}
|
|
RHI::RHIResourceView* shaderView = reinterpret_cast<RHI::RHIResourceView*>(texture.nativeHandle);
|
|
if (shaderView == nullptr || !shaderView->IsValid() || shaderView->GetViewType() != RHI::ResourceViewType::ShaderResource) return nullptr;
|
|
RHI::DescriptorSetLayoutBinding textureBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::SRV), 1 };
|
|
RHI::DescriptorSetLayoutDesc textureLayout = { &textureBinding, 1 };
|
|
RHI::RHIDescriptorSet* textureSet = m_overlayTexturePool->AllocateSet(textureLayout);
|
|
if (textureSet == nullptr) return nullptr;
|
|
textureSet->Update(0, shaderView);
|
|
m_externalTextureBindings.push_back({ texture.nativeHandle, shaderView, textureSet });
|
|
return textureSet;
|
|
}
|
|
|
|
bool XCUIRHIRenderBackend::RenderCompiledDrawData(
|
|
::XCEngine::RHI::RHICommandList& commandList,
|
|
::XCEngine::RHI::RHIResourceView* renderTarget,
|
|
const ::XCEngine::Rendering::RenderSurface& surface,
|
|
const ::XCEngine::UI::UIDrawData& drawData,
|
|
const IXCUITextAtlasProvider* atlasProvider,
|
|
bool fontAtlasReady) {
|
|
CommandCompiler compiler = {};
|
|
CommandCompiler::CompileConfig config = {};
|
|
config.surfaceClipRect = UI::UIRect(0.0f, 0.0f, static_cast<float>(surface.GetWidth()), static_cast<float>(surface.GetHeight()));
|
|
std::optional<TextGlyphProvider> glyphProvider = std::nullopt;
|
|
if (fontAtlasReady && atlasProvider != nullptr) {
|
|
glyphProvider.emplace(*atlasProvider, m_overlayFont, m_overlayFontTextureHandle);
|
|
config.textGlyphProvider = &(*glyphProvider);
|
|
}
|
|
CommandCompiler::CompiledDrawData compiled = {};
|
|
compiler.Compile(drawData, config, compiled);
|
|
m_lastOverlayStats.drawListCount = compiled.stats.drawListCount;
|
|
m_lastOverlayStats.commandCount = compiled.stats.commandCount;
|
|
m_lastOverlayStats.batchCount = compiled.stats.batchCount;
|
|
m_lastOverlayStats.skippedCommandCount = compiled.stats.skippedCommandCount;
|
|
m_lastOverlayStats.vertexCount = compiled.stats.colorVertexCount + compiled.stats.texturedVertexCount;
|
|
m_lastOverlayStats.triangleCount = compiled.stats.triangleCount;
|
|
if (compiled.Empty()) return true;
|
|
if (!compiled.colorVertices.empty() && !EnsureOverlayVertexBufferCapacity(compiled.colorVertices.size())) return false;
|
|
if (!compiled.texturedVertices.empty() && !EnsureTexturedOverlayVertexBufferCapacity(compiled.texturedVertices.size())) return false;
|
|
if (!compiled.colorVertices.empty()) m_overlayVertexBuffer->SetData(compiled.colorVertices.data(), compiled.colorVertices.size() * sizeof(CommandCompiler::ColorVertex));
|
|
if (!compiled.texturedVertices.empty()) m_texturedOverlayVertexBuffer->SetData(compiled.texturedVertices.data(), compiled.texturedVertices.size() * sizeof(CommandCompiler::TexturedVertex));
|
|
OverlayConstants constants = {};
|
|
constants.viewportSize = Math::Vector4(static_cast<float>(surface.GetWidth()), static_cast<float>(surface.GetHeight()), 0.0f, 0.0f);
|
|
m_overlayConstantSet->WriteConstant(0, &constants, sizeof(constants));
|
|
commandList.SetRenderTargets(1, &renderTarget, nullptr);
|
|
const RHI::Viewport viewport = { 0.0f, 0.0f, static_cast<float>(surface.GetWidth()), static_cast<float>(surface.GetHeight()), 0.0f, 1.0f };
|
|
const RHI::Rect fullSurfaceScissorRect = MakeSurfaceScissorRect(surface);
|
|
commandList.SetViewport(viewport);
|
|
commandList.SetScissorRect(fullSurfaceScissorRect);
|
|
commandList.SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
|
RHI::Rect currentScissorRect = fullSurfaceScissorRect;
|
|
for (const CommandCompiler::Batch& batch : compiled.batches) {
|
|
if (batch.vertexCount == 0u) continue;
|
|
const RHI::Rect batchScissorRect = ClampBatchClipRect(surface, batch.clipRect);
|
|
if (!RectEquals(currentScissorRect, batchScissorRect)) {
|
|
commandList.SetScissorRect(batchScissorRect);
|
|
currentScissorRect = batchScissorRect;
|
|
}
|
|
if (!RectEquals(batchScissorRect, fullSurfaceScissorRect)) {
|
|
++m_lastOverlayStats.scissoredBatchCount;
|
|
}
|
|
if (batch.kind == CommandCompiler::BatchKind::Color) {
|
|
++m_lastOverlayStats.colorBatchCount;
|
|
m_lastOverlayStats.renderedCommandCount += batch.commandCount;
|
|
commandList.SetPipelineState(m_overlayPipelineState);
|
|
RHI::RHIResourceView* vertexBuffers[] = { m_overlayVertexBufferView };
|
|
const std::uint64_t offsets[] = { 0u };
|
|
const std::uint32_t strides[] = { static_cast<std::uint32_t>(sizeof(CommandCompiler::ColorVertex)) };
|
|
commandList.SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
|
RHI::RHIDescriptorSet* descriptorSets[] = { m_overlayConstantSet };
|
|
commandList.SetGraphicsDescriptorSets(0, 1, descriptorSets, m_overlayPipelineLayout);
|
|
commandList.Draw(batch.vertexCount, 1, batch.firstVertex, 0);
|
|
continue;
|
|
}
|
|
++m_lastOverlayStats.texturedBatchCount;
|
|
++m_lastOverlayStats.textureResolveCount;
|
|
bool textureCacheHit = false;
|
|
RHI::RHIDescriptorSet* textureSet = ResolveTextureSet(batch.texture, &textureCacheHit);
|
|
if (textureCacheHit) {
|
|
++m_lastOverlayStats.textureCacheHitCount;
|
|
}
|
|
if (textureSet == nullptr || m_overlaySamplerSet == nullptr) {
|
|
m_lastOverlayStats.skippedCommandCount += batch.commandCount;
|
|
continue;
|
|
}
|
|
m_lastOverlayStats.renderedCommandCount += batch.commandCount;
|
|
commandList.SetPipelineState(m_texturedOverlayPipelineState);
|
|
RHI::RHIResourceView* vertexBuffers[] = { m_texturedOverlayVertexBufferView };
|
|
const std::uint64_t offsets[] = { 0u };
|
|
const std::uint32_t strides[] = { static_cast<std::uint32_t>(sizeof(CommandCompiler::TexturedVertex)) };
|
|
commandList.SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
|
RHI::RHIDescriptorSet* descriptorSets[] = { m_overlayConstantSet, textureSet, m_overlaySamplerSet };
|
|
commandList.SetGraphicsDescriptorSets(0, 3, descriptorSets, m_texturedOverlayPipelineLayout);
|
|
commandList.Draw(batch.vertexCount, 1, batch.firstVertex, 0);
|
|
}
|
|
if (!RectEquals(currentScissorRect, fullSurfaceScissorRect)) {
|
|
commandList.SetScissorRect(fullSurfaceScissorRect);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void XCUIRHIRenderBackend::ResetFontAtlasResources() {
|
|
if (m_overlayFontTextureSet != nullptr) { m_overlayFontTextureSet->Shutdown(); delete m_overlayFontTextureSet; m_overlayFontTextureSet = nullptr; }
|
|
if (m_overlayFontTextureView != nullptr) { m_overlayFontTextureView->Shutdown(); delete m_overlayFontTextureView; m_overlayFontTextureView = nullptr; }
|
|
if (m_overlayFontTexture != nullptr) { m_overlayFontTexture->Shutdown(); delete m_overlayFontTexture; m_overlayFontTexture = nullptr; }
|
|
m_overlayFont = {};
|
|
m_overlayFontTextureHandle = {};
|
|
m_fontAtlasStorageKey = 0;
|
|
m_fontAtlasPixelDataKey = 0;
|
|
}
|
|
|
|
void XCUIRHIRenderBackend::DestroyResources() {
|
|
for (ExternalTextureBinding& binding : m_externalTextureBindings) {
|
|
if (binding.textureSet != nullptr) { binding.textureSet->Shutdown(); delete binding.textureSet; binding.textureSet = nullptr; }
|
|
binding.shaderView = nullptr;
|
|
binding.handleKey = 0;
|
|
}
|
|
m_externalTextureBindings.clear();
|
|
if (m_texturedOverlayVertexBufferView != nullptr) { m_texturedOverlayVertexBufferView->Shutdown(); delete m_texturedOverlayVertexBufferView; m_texturedOverlayVertexBufferView = nullptr; }
|
|
if (m_texturedOverlayVertexBuffer != nullptr) { m_texturedOverlayVertexBuffer->Shutdown(); delete m_texturedOverlayVertexBuffer; m_texturedOverlayVertexBuffer = nullptr; }
|
|
if (m_overlayVertexBufferView != nullptr) { m_overlayVertexBufferView->Shutdown(); delete m_overlayVertexBufferView; m_overlayVertexBufferView = nullptr; }
|
|
if (m_overlayVertexBuffer != nullptr) { m_overlayVertexBuffer->Shutdown(); delete m_overlayVertexBuffer; m_overlayVertexBuffer = nullptr; }
|
|
ResetFontAtlasResources();
|
|
if (m_texturedOverlayPipelineState != nullptr) { m_texturedOverlayPipelineState->Shutdown(); delete m_texturedOverlayPipelineState; m_texturedOverlayPipelineState = nullptr; }
|
|
if (m_overlaySamplerSet != nullptr) { m_overlaySamplerSet->Shutdown(); delete m_overlaySamplerSet; m_overlaySamplerSet = nullptr; }
|
|
if (m_overlaySampler != nullptr) { m_overlaySampler->Shutdown(); delete m_overlaySampler; m_overlaySampler = nullptr; }
|
|
if (m_overlaySamplerPool != nullptr) { m_overlaySamplerPool->Shutdown(); delete m_overlaySamplerPool; m_overlaySamplerPool = nullptr; }
|
|
if (m_overlayTexturePool != nullptr) { m_overlayTexturePool->Shutdown(); delete m_overlayTexturePool; m_overlayTexturePool = nullptr; }
|
|
if (m_texturedOverlayPipelineLayout != nullptr) { m_texturedOverlayPipelineLayout->Shutdown(); delete m_texturedOverlayPipelineLayout; m_texturedOverlayPipelineLayout = nullptr; }
|
|
if (m_overlayPipelineState != nullptr) { m_overlayPipelineState->Shutdown(); delete m_overlayPipelineState; m_overlayPipelineState = nullptr; }
|
|
if (m_overlayConstantSet != nullptr) { m_overlayConstantSet->Shutdown(); delete m_overlayConstantSet; m_overlayConstantSet = nullptr; }
|
|
if (m_overlayConstantPool != nullptr) { m_overlayConstantPool->Shutdown(); delete m_overlayConstantPool; m_overlayConstantPool = nullptr; }
|
|
if (m_overlayPipelineLayout != nullptr) { m_overlayPipelineLayout->Shutdown(); delete m_overlayPipelineLayout; m_overlayPipelineLayout = nullptr; }
|
|
m_overlayVertexBufferCapacity = 0u;
|
|
m_texturedOverlayVertexBufferCapacity = 0u;
|
|
m_lastOverlayStats = {};
|
|
m_device = nullptr;
|
|
m_backendType = RHI::RHIType::D3D12;
|
|
}
|
|
|
|
} // namespace XCUIBackend
|
|
} // namespace Editor
|
|
} // namespace XCEngine
|