//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "renderInstance/renderParticleMgr.h" #include "renderInstance/renderDeferredMgr.h" #include "scene/sceneManager.h" #include "scene/sceneObject.h" #include "scene/sceneRenderState.h" #include "gfx/gfxPrimitiveBuffer.h" #include "gfx/gfxTransformSaver.h" #include "gfx/gfxDebugEvent.h" #include "materials/shaderData.h" #include "materials/sceneData.h" #include "materials/matInstance.h" #include "gfx/util/screenspace.h" #include "gfx/gfxDrawUtil.h" #include "collision/clippedPolyList.h" static const Point4F cubePoints[9] = { Point4F(-0.5, -0.5, -0.5, 1.0f), Point4F(-0.5, -0.5, 0.5, 1.0f), Point4F(-0.5, 0.5, -0.5, 1.0f), Point4F(-0.5, 0.5, 0.5, 1.0f), Point4F( 0.5, -0.5, -0.5, 1.0f), Point4F( 0.5, -0.5, 0.5, 1.0f), Point4F( 0.5, 0.5, -0.5, 1.0f), Point4F( 0.5, 0.5, 0.5, 1.0f) }; GFXImplementVertexFormat( CompositeQuadVert ) { addElement( "COLOR", GFXDeclType_Color ); } IMPLEMENT_CONOBJECT(RenderParticleMgr); ConsoleDocClass( RenderParticleMgr, "@brief A render bin which renders particle geometry.\n\n" "This render bin gathers particle render instances, sorts, and renders them. " "It is currently used by ParticleEmitter and LightFlareData.\n\n" "@ingroup RenderBin\n" ); const RenderInstType RenderParticleMgr::RIT_Particles("ParticleSystem"); // TODO: Replace these once they are supported via options const bool RenderToParticleTarget = true; const bool RenderToSingleTarget = true; RenderParticleMgr::RenderParticleMgr() : Parent( RenderParticleMgr::RIT_Particles, 1.0f, 1.0f, GFXFormatR8G8B8A8, Point2I( Parent::DefaultTargetSize, Parent::DefaultTargetSize), RenderToParticleTarget ? Parent::DefaultTargetChainLength : 0 ), mParticleShader( NULL ) { // Render particles at 1/4 resolution mTargetSizeType = WindowSizeScaled; mTargetScale.set(0.5f, 0.5f); // We use the target chain like a texture pool, not like a swap chain if(!RenderToSingleTarget) setTargetChainLength(5); else mOffscreenSystems.setSize(1); notifyType( RenderPassManager::RIT_Particle ); LightManager::smActivateSignal.notify( this, &RenderParticleMgr::_onLMActivate ); } RenderParticleMgr::~RenderParticleMgr() { LightManager::smActivateSignal.remove( this, &RenderParticleMgr::_onLMActivate ); } void RenderParticleMgr::setTargetChainLength( const U32 chainLength ) { Parent::setTargetChainLength(chainLength); if(!RenderToSingleTarget) mOffscreenSystems.setSize(chainLength); } void RenderParticleMgr::addElement( RenderInst *inst ) { ParticleRenderInst *pri = reinterpret_cast(inst); // If this system isn't waiting for an offscreen draw, skip it if( pri->systemState != PSS_AwaitingOffscreenDraw ) return; // If offscreen rendering isn't enabled, set to high-res, and skip if(!mOffscreenRenderEnabled) { pri->systemState = PSS_AwaitingHighResDraw; return; } // Assign a target index RectF screenRect; S32 chainIndex = -1; if(RenderToSingleTarget) { pri->targetIndex = 0; screenRect.point.set(-1.0f, -1.0f); screenRect.extent.set(2.0f, 2.0f); mElementList.setSize(1); } else { // If we can't fit this into the offscreen systems, skip it, it will render // high resolution // // TODO: Improve this once we are grouping systems if( mTargetChainIdx == OffscreenPoolSize ) return; // Transform bounding box into screen space const static PlaneF planes[] = { PlaneF(Point3F( 1.0f, 0.0f, 0.0f), Point3F(-1.0f, 0.0f, 0.0f)), PlaneF(Point3F(-1.0f, 0.0f, 0.0f), Point3F( 1.0f, 0.0f, 0.0f)), PlaneF(Point3F( 0.0f, 1.0f, 0.0f), Point3F( 0.0f, -1.0f, 0.0f)), PlaneF(Point3F( 0.0f, -1.0f, 0.0f), Point3F( 0.0f, 1.0f, 0.0f)), PlaneF(Point3F( 0.0f, 0.0f, 0.0f), Point3F( 0.0f, 0.0f, 1.0f)), PlaneF(Point3F( 0.0f, 0.0f, 1.0f), Point3F( 0.0f, 0.0f, -1.0f)), }; const static dsize_t numPlanes = sizeof(planes) / sizeof(PlaneF); // Set up a clipper ClippedPolyList screenClipper; screenClipper.setBaseTransform(MatrixF::Identity); screenClipper.setTransform(&MatrixF::Identity, Point3F::One); TORQUE_UNUSED(numPlanes); Point4F tempPt(0.0f, 0.0f, 0.0f, 1.0f); pri->bbModelViewProj->mul(tempPt); tempPt = tempPt / tempPt.w; for(S32 i = 0; i < 1; i++) { screenClipper.mPlaneList.push_back(planes[i]); screenClipper.mPlaneList.last() += tempPt.asPoint3F(); } Box3F screenSpaceBoundingBox; screenSpaceBoundingBox.minExtents = Point3F::Zero; screenSpaceBoundingBox.maxExtents = Point3F::Zero; for(S32 i = 0; i < 8; i++) { tempPt = cubePoints[i]; pri->bbModelViewProj->mul(tempPt); tempPt = tempPt / tempPt.w; screenSpaceBoundingBox.maxExtents.setMax(tempPt.asPoint3F()); screenSpaceBoundingBox.minExtents.setMin(tempPt.asPoint3F()); } screenClipper.addBox(screenSpaceBoundingBox); screenClipper.cullUnusedVerts(); //screenClipper.triangulate(); // Empty vertex list? Skip! if(screenClipper.mVertexList.empty()) { pri->systemState = PSS_AwaitingHighResDraw; return; } Point2F minExtents(0.0f, 0.0f), maxExtents(0.0f, 0.0f); for(ClippedPolyList::VertexList::const_iterator itr = screenClipper.mVertexList.begin(); itr != screenClipper.mVertexList.end(); itr++) { minExtents.x = getMin(minExtents.x, (*itr).point.x); minExtents.y = getMin(minExtents.y, (*itr).point.y); maxExtents.x = getMax(maxExtents.x, (*itr).point.x); maxExtents.y = getMax(maxExtents.y, (*itr).point.y); } screenRect.set( minExtents, maxExtents - minExtents ); // Check the size of the system on screen. If it is small, it won't // be eating fillrate anyway, so just draw it high-resolution. // The value it checks against is one I found from experimentation, // not anything really meaningful. if( screenRect.extent.x < 0.35f || screenRect.extent.y < 0.35f ) { pri->systemState = PSS_AwaitingHighResDraw; return; } pri->targetIndex = mTargetChainIdx; chainIndex = mTargetChainIdx; mTargetChainIdx++; // TODO: Rewrite this... mElementList.increment(); } // Set up system entry OffscreenSystemEntry &systemEntry = mOffscreenSystems[pri->targetIndex]; systemEntry.screenRect = screenRect; systemEntry.targetChainIdx = chainIndex; systemEntry.pInstances.push_back(pri); // TODO: Rewrite this block // Assign proper values to sort element MainSortElem& elem = mElementList.last(); elem.inst = reinterpret_cast(&systemEntry); elem.key = *((U32*)&inst->sortDistSq); elem.key2 = inst->defaultKey; // TODO: [re]move this block systemEntry.clipMatrix.identity(); if(!RenderToSingleTarget) { // Construct crop matrix Point3F scale( getMax(2.0f / systemEntry.screenRect.extent.x, 1.0f), getMax(2.0f / systemEntry.screenRect.extent.y, 1.0f), 1.0f); Point3F offset((systemEntry.screenRect.point.x + systemEntry.screenRect.extent.x * 0.5f) * scale.x, (systemEntry.screenRect.point.y + systemEntry.screenRect.extent.y * 0.5f) * scale.y, 0.0f); //systemEntry.clipMatrix.scale(scale); //systemEntry.clipMatrix.setPosition(-offset); } // The translucent mgr will also pick up particles, and will call this class // to composiste the particle systems back into the main scene } void RenderParticleMgr::sort() { Parent::sort(); } void RenderParticleMgr::clear() { Parent::clear(); // Reset pool index if(!RenderToSingleTarget) mTargetChainIdx = 0; for(Vector::iterator itr = mOffscreenSystems.begin(); itr != mOffscreenSystems.end(); itr++) { (*itr).drawnThisFrame = false; (*itr).pInstances.clear(); } } void RenderParticleMgr::render( SceneRenderState *state ) { PROFILE_SCOPE(RenderParticleMgr_render); // Early out if nothing to draw if( !mElementList.size() || (!mParticleShader && !_initShader()) ) return; GFXDEBUGEVENT_SCOPE(RenderParticleMgr_Render, ColorI::RED); GFXTransformSaver saver; // Iterate render instances for( Vector::const_iterator itr = mElementList.begin(); itr != mElementList.end(); itr++ ) { OffscreenSystemEntry &systemEntry = *reinterpret_cast(itr->inst); // Setup target // NOTE: If you are using this on the Xbox360 with Basic Lighting, // you are going to have to mess with either the render order, or // you are going to have to make this a 'preserve' draw if(!RenderToSingleTarget) mTargetChainIdx = systemEntry.targetChainIdx; _onPreRender(state); // Clear offscreen target GFX->clear(GFXClearTarget, ColorI::ZERO, 1.0f, 0); // Draw offscreen systems for( Vector::const_iterator itr2 = systemEntry.pInstances.begin(); itr2 != systemEntry.pInstances.end(); itr2++ ) { ParticleRenderInst *ri = *itr2; // Sanity check if(ri->systemState == PSS_AwaitingOffscreenDraw) { // If this is not a diffuse path, flag the system appropriately, and skip // the offscreen processing. if( !state->isDiffusePass() ) { if(state->isReflectPass()) ri->systemState = PSS_AwaitingHighResDraw; else ri->systemState = PSS_DrawComplete; continue; } // Draw system offscreen renderInstance(ri, state); } } // Cleanup _onPostRender(); } } void RenderParticleMgr::_initGFXResources() { // Screen quad U16 prims [] = { 0, 1, 2, 3, }; mScreenQuadPrimBuff.immutable(GFX, 4, 2, prims); mScreenQuadVertBuff.set(GFX, 4, GFXBufferTypeStatic); CompositeQuadVert *verts = mScreenQuadVertBuff.lock(); (*verts++).uvCoord.set(0, 0, 0, 0); (*verts++).uvCoord.set(0, 255, 0, 0); (*verts++).uvCoord.set(255, 0, 0, 0); (*verts++).uvCoord.set(255, 255, 0, 0); mScreenQuadVertBuff.unlock(); // Stencil setup state block GFXStateBlockDesc d; d.setCullMode(GFXCullNone); d.setColorWrites(false, false, false, false); d.setBlend(false); d.setZReadWrite(false, false); d.stencilDefined = true; d.stencilEnable = true; d.stencilMask = RenderParticleMgr::ParticleSystemStencilMask; d.stencilWriteMask = RenderParticleMgr::ParticleSystemStencilMask; d.stencilFunc = GFXCmpAlways; d.stencilPassOp = GFXStencilOpZero; mStencilClearSB = GFX->createStateBlock(d); } void RenderParticleMgr::renderInstance(ParticleRenderInst *ri, SceneRenderState *state) { // Draw system path, or draw composite path if(ri->systemState == PSS_DrawComplete) return; if(ri->systemState != PSS_AwaitingCompositeDraw) { // Set proper stateblock, and update state if(ri->systemState == PSS_AwaitingOffscreenDraw) { GFX->setStateBlock( _getOffscreenStateBlock(ri) ); ri->systemState = PSS_AwaitingCompositeDraw; mParticleShaderConsts.mShaderConsts->setSafe( mParticleShaderConsts.mModelViewProjSC, *ri->modelViewProj * mOffscreenSystems[ri->targetIndex].clipMatrix ); } else { if(ri->systemState == PSS_AwaitingMixedResDraw) GFX->setStateBlock( _getMixedResStateBlock( ri ) ); else GFX->setStateBlock( _getHighResStateBlock( ri ) ); ri->systemState = PSS_DrawComplete; mParticleShaderConsts.mShaderConsts->setSafe( mParticleShaderConsts.mModelViewProjSC, *ri->modelViewProj ); } renderParticle(ri, state); } else if(ri->systemState == PSS_AwaitingCompositeDraw) { OffscreenSystemEntry &systemEntry = mOffscreenSystems[ri->targetIndex]; // If this system has already been composited this frame, skip it if(systemEntry.drawnThisFrame) return; // Non-target render, composite the particle system back into the scene GFX->setVertexBuffer(mScreenQuadVertBuff); GFX->setPrimitiveBuffer(mScreenQuadPrimBuff); // Set up shader constants mParticleCompositeShaderConsts.mShaderConsts->setSafe( mParticleCompositeShaderConsts.mScreenRect, *((Point4F *)&systemEntry.screenRect) ); // Set offscreen texture Point4F rtParams; GFXTextureObject *particleSource = mNamedTarget.getTexture(); GFX->setTexture( mParticleCompositeShaderConsts.mSamplerColorSource->getSamplerRegister(), particleSource ); if(particleSource) { ScreenSpace::RenderTargetParameters(particleSource->getSize(), mNamedTarget.getViewport(), rtParams); mParticleCompositeShaderConsts.mShaderConsts->setSafe( mParticleCompositeShaderConsts.mOffscreenTargetParamsSC, rtParams ); } // And edges GFXTextureObject *texObject = mEdgeTarget ? mEdgeTarget->getTexture() : NULL; GFX->setTexture( mParticleCompositeShaderConsts.mSamplerEdgeSource->getSamplerRegister(), texObject ); if(texObject) { ScreenSpace::RenderTargetParameters(texObject->getSize(), mEdgeTarget->getViewport(), rtParams); mParticleCompositeShaderConsts.mShaderConsts->setSafe( mParticleCompositeShaderConsts.mEdgeTargetParamsSC, rtParams ); } else { AssertFatal(false, "No edge texture target defined, if you want to use mixed particle" "rendering, then make sure that the EdgeDetectPostEffect is enabled."); ri->systemState = PSS_AwaitingHighResDraw; return; } // Set shader and constant buffer GFX->setShader( mParticleCompositeShader ); GFX->setShaderConstBuffer( mParticleCompositeShaderConsts.mShaderConsts ); // Draw to stencil buffer only to clear the stencil values GFX->setStateBlock( mStencilClearSB ); GFX->drawIndexedPrimitive( GFXTriangleStrip, 0, 0, 4, 0, 2 ); // composite particle system back into the scene GFX->setStateBlock( _getCompositeStateBlock(ri) ); GFX->drawIndexedPrimitive( GFXTriangleStrip, 0, 0, 4, 0, 2 ); // Re-draw the particle systems in high-res, but only to the stenciled // areas which were enabled via the edge buffer for( Vector::const_iterator itr = systemEntry.pInstances.begin(); itr != systemEntry.pInstances.end(); itr++ ) { ParticleRenderInst *pri = *itr; if(pri->systemState == PSS_AwaitingCompositeDraw) { pri->systemState = PSS_AwaitingMixedResDraw; renderInstance(pri, state); } } // Mark this system as having been composited this frame systemEntry.drawnThisFrame = true; } } void RenderParticleMgr::renderParticle(ParticleRenderInst* ri, SceneRenderState* state) { // We want to turn everything into variation on a pre-multiplied alpha blend F32 alphaFactor = 0.0f, alphaScale = 1.0f; switch(ri->blendStyle) { // SrcAlpha, InvSrcAlpha case ParticleRenderInst::BlendNormal: alphaFactor = 1.0f; break; // SrcAlpha, One case ParticleRenderInst::BlendAdditive: alphaFactor = 1.0f; alphaScale = 0.0f; break; // SrcColor, One case ParticleRenderInst::BlendGreyscale: alphaFactor = -1.0f; alphaScale = 0.0f; break; } mParticleShaderConsts.mShaderConsts->setSafe( mParticleShaderConsts.mAlphaFactorSC, alphaFactor ); mParticleShaderConsts.mShaderConsts->setSafe( mParticleShaderConsts.mAlphaScaleSC, alphaScale ); mParticleShaderConsts.mShaderConsts->setSafe( mParticleShaderConsts.mFSModelViewProjSC, *ri->modelViewProj ); mParticleShaderConsts.mShaderConsts->setSafe( mParticleShaderConsts.mOneOverFarSC, 1.0f / state->getFarPlane() ); if ( mParticleShaderConsts.mOneOverSoftnessSC->isValid() ) { F32 oneOverSoftness = 1.0f; if ( ri->softnessDistance > 0.0f ) oneOverSoftness = 1.0f / ( ri->softnessDistance / state->getFarPlane() ); mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mOneOverSoftnessSC, oneOverSoftness ); } GFX->setShader( mParticleShader ); GFX->setShaderConstBuffer( mParticleShaderConsts.mShaderConsts ); GFX->setTexture( mParticleShaderConsts.mSamplerDiffuse->getSamplerRegister(), ri->diffuseTex ); // Set up the deferred texture. if ( mParticleShaderConsts.mDeferredTargetParamsSC->isValid() ) { GFXTextureObject *texObject = mDeferredTarget ? mDeferredTarget->getTexture(0) : NULL; GFX->setTexture( mParticleShaderConsts.mSamplerDeferredTex->getSamplerRegister(), texObject ); Point4F rtParams( 0.0f, 0.0f, 1.0f, 1.0f ); if ( texObject ) ScreenSpace::RenderTargetParameters(texObject->getSize(), mDeferredTarget->getViewport(), rtParams); mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mDeferredTargetParamsSC, rtParams ); } GFX->setPrimitiveBuffer( *ri->primBuff ); GFX->setVertexBuffer( *ri->vertBuff ); GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, ri->count * 4, 0, ri->count * 2 ); } bool RenderParticleMgr::_initShader() { ShaderData *shaderData = NULL; bool ret = true; // Need depth from pre-pass, so get the macros Vector macros; if ( mDeferredTarget ) mDeferredTarget->getShaderMacros( ¯os ); // Create particle shader if ( !Sim::findObject( "ParticlesShaderData", shaderData ) || !shaderData ) Con::warnf( "RenderParticleMgr::_initShader - failed to locate shader ParticlesShaderData!" ); if( shaderData ) mParticleShader = shaderData->getShader( macros ); ret &= (mParticleShader != NULL); if ( mParticleShader ) { mParticleShaderConsts.mShaderConsts = mParticleShader->allocConstBuffer(); mParticleShaderConsts.mModelViewProjSC = mParticleShader->getShaderConstHandle( "$modelViewProj" ); mParticleShaderConsts.mOneOverFarSC = mParticleShader->getShaderConstHandle( "$oneOverFar" ); mParticleShaderConsts.mOneOverSoftnessSC = mParticleShader->getShaderConstHandle( "$oneOverSoftness" ); mParticleShaderConsts.mAlphaFactorSC = mParticleShader->getShaderConstHandle( "$alphaFactor" ); mParticleShaderConsts.mAlphaScaleSC = mParticleShader->getShaderConstHandle( "$alphaScale" ); mParticleShaderConsts.mFSModelViewProjSC = mParticleShader->getShaderConstHandle( "$fsModelViewProj" ); mParticleShaderConsts.mDeferredTargetParamsSC = mParticleShader->getShaderConstHandle( "$deferredTargetParams" ); //samplers mParticleShaderConsts.mSamplerDiffuse = mParticleShader->getShaderConstHandle("$diffuseMap"); mParticleShaderConsts.mSamplerDeferredTex = mParticleShader->getShaderConstHandle("$deferredTex"); mParticleShaderConsts.mSamplerParaboloidLightMap = mParticleShader->getShaderConstHandle("$paraboloidLightMap"); } shaderData = NULL; // Create off screen particle composite shader if ( !Sim::findObject( "OffscreenParticleCompositeShaderData", shaderData ) || !shaderData ) Con::warnf( "RenderParticleMgr::_initShader - failed to locate shader OffscreenParticleCompositeShaderData!" ); if( shaderData ) mParticleCompositeShader = shaderData->getShader( macros ); ret &= (mParticleCompositeShader != NULL); if ( mParticleCompositeShader ) { mParticleCompositeShaderConsts.mShaderConsts = mParticleCompositeShader->allocConstBuffer(); mParticleCompositeShaderConsts.mScreenRect = mParticleCompositeShader->getShaderConstHandle( "$screenRect" ); mParticleCompositeShaderConsts.mSamplerColorSource = mParticleCompositeShader->getShaderConstHandle( "$colorSource" ); mParticleCompositeShaderConsts.mSamplerEdgeSource = mParticleCompositeShader->getShaderConstHandle( "$edgeSource" ); mParticleCompositeShaderConsts.mEdgeTargetParamsSC = mParticleCompositeShader->getShaderConstHandle( "$edgeTargetParams" ); mParticleCompositeShaderConsts.mOffscreenTargetParamsSC = mParticleCompositeShader->getShaderConstHandle( "$offscreenTargetParams" ); } return ret; } void RenderParticleMgr::_onLMActivate( const char*, bool activate ) { if ( activate ) { RenderPassManager *rpm = getRenderPass(); if ( !rpm ) return; // Hunt for the pre-pass manager/target RenderDeferredMgr *deferredBin = NULL; for( U32 i = 0; i < rpm->getManagerCount(); i++ ) { RenderBinManager *bin = rpm->getManager(i); if( bin->getRenderInstType() == RenderDeferredMgr::RIT_Deferred ) { deferredBin = (RenderDeferredMgr*)bin; break; } } // If we found the deferred bin, set this bin to render very shortly afterwards // and re-add this render-manager. If there is no pre-pass bin, or it doesn't // have a depth-texture, we can't render offscreen. mOffscreenRenderEnabled = deferredBin && (deferredBin->getTargetChainLength() > 0); if(mOffscreenRenderEnabled) { rpm->removeManager(this); setRenderOrder( deferredBin->getRenderOrder() + 0.011f ); rpm->addManager(this); } // Find the targets we use mDeferredTarget = NamedTexTarget::find( "deferred" ); mEdgeTarget = NamedTexTarget::find( "edge" ); // Setup the shader _initShader(); if ( mScreenQuadVertBuff.isNull() ) _initGFXResources(); } else { mStencilClearSB = NULL; mScreenQuadPrimBuff = NULL; mScreenQuadVertBuff = NULL; } } GFXStateBlockRef RenderParticleMgr::_getOffscreenStateBlock(ParticleRenderInst *ri) { const U8 blendStyle = ri->blendStyle; if ( mOffscreenBlocks[blendStyle].isValid() ) return mOffscreenBlocks[blendStyle]; GFXStateBlockDesc d; d.setCullMode(GFXCullNone); d.setZReadWrite(false, false); // No zreads or writes, all z-testing is done in the pixel shader // Draw everything either subtractive, or using a variation on premultiplied // alpha if(blendStyle == ParticleRenderInst::BlendSubtractive) d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); else d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); // Offscreen target, we need to add alpha. d.separateAlphaBlendDefined = true; d.separateAlphaBlendEnable = true; d.separateAlphaBlendSrc = GFXBlendOne; d.separateAlphaBlendDest = GFXBlendInvSrcAlpha; d.samplersDefined = true; // Diffuse texture sampler d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); // Deferred sampler d.samplers[1] = GFXSamplerStateDesc::getClampPoint(); mOffscreenBlocks[blendStyle] = GFX->createStateBlock(d); return mOffscreenBlocks[blendStyle]; } GFXStateBlockRef RenderParticleMgr::_getHighResStateBlock(ParticleRenderInst *ri) { const U8 blendStyle = ri->blendStyle; if ( mHighResBlocks[blendStyle].isValid() ) return mHighResBlocks[blendStyle]; GFXStateBlockDesc d; d.setZReadWrite(true, false); d.setCullMode(GFXCullNone); // Draw everything either subtractive, or using a variation on premultiplied // alpha if(blendStyle == ParticleRenderInst::BlendSubtractive) d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); else d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); d.samplersDefined = true; // Diffuse texture sampler d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); // Deferred sampler d.samplers[1] = GFXSamplerStateDesc::getClampPoint(); mHighResBlocks[blendStyle] = GFX->createStateBlock(d); return mHighResBlocks[blendStyle]; } GFXStateBlockRef RenderParticleMgr::_getMixedResStateBlock(ParticleRenderInst *ri) { const U8 blendStyle = ri->blendStyle; if ( mMixedResBlocks[blendStyle].isValid() ) return mMixedResBlocks[blendStyle]; GFXStateBlockDesc d; d.setZReadWrite(true, false); d.setCullMode(GFXCullNone); /* // Old blend styles... switch (blendStyle) { case ParticleRenderInst::BlendNormal: d.blendSrc = GFXBlendSrcAlpha; d.blendDest = GFXBlendInvSrcAlpha; break; case ParticleRenderInst::BlendSubtractive: d.blendSrc = GFXBlendZero; d.blendDest = GFXBlendInvSrcColor; break; case ParticleRenderInst::BlendPremultAlpha: d.blendSrc = GFXBlendOne; d.blendDest = GFXBlendInvSrcAlpha; break; // Default to additive blend mode case ParticleRenderInst::BlendAdditive: case ParticleRenderInst::BlendUndefined: default: d.blendSrc = GFXBlendSrcAlpha; d.blendDest = GFXBlendOne; break; } */ // Draw everything either subtractive, or using a variation on premultiplied // alpha if(blendStyle == ParticleRenderInst::BlendSubtractive) d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); else d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); // Draw to anything but the stencil ref value (the edges) d.stencilDefined = true; d.stencilEnable = true; d.stencilRef = RenderParticleMgr::HighResStencilRef; d.stencilMask = RenderParticleMgr::ParticleSystemStencilMask; d.stencilPassOp = GFXStencilOpKeep; d.stencilFailOp = GFXStencilOpKeep; d.stencilZFailOp = GFXStencilOpKeep; d.stencilFunc = GFXCmpNotEqual; d.samplersDefined = true; // Diffuse texture sampler d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); // Deferred sampler d.samplers[1] = GFXSamplerStateDesc::getClampPoint(); mMixedResBlocks[blendStyle] = GFX->createStateBlock(d); return mMixedResBlocks[blendStyle]; } GFXStateBlockRef RenderParticleMgr::_getCompositeStateBlock(ParticleRenderInst *ri) { const U8 blendStyle = ri->blendStyle; if ( mBackbufferBlocks[blendStyle].isValid() ) return mBackbufferBlocks[blendStyle]; GFXStateBlockDesc d; // This is a billboard d.setCullMode(GFXCullNone); d.setZReadWrite(false, false); // When we re-composite the particles, it is always either a pre-mult alpha // blend, or a subtractive blend! if(blendStyle == ParticleRenderInst::BlendSubtractive) d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); else d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); // All areas which are not along the edges of geometry where the particle system // is being drawn get assigned a stencil ref value as the system is composited // back into the scene. The high-res stateblock uses this value as a mask, and // draws only in areas which are NOT this ref value. This causes high resolution // draws to ONLY the edge areas. d.stencilDefined = true; d.stencilEnable = true; d.stencilRef = RenderParticleMgr::HighResStencilRef; d.stencilWriteMask = RenderParticleMgr::ParticleSystemStencilMask; d.stencilMask = RenderParticleMgr::ParticleSystemStencilMask; d.stencilPassOp = GFXStencilOpReplace; d.stencilFunc = GFXCmpGreater; // Diffuse texture sampler and d.samplersDefined = true; d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); d.samplers[1] = GFXSamplerStateDesc::getClampLinear(); mBackbufferBlocks[blendStyle] = GFX->createStateBlock(d); return mBackbufferBlocks[blendStyle]; } bool RenderParticleMgr::_handleGFXEvent( GFXDevice::GFXDeviceEventType event ) { if(RenderToSingleTarget) return Parent::_handleGFXEvent( event ); // Do nothing. This render manager uses its target chain as a pool of targets. return true; }