Add material render state and pipeline caching
This commit is contained in:
@@ -86,7 +86,10 @@ void main() {
|
||||
}
|
||||
)";
|
||||
|
||||
RHI::GraphicsPipelineDesc CreatePipelineDesc(RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout) {
|
||||
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIType backendType,
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
const Resources::Material* material) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
@@ -94,16 +97,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(RHI::RHIType backendType, RHI::RHIP
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
|
||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||
pipelineDesc.rasterizerState.depthClipEnable = true;
|
||||
|
||||
pipelineDesc.depthStencilState.depthTestEnable = true;
|
||||
pipelineDesc.depthStencilState.depthWriteEnable = true;
|
||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Less);
|
||||
pipelineDesc.depthStencilState.stencilEnable = false;
|
||||
ApplyMaterialRenderState(material, pipelineDesc);
|
||||
|
||||
RHI::InputElementDesc position = {};
|
||||
position.semanticName = "POSITION";
|
||||
@@ -248,14 +242,24 @@ bool BuiltinForwardPipeline::Render(
|
||||
commandList->ClearDepthStencil(surface.GetDepthAttachment(), 1.0f, 0);
|
||||
}
|
||||
|
||||
commandList->SetPipelineState(m_pipelineState);
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
|
||||
RHI::RHIPipelineState* currentPipelineState = nullptr;
|
||||
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
|
||||
if (!MatchesBuiltinPass(ResolveMaterial(visibleItem), BuiltinMaterialPass::Forward)) {
|
||||
const Resources::Material* material = ResolveMaterial(visibleItem);
|
||||
if (!MatchesBuiltinPass(material, BuiltinMaterialPass::Forward)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material);
|
||||
if (pipelineState == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (pipelineState != currentPipelineState) {
|
||||
commandList->SetPipelineState(pipelineState);
|
||||
currentPipelineState = pipelineState;
|
||||
}
|
||||
|
||||
DrawVisibleItem(context, sceneData, visibleItem);
|
||||
}
|
||||
|
||||
@@ -387,12 +391,6 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex
|
||||
return false;
|
||||
}
|
||||
|
||||
const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc(context.backendType, m_pipelineLayout);
|
||||
m_pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::SamplerDesc samplerDesc = {};
|
||||
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
|
||||
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
@@ -441,6 +439,14 @@ bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& contex
|
||||
void BuiltinForwardPipeline::DestroyPipelineResources() {
|
||||
m_resourceCache.Shutdown();
|
||||
|
||||
for (auto& pipelinePair : m_pipelineStates) {
|
||||
if (pipelinePair.second != nullptr) {
|
||||
pipelinePair.second->Shutdown();
|
||||
delete pipelinePair.second;
|
||||
}
|
||||
}
|
||||
m_pipelineStates.clear();
|
||||
|
||||
if (m_fallbackTextureView != nullptr) {
|
||||
m_fallbackTextureView->Shutdown();
|
||||
delete m_fallbackTextureView;
|
||||
@@ -459,12 +465,6 @@ void BuiltinForwardPipeline::DestroyPipelineResources() {
|
||||
m_sampler = nullptr;
|
||||
}
|
||||
|
||||
if (m_pipelineState != nullptr) {
|
||||
m_pipelineState->Shutdown();
|
||||
delete m_pipelineState;
|
||||
m_pipelineState = nullptr;
|
||||
}
|
||||
|
||||
if (m_pipelineLayout != nullptr) {
|
||||
m_pipelineLayout->Shutdown();
|
||||
delete m_pipelineLayout;
|
||||
@@ -511,6 +511,32 @@ void BuiltinForwardPipeline::DestroyPipelineResources() {
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState(
|
||||
const RenderContext& context,
|
||||
const Resources::Material* material) {
|
||||
const Resources::MaterialRenderState renderState =
|
||||
material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState();
|
||||
|
||||
const auto existing = m_pipelineStates.find(renderState);
|
||||
if (existing != m_pipelineStates.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
const RHI::GraphicsPipelineDesc pipelineDesc =
|
||||
CreatePipelineDesc(context.backendType, m_pipelineLayout, material);
|
||||
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
||||
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
||||
if (pipelineState != nullptr) {
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_pipelineStates.emplace(renderState, pipelineState);
|
||||
return pipelineState;
|
||||
}
|
||||
|
||||
const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const {
|
||||
return material != nullptr ? FindMaterialTexture(*material) : nullptr;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ Material::~Material() = default;
|
||||
void Material::Release() {
|
||||
m_shader.Reset();
|
||||
m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||
m_renderState = MaterialRenderState();
|
||||
m_shaderPass.Clear();
|
||||
m_tags.Clear();
|
||||
m_properties.Clear();
|
||||
@@ -34,6 +35,11 @@ void Material::SetRenderQueue(MaterialRenderQueue renderQueue) {
|
||||
SetRenderQueue(static_cast<Core::int32>(renderQueue));
|
||||
}
|
||||
|
||||
void Material::SetRenderState(const MaterialRenderState& renderState) {
|
||||
m_renderState = renderState;
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::SetShaderPass(const Containers::String& shaderPass) {
|
||||
m_shaderPass = shaderPass;
|
||||
UpdateMemorySize();
|
||||
@@ -267,6 +273,7 @@ void Material::ClearAllProperties() {
|
||||
|
||||
void Material::UpdateMemorySize() {
|
||||
m_memorySize = m_constantBufferData.Size() +
|
||||
sizeof(MaterialRenderState) +
|
||||
m_shaderPass.Length() +
|
||||
m_tags.Size() * sizeof(TagEntry) +
|
||||
m_textureBindings.Size() * sizeof(TextureBinding) +
|
||||
|
||||
@@ -109,6 +109,24 @@ bool TryParseIntValue(const std::string& json, const char* key, Core::int32& out
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryParseBoolValue(const std::string& json, const char* key, bool& outValue) {
|
||||
size_t valuePos = 0;
|
||||
if (!FindValueStart(json, key, valuePos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (json.compare(valuePos, 4, "true") == 0) {
|
||||
outValue = true;
|
||||
return true;
|
||||
}
|
||||
if (json.compare(valuePos, 5, "false") == 0) {
|
||||
outValue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) {
|
||||
size_t valuePos = 0;
|
||||
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '{') {
|
||||
@@ -234,6 +252,270 @@ bool TryParseTagMap(const std::string& objectText, Material* material) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseCullMode(const Containers::String& value, MaterialCullMode& outMode) {
|
||||
const Containers::String normalized = value.Trim().ToLower();
|
||||
if (normalized == "none" || normalized == "off") {
|
||||
outMode = MaterialCullMode::None;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "front") {
|
||||
outMode = MaterialCullMode::Front;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "back") {
|
||||
outMode = MaterialCullMode::Back;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseComparisonFunc(const Containers::String& value, MaterialComparisonFunc& outFunc) {
|
||||
const Containers::String normalized = value.Trim().ToLower();
|
||||
if (normalized == "never") {
|
||||
outFunc = MaterialComparisonFunc::Never;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "less") {
|
||||
outFunc = MaterialComparisonFunc::Less;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "equal") {
|
||||
outFunc = MaterialComparisonFunc::Equal;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "lessequal" || normalized == "less_equal" || normalized == "lequal") {
|
||||
outFunc = MaterialComparisonFunc::LessEqual;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "greater") {
|
||||
outFunc = MaterialComparisonFunc::Greater;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "notequal" || normalized == "not_equal") {
|
||||
outFunc = MaterialComparisonFunc::NotEqual;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "greaterequal" || normalized == "greater_equal" || normalized == "gequal") {
|
||||
outFunc = MaterialComparisonFunc::GreaterEqual;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "always") {
|
||||
outFunc = MaterialComparisonFunc::Always;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseBlendFactor(const Containers::String& value, MaterialBlendFactor& outFactor) {
|
||||
const Containers::String normalized = value.Trim().ToLower();
|
||||
if (normalized == "zero") {
|
||||
outFactor = MaterialBlendFactor::Zero;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "one") {
|
||||
outFactor = MaterialBlendFactor::One;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "srccolor" || normalized == "src_color") {
|
||||
outFactor = MaterialBlendFactor::SrcColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invsrccolor" || normalized == "inv_src_color" || normalized == "one_minus_src_color") {
|
||||
outFactor = MaterialBlendFactor::InvSrcColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "srcalpha" || normalized == "src_alpha") {
|
||||
outFactor = MaterialBlendFactor::SrcAlpha;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invsrcalpha" || normalized == "inv_src_alpha" || normalized == "one_minus_src_alpha") {
|
||||
outFactor = MaterialBlendFactor::InvSrcAlpha;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "dstalpha" || normalized == "dst_alpha") {
|
||||
outFactor = MaterialBlendFactor::DstAlpha;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invdstalpha" || normalized == "inv_dst_alpha" || normalized == "one_minus_dst_alpha") {
|
||||
outFactor = MaterialBlendFactor::InvDstAlpha;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "dstcolor" || normalized == "dst_color") {
|
||||
outFactor = MaterialBlendFactor::DstColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invdstcolor" || normalized == "inv_dst_color" || normalized == "one_minus_dst_color") {
|
||||
outFactor = MaterialBlendFactor::InvDstColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "srcalphasat" || normalized == "src_alpha_sat") {
|
||||
outFactor = MaterialBlendFactor::SrcAlphaSat;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "blendfactor" || normalized == "blend_factor") {
|
||||
outFactor = MaterialBlendFactor::BlendFactor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invblendfactor" || normalized == "inv_blend_factor" || normalized == "one_minus_blend_factor") {
|
||||
outFactor = MaterialBlendFactor::InvBlendFactor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "src1color" || normalized == "src1_color") {
|
||||
outFactor = MaterialBlendFactor::Src1Color;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invsrc1color" || normalized == "inv_src1_color" || normalized == "one_minus_src1_color") {
|
||||
outFactor = MaterialBlendFactor::InvSrc1Color;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "src1alpha" || normalized == "src1_alpha") {
|
||||
outFactor = MaterialBlendFactor::Src1Alpha;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "invsrc1alpha" || normalized == "inv_src1_alpha" || normalized == "one_minus_src1_alpha") {
|
||||
outFactor = MaterialBlendFactor::InvSrc1Alpha;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseBlendOp(const Containers::String& value, MaterialBlendOp& outOp) {
|
||||
const Containers::String normalized = value.Trim().ToLower();
|
||||
if (normalized == "add") {
|
||||
outOp = MaterialBlendOp::Add;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "subtract") {
|
||||
outOp = MaterialBlendOp::Subtract;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "reversesubtract" || normalized == "reverse_subtract") {
|
||||
outOp = MaterialBlendOp::ReverseSubtract;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "min") {
|
||||
outOp = MaterialBlendOp::Min;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "max") {
|
||||
outOp = MaterialBlendOp::Max;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseRenderStateObject(const std::string& objectText, Material* material) {
|
||||
if (material == nullptr || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') {
|
||||
return false;
|
||||
}
|
||||
|
||||
MaterialRenderState renderState = material->GetRenderState();
|
||||
|
||||
Containers::String enumValue;
|
||||
if (HasKey(objectText, "cull")) {
|
||||
if (!TryParseStringValue(objectText, "cull", enumValue) ||
|
||||
!TryParseCullMode(enumValue, renderState.cullMode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool boolValue = false;
|
||||
if (HasKey(objectText, "depthTest")) {
|
||||
if (!TryParseBoolValue(objectText, "depthTest", boolValue)) {
|
||||
return false;
|
||||
}
|
||||
renderState.depthTestEnable = boolValue;
|
||||
}
|
||||
if (HasKey(objectText, "depthWrite")) {
|
||||
if (!TryParseBoolValue(objectText, "depthWrite", boolValue)) {
|
||||
return false;
|
||||
}
|
||||
renderState.depthWriteEnable = boolValue;
|
||||
}
|
||||
if (HasKey(objectText, "zWrite")) {
|
||||
if (!TryParseBoolValue(objectText, "zWrite", boolValue)) {
|
||||
return false;
|
||||
}
|
||||
renderState.depthWriteEnable = boolValue;
|
||||
}
|
||||
if (HasKey(objectText, "blend")) {
|
||||
if (!TryParseBoolValue(objectText, "blend", boolValue)) {
|
||||
return false;
|
||||
}
|
||||
renderState.blendEnable = boolValue;
|
||||
}
|
||||
if (HasKey(objectText, "blendEnable")) {
|
||||
if (!TryParseBoolValue(objectText, "blendEnable", boolValue)) {
|
||||
return false;
|
||||
}
|
||||
renderState.blendEnable = boolValue;
|
||||
}
|
||||
|
||||
if (HasKey(objectText, "depthFunc")) {
|
||||
if (!TryParseStringValue(objectText, "depthFunc", enumValue) ||
|
||||
!TryParseComparisonFunc(enumValue, renderState.depthFunc)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "zTest")) {
|
||||
if (!TryParseStringValue(objectText, "zTest", enumValue) ||
|
||||
!TryParseComparisonFunc(enumValue, renderState.depthFunc)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "colorWriteMask")) {
|
||||
Core::int32 colorWriteMask = 0;
|
||||
if (!TryParseIntValue(objectText, "colorWriteMask", colorWriteMask) ||
|
||||
colorWriteMask < 0 || colorWriteMask > 0xF) {
|
||||
return false;
|
||||
}
|
||||
renderState.colorWriteMask = static_cast<Core::uint8>(colorWriteMask);
|
||||
}
|
||||
if (HasKey(objectText, "srcBlend")) {
|
||||
if (!TryParseStringValue(objectText, "srcBlend", enumValue) ||
|
||||
!TryParseBlendFactor(enumValue, renderState.srcBlend)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "dstBlend")) {
|
||||
if (!TryParseStringValue(objectText, "dstBlend", enumValue) ||
|
||||
!TryParseBlendFactor(enumValue, renderState.dstBlend)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "srcBlendAlpha")) {
|
||||
if (!TryParseStringValue(objectText, "srcBlendAlpha", enumValue) ||
|
||||
!TryParseBlendFactor(enumValue, renderState.srcBlendAlpha)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "dstBlendAlpha")) {
|
||||
if (!TryParseStringValue(objectText, "dstBlendAlpha", enumValue) ||
|
||||
!TryParseBlendFactor(enumValue, renderState.dstBlendAlpha)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "blendOp")) {
|
||||
if (!TryParseStringValue(objectText, "blendOp", enumValue) ||
|
||||
!TryParseBlendOp(enumValue, renderState.blendOp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (HasKey(objectText, "blendOpAlpha")) {
|
||||
if (!TryParseStringValue(objectText, "blendOpAlpha", enumValue) ||
|
||||
!TryParseBlendOp(enumValue, renderState.blendOpAlpha)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
material->SetRenderState(renderState);
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath) {
|
||||
ResourceHandle<Shader> shader = ResourceManager::Get().Load<Shader>(shaderPath);
|
||||
if (shader.IsValid()) {
|
||||
@@ -349,6 +631,14 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& dat
|
||||
}
|
||||
}
|
||||
|
||||
if (HasKey(jsonText, "renderState")) {
|
||||
std::string renderStateObject;
|
||||
if (!TryExtractObject(jsonText, "renderState", renderStateObject) ||
|
||||
!TryParseRenderStateObject(renderStateObject, material)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user