/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 2022 Stephen Pridham This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "renderer/RenderCommon.h" #include "PipelineCache.h" PipelineCache::PipelineCache() { } void PipelineCache::Init( nvrhi::DeviceHandle deviceHandle ) { device = deviceHandle; } void PipelineCache::Shutdown() { // SRS - Remove reference to nvrhi::IDevice, otherwise won't clean up properly on shutdown device = nullptr; } void PipelineCache::Clear() { pipelines.Clear(); pipelineHash.Clear(); } nvrhi::GraphicsPipelineHandle PipelineCache::GetOrCreatePipeline( const PipelineKey& key ) { std::size_t h = std::hash {}( key ); for( int i = pipelineHash.First( h ); i >= 0; i = pipelineHash.Next( i ) ) { if( pipelines[i].first == key ) { return pipelines[i].second; } } nvrhi::GraphicsPipelineDesc pipelineDesc; const programInfo_t progInfo = renderProgManager.GetProgramInfo( key.program ); pipelineDesc.setVertexShader( progInfo.vs ).setFragmentShader( progInfo.ps ); pipelineDesc.inputLayout = progInfo.inputLayout; for( int i = 0; i < progInfo.bindingLayouts->Num(); i++ ) { pipelineDesc.bindingLayouts.push_back( ( *progInfo.bindingLayouts )[i] ); } pipelineDesc.primType = nvrhi::PrimitiveType::TriangleList; // Set up default state. pipelineDesc.renderState.rasterState.enableScissor(); pipelineDesc.renderState.depthStencilState.enableDepthTest().enableDepthWrite(); for( auto& target : pipelineDesc.renderState.blendState.targets ) { target.enableBlend(); } // Specialize the state with the state key. GetRenderState( key.state, key, pipelineDesc.renderState ); auto pipeline = device->createGraphicsPipeline( pipelineDesc, key.framebuffer->GetApiObject() ); pipelineHash.Add( h, pipelines.Append( { key, pipeline } ) ); return pipeline; } void PipelineCache::GetRenderState( uint64 stateBits, PipelineKey key, nvrhi::RenderState& renderState ) { /* uint64 diff = stateBits ^ GLS_DEFAULT; if( diff == 0 ) { return; } */ auto& rasterizationState = renderState.rasterState; // // culling & mirrors // //if( stateBits & ( GLS_CULL_BITS ) )//| GLS_MIRROR_VIEW ) ) { switch( stateBits & GLS_CULL_BITS ) { case GLS_CULL_TWOSIDED: rasterizationState.setCullNone(); break; case GLS_CULL_BACKSIDED: if( stateBits & GLS_MIRROR_VIEW ) { rasterizationState.setCullFront(); } else { rasterizationState.setCullBack(); } break; case GLS_CULL_FRONTSIDED: default: if( stateBits & GLS_MIRROR_VIEW ) { rasterizationState.setCullBack(); } else { rasterizationState.setCullFront(); } break; } } rasterizationState.setFrontCounterClockwise( ( stateBits & GLS_CLOCKWISE ) == 0 ); // // fill/line mode // //if( diff & GLS_POLYMODE_LINE ) { if( stateBits & GLS_POLYMODE_LINE ) { rasterizationState.setFillMode( nvrhi::RasterFillMode::Wireframe ); } else { rasterizationState.setFillMode( nvrhi::RasterFillMode::Solid ); } } // // polygon offset // //if( diff & GLS_POLYGON_OFFSET ) { if( stateBits & GLS_POLYGON_OFFSET ) { rasterizationState.depthBias = key.depthBias; rasterizationState.slopeScaledDepthBias = key.slopeBias; rasterizationState.enableQuadFill(); } else { //currentRasterState.disableQuadFill(); } } nvrhi::BlendState::RenderTarget renderTarget; // // check blend bits // //if( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) { nvrhi::BlendFactor srcFactor = nvrhi::BlendFactor::One; switch( stateBits & GLS_SRCBLEND_BITS ) { case GLS_SRCBLEND_ZERO: srcFactor = nvrhi::BlendFactor::Zero; break; case GLS_SRCBLEND_ONE: srcFactor = nvrhi::BlendFactor::One; break; case GLS_SRCBLEND_DST_COLOR: srcFactor = nvrhi::BlendFactor::DstColor; break; case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: srcFactor = nvrhi::BlendFactor::OneMinusDstColor; break; case GLS_SRCBLEND_SRC_ALPHA: srcFactor = nvrhi::BlendFactor::SrcAlpha; break; case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: srcFactor = nvrhi::BlendFactor::OneMinusSrcAlpha; break; case GLS_SRCBLEND_DST_ALPHA: srcFactor = nvrhi::BlendFactor::DstAlpha; break; case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: srcFactor = nvrhi::BlendFactor::OneMinusDstAlpha; break; default: assert( !"GL_State: invalid src blend state bits\n" ); break; } nvrhi::BlendFactor dstFactor = nvrhi::BlendFactor::Zero; switch( stateBits & GLS_DSTBLEND_BITS ) { case GLS_DSTBLEND_ZERO: dstFactor = nvrhi::BlendFactor::Zero; break; case GLS_DSTBLEND_ONE: dstFactor = nvrhi::BlendFactor::One; break; case GLS_DSTBLEND_SRC_COLOR: dstFactor = nvrhi::BlendFactor::SrcColor; break; case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: dstFactor = nvrhi::BlendFactor::OneMinusSrcColor; break; case GLS_DSTBLEND_SRC_ALPHA: dstFactor = nvrhi::BlendFactor::SrcAlpha; break; case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: dstFactor = nvrhi::BlendFactor::OneMinusSrcAlpha; break; case GLS_DSTBLEND_DST_ALPHA: dstFactor = nvrhi::BlendFactor::DstAlpha; break; case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: dstFactor = nvrhi::BlendFactor::OneMinusDstAlpha; break; default: assert( !"GL_State: invalid dst blend state bits\n" ); break; } // RB: blend ops are only used by the SWF render states so far nvrhi::BlendOp blendOp = nvrhi::BlendOp::Add; switch( stateBits & GLS_BLENDOP_BITS ) { case GLS_BLENDOP_MIN: blendOp = nvrhi::BlendOp::Min; break; case GLS_BLENDOP_MAX: blendOp = nvrhi::BlendOp::Max; break; case GLS_BLENDOP_ADD: blendOp = nvrhi::BlendOp::Add; break; case GLS_BLENDOP_SUB: blendOp = nvrhi::BlendOp::Subrtact; break; } // Only actually update GL's blend func if blending is enabled. if( srcFactor == nvrhi::BlendFactor::One && dstFactor == nvrhi::BlendFactor::Zero ) { renderTarget.disableBlend(); } else { //colorBlendState.setAlphaToCoverageEnable( true ); renderTarget.enableBlend(); //renderTarget.setBlendOp( blendOp ); renderTarget.setSrcBlend( srcFactor ); renderTarget.setDestBlend( dstFactor ); //renderTarget.setBlendOpAlpha( blendOp ); //renderTarget.setSrcBlendAlpha( srcFactor ); //renderTarget.setDestBlendAlpha( dstFactor ); } } // // check colormask // //if( diff & ( GLS_REDMASK | GLS_GREENMASK | GLS_BLUEMASK | GLS_ALPHAMASK ) ) { nvrhi::ColorMask mask{ nvrhi::ColorMask::All }; if( stateBits & GLS_REDMASK ) { mask = mask & ~nvrhi::ColorMask::Red; } if( stateBits & GLS_GREENMASK ) { mask = mask & ~nvrhi::ColorMask::Green; } if( stateBits & GLS_BLUEMASK ) { mask = mask & ~nvrhi::ColorMask::Blue; } if( stateBits & GLS_ALPHAMASK ) { mask = mask & ~nvrhi::ColorMask::Alpha; } //renderTarget.enableBlend(); renderTarget.setColorWriteMask( mask ); } renderState.blendState.setRenderTarget( 0, renderTarget ); auto& depthStencilState = renderState.depthStencilState; // // check depthFunc bits // //if( diff & GLS_DEPTHFUNC_BITS ) { switch( stateBits & GLS_DEPTHFUNC_BITS ) { case GLS_DEPTHFUNC_EQUAL: depthStencilState.depthFunc = nvrhi::ComparisonFunc::Equal; break; case GLS_DEPTHFUNC_ALWAYS: depthStencilState.depthFunc = nvrhi::ComparisonFunc::Always; break; case GLS_DEPTHFUNC_LESS: depthStencilState.depthFunc = nvrhi::ComparisonFunc::LessOrEqual; break; case GLS_DEPTHFUNC_GREATER: depthStencilState.depthFunc = nvrhi::ComparisonFunc::Greater; break; } } // // check depthmask // //if( diff & GLS_DEPTHMASK ) { if( stateBits & GLS_DEPTHMASK ) { depthStencilState.disableDepthWrite(); if( ( stateBits & GLS_DEPTHFUNC_BITS ) == GLS_DEPTHFUNC_ALWAYS ) { depthStencilState.disableDepthTest(); } } } // // stencil // //if( diff & ( GLS_STENCIL_FUNC_BITS | GLS_STENCIL_OP_BITS ) ) { // SRS - Eliminate r/w depth attachment validation warnings when depth-stencil is read-only if( ( stateBits & ( GLS_STENCIL_FUNC_BITS | GLS_STENCIL_OP_BITS | GLS_SEPARATE_STENCIL ) ) != 0 || ( depthStencilState.depthTestEnable && !depthStencilState.depthWriteEnable ) ) { depthStencilState.enableStencil(); } else { depthStencilState.disableStencil(); } } if( stateBits & ( GLS_STENCIL_FUNC_BITS | GLS_STENCIL_FUNC_REF_BITS | GLS_STENCIL_FUNC_MASK_BITS ) ) { uint32 ref = uint32( ( stateBits & GLS_STENCIL_FUNC_REF_BITS ) >> GLS_STENCIL_FUNC_REF_SHIFT ); uint32 mask = uint32( ( stateBits & GLS_STENCIL_FUNC_MASK_BITS ) >> GLS_STENCIL_FUNC_MASK_SHIFT ); depthStencilState.setStencilRefValue( ref ); depthStencilState.setStencilReadMask( mask ); depthStencilState.setStencilWriteMask( 0xFF ); } nvrhi::DepthStencilState::StencilOpDesc stencilFuncOp; switch( stateBits & GLS_STENCIL_FUNC_BITS ) { case GLS_STENCIL_FUNC_NEVER: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::Never ); break; case GLS_STENCIL_FUNC_LESS: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::Less ); break; case GLS_STENCIL_FUNC_EQUAL: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::Equal ); break; case GLS_STENCIL_FUNC_LEQUAL: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::LessOrEqual ); break; case GLS_STENCIL_FUNC_GREATER: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::Greater ); break; case GLS_STENCIL_FUNC_NOTEQUAL: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::NotEqual ); break; case GLS_STENCIL_FUNC_GEQUAL: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::GreaterOrEqual ); break; case GLS_STENCIL_FUNC_ALWAYS: stencilFuncOp.setStencilFunc( nvrhi::ComparisonFunc::Always ); break; } // Carmack's Reverse with GLS_SEPARATE_STENCIL if( stateBits & GLS_SEPARATE_STENCIL ) { nvrhi::DepthStencilState::StencilOpDesc frontStencilOp = GetStencilOpState( stateBits & GLS_STENCIL_FRONT_OPS ); frontStencilOp.stencilFunc = stencilFuncOp.stencilFunc; nvrhi::DepthStencilState::StencilOpDesc backStencilOp = GetStencilOpState( ( stateBits & GLS_STENCIL_BACK_OPS ) >> 12 ); backStencilOp.stencilFunc = stencilFuncOp.stencilFunc; depthStencilState.setFrontFaceStencil( frontStencilOp ); depthStencilState.setBackFaceStencil( backStencilOp ); } else { nvrhi::DepthStencilState::StencilOpDesc stencilOp = GetStencilOpState( stateBits ); stencilOp.stencilFunc = stencilFuncOp.stencilFunc; depthStencilState.setFrontFaceStencil( stencilOp ); depthStencilState.setBackFaceStencil( stencilOp ); } } nvrhi::DepthStencilState::StencilOpDesc PipelineCache::GetStencilOpState( uint64 stateBits ) { nvrhi::DepthStencilState::StencilOpDesc stencilOp; //if( stateBits & ( GLS_STENCIL_OP_FAIL_BITS | GLS_STENCIL_OP_ZFAIL_BITS | GLS_STENCIL_OP_PASS_BITS ) ) { switch( stateBits & GLS_STENCIL_OP_FAIL_BITS ) { case GLS_STENCIL_OP_FAIL_KEEP: stencilOp.setFailOp( nvrhi::StencilOp::Keep ); break; case GLS_STENCIL_OP_FAIL_ZERO: stencilOp.setFailOp( nvrhi::StencilOp::Zero ); break; case GLS_STENCIL_OP_FAIL_REPLACE: stencilOp.setFailOp( nvrhi::StencilOp::Replace ); break; case GLS_STENCIL_OP_FAIL_INCR: stencilOp.setFailOp( nvrhi::StencilOp::IncrementAndClamp ); break; case GLS_STENCIL_OP_FAIL_DECR: stencilOp.setFailOp( nvrhi::StencilOp::DecrementAndClamp ); break; case GLS_STENCIL_OP_FAIL_INVERT: stencilOp.setFailOp( nvrhi::StencilOp::Invert ); break; case GLS_STENCIL_OP_FAIL_INCR_WRAP: stencilOp.setFailOp( nvrhi::StencilOp::IncrementAndWrap ); break; case GLS_STENCIL_OP_FAIL_DECR_WRAP: stencilOp.setFailOp( nvrhi::StencilOp::DecrementAndWrap ); break; } switch( stateBits & GLS_STENCIL_OP_ZFAIL_BITS ) { case GLS_STENCIL_OP_ZFAIL_KEEP: stencilOp.setDepthFailOp( nvrhi::StencilOp::Keep ); break; case GLS_STENCIL_OP_ZFAIL_ZERO: stencilOp.setDepthFailOp( nvrhi::StencilOp::Zero ); break; case GLS_STENCIL_OP_ZFAIL_REPLACE: stencilOp.setDepthFailOp( nvrhi::StencilOp::Replace ); break; case GLS_STENCIL_OP_ZFAIL_INCR: stencilOp.setDepthFailOp( nvrhi::StencilOp::IncrementAndClamp ); break; case GLS_STENCIL_OP_ZFAIL_DECR: stencilOp.setDepthFailOp( nvrhi::StencilOp::DecrementAndClamp ); break; case GLS_STENCIL_OP_ZFAIL_INVERT: stencilOp.setDepthFailOp( nvrhi::StencilOp::Invert ); break; case GLS_STENCIL_OP_ZFAIL_INCR_WRAP: stencilOp.setDepthFailOp( nvrhi::StencilOp::IncrementAndWrap ); break; case GLS_STENCIL_OP_ZFAIL_DECR_WRAP: stencilOp.setDepthFailOp( nvrhi::StencilOp::DecrementAndWrap ); break; } switch( stateBits & GLS_STENCIL_OP_PASS_BITS ) { case GLS_STENCIL_OP_PASS_KEEP: stencilOp.setPassOp( nvrhi::StencilOp::Keep ); break; case GLS_STENCIL_OP_PASS_ZERO: stencilOp.setPassOp( nvrhi::StencilOp::Zero ); break; case GLS_STENCIL_OP_PASS_REPLACE: stencilOp.setPassOp( nvrhi::StencilOp::Replace ); break; case GLS_STENCIL_OP_PASS_INCR: stencilOp.setPassOp( nvrhi::StencilOp::IncrementAndClamp ); break; case GLS_STENCIL_OP_PASS_DECR: stencilOp.setPassOp( nvrhi::StencilOp::DecrementAndClamp ); break; case GLS_STENCIL_OP_PASS_INVERT: stencilOp.setPassOp( nvrhi::StencilOp::Invert ); break; case GLS_STENCIL_OP_PASS_INCR_WRAP: stencilOp.setPassOp( nvrhi::StencilOp::IncrementAndWrap ); break; case GLS_STENCIL_OP_PASS_DECR_WRAP: stencilOp.setPassOp( nvrhi::StencilOp::DecrementAndWrap ); break; } } return stencilOp; }