/////////////////////////////////////////////////////////////////////////////// // Copyright (C) Microsoft Corporation, 1998. // // fragrslv.cpp // // Direct3D Reference Rasterizer - Fragment Resolve Methods // /////////////////////////////////////////////////////////////////////////////// #include "pch.cpp" #pragma hdrstop // fragment resolution controls - need to expose these somehow (maybe...) static BOOL g_bPreMultAlpha = TRUE; static BOOL g_bDoCoverageOnly = FALSE; //----------------------------------------------------------------------------- // // CountFrags - Utility to count fragments in a linked list. // //----------------------------------------------------------------------------- int CountFrags(RRFRAGMENT* pFrag) { if (g_iDPFLevel < 4) { return 0; } int iRet = 0; while ( NULL != pFrag ) { pFrag = (RRFRAGMENT* )pFrag->pNext; iRet++; } return iRet; } //----------------------------------------------------------------------------- // // DPFFrags - Utility to debug-print fragment list. // //----------------------------------------------------------------------------- void DPFFrags(RRFRAGMENT* pFrag) { if (g_iDPFLevel < 7) { return; } while (NULL != pFrag) { DPFM(7,FRAG,(" (%06x,%06x) %08x %f %04x\n", pFrag,pFrag->pNext,UINT32(pFrag->Color),FLOAT(pFrag->Depth),pFrag->CvgMask)) pFrag = (RRFRAGMENT *)pFrag->pNext; } } //----------------------------------------------------------------------------- // // DoFragResolve - Invoked during the buffer-resolve for each pixel which // has fragments. Takes a pointer to a linked list of fragments and returns // the resolved color. // // This constists of two steps: fragment sorting; and fragment resolve // accumulation. The fragments are sorted by stepping through the original // linked list and moving the fragments into a new linked list (using the // same link pointers) which is sorted in Z. // // The fragment resolve occurs in one of two ways depending on if any non-opaque // fragments exist in the list (this is determined during the sort). If there // are only opaque fragments, then the resolve accumulation depends only on // the coverage masks and is thus simplified. For cases with fragments due // to transparency, and more complex (and slower) resolve accumulation is // performed. // //----------------------------------------------------------------------------- void ReferenceRasterizer::DoFragResolve( RRColor& ResolvedColor, // out: final color for pixel RRDepth& ResolvedDepth, // out: final depth for pixel RRFRAGMENT* pFrag, // in: pointer to frag list for pixel const RRColor& CoveredColor ) // out: color of frontmost fully covered sample { DPFM(7,FRAG,(" presort\n")) DPFFrags(pFrag); // // reform fragments into sorted (front-to-back) linked list // // put first fragment into sortlist RRFRAGMENT* pFragSort = pFrag; pFrag = (RRFRAGMENT *)pFrag->pNext; pFragSort->pNext = NULL; // track if any non-opaque alphas (used to select resolve routine) // init value by checking the first one (already in sort list) BOOL bAnyNonOpaque = ( UINT8(pFragSort->Color.A) < 0xff ); // step thru fragment list and move each fragment into sort list while ( NULL != pFrag ) { // check for non-opaque alpha if ( UINT8(pFrag->Color.A) < 0xff ) { bAnyNonOpaque = TRUE; } // get ptr to next here so it can be overwritten RRFRAGMENT* pFragNext = (RRFRAGMENT *)pFrag->pNext; // use this to step thru sort list and insert RRFRAGMENT **ppFragSortT = &pFragSort; while ( NULL != *ppFragSortT ) { if ( DepthCloser( pFrag->Depth, (*ppFragSortT)->Depth ) ) { // current frag is closer than sort list entry, so // before this sort entry pFrag->pNext = *ppFragSortT; *ppFragSortT = pFrag; break; } else if ( NULL == (*ppFragSortT)->pNext ) { // if last, then insert after this sort list entry (*ppFragSortT)->pNext = pFrag; pFrag->pNext = NULL; break; } ppFragSortT = (RRFRAGMENT **)&((*ppFragSortT)->pNext); } // advance input frag list pFrag = pFragNext; } // all fragments have now been passed to sort list in front-to-back order DPFM(7,FRAG,(" postsort\n")) DPFFrags(pFragSort); // return first sorted fragment (this is the closest, which is as good // as anything to put into the depth buffer for the resolved pixels...) ResolvedDepth = pFragSort->Depth; // // now step thru sorted list and accumulate color // if ( bAnyNonOpaque ) { // here for fragment resolve accumulation which also does // full transparency computations - use this only if any // non-opaque fragments // instantiate and reset fragment resolve accumulator FragResolveAccum ResAccum; ResAccum.Reset(); // per frag pFrag = pFragSort; BOOL bCovered = FALSE; while ( NULL != pFrag ) { bCovered = ResAccum.Accum( pFrag->CvgMask, pFrag->Color ); if (bCovered) { break; } // fully covered, so don't do rest of frags (or background) pFrag = (RRFRAGMENT *)pFrag->pNext; } // add in background color (last) if ( !bCovered && ( UINT8(CoveredColor.A) > 0 ) ) { ResAccum.Accum( TL_CVGFULL, CoveredColor ); } // unload accumulator ResAccum.GetColor( ResolvedColor ); } else { // // here for fragment resolve of all fully opaque fragments // // // accumulated coverage and color // CVGMASK CvgMaskAccum = 0x0; FLOAT fRAcc = 0.F; // these 0. to 1. range FLOAT fGAcc = 0.F; FLOAT fBAcc = 0.F; FLOAT fWeightAccum = 0.F; // per frag pFrag = pFragSort; while ( NULL != pFrag ) { // compute this fragment's contribution CVGMASK CvgMaskContrib = pFrag->CvgMask & ~(CvgMaskAccum); FLOAT fWeight = (1.f/16.f) * (FLOAT)CountSetBits(CvgMaskContrib, 16); // accumulate rgb fRAcc += fWeight * FLOAT(pFrag->Color.R); fGAcc += fWeight * FLOAT(pFrag->Color.G); fBAcc += fWeight * FLOAT(pFrag->Color.B); // accumulate total coverage and weight CvgMaskAccum |= CvgMaskContrib; fWeightAccum += fWeight; // bail out early if fully covered if ( TL_CVGFULL == CvgMaskAccum ) { goto DoneAccumulating; } // next pFrag = (RRFRAGMENT *)pFrag->pNext; } // blend with background color/alpha if ( (fWeightAccum < 1.f) && ( UINT8(CoveredColor.A) > 0 ) ) { // blend in remaining weight of background color FLOAT fWeightBg = 1.F - fWeightAccum; fRAcc += fWeightBg * FLOAT(CoveredColor.R); fGAcc += fWeightBg * FLOAT(CoveredColor.G); fBAcc += fWeightBg * FLOAT(CoveredColor.B); // fix accumulated weight - pixel is fully covered now fWeightAccum = 1.f; } DoneAccumulating: // clamp accumulators if ( fWeightAccum > 1.F ) { fWeightAccum = 1.F; } if ( fRAcc > 1.F ) { fRAcc = 1.F; } if ( fGAcc > 1.F ) { fGAcc = 1.F; } if ( fBAcc > 1.F ) { fBAcc = 1.F; } // set in color return ResolvedColor.A = fWeightAccum; ResolvedColor.R = fRAcc; ResolvedColor.G = fGAcc; ResolvedColor.B = fBAcc; } // free this pixel's fragments pFrag = pFragSort; while ( NULL != pFrag ) { RRFRAGMENT* pFragFree = pFrag; pFrag = (RRFRAGMENT*)pFrag->pNext; FragFree( pFragFree ); } return; } //----------------------------------------------------------------------------- // // DoBufferResolve - Invoked at EndScene to resolve fragments into single // color for each pixel location. // //----------------------------------------------------------------------------- void ReferenceRasterizer::DoBufferResolve(void) { DPFM(2,FRAG,(" DoBufferResolve (%d,%d)\n",m_pRenderTarget->m_iWidth,m_pRenderTarget->m_iHeight)) // buffer may not be allocated if there were no fragments if (NULL == m_ppFragBuf) { return; } for ( int iY=0; iY < m_pRenderTarget->m_iHeight; iY++ ) { for ( int iX=0; iX < m_pRenderTarget->m_iWidth; iX++ ) { RRFRAGMENT* pFrag = *(m_ppFragBuf + (m_pRenderTarget->m_iWidth*iY) + iX); if ( NULL != pFrag ) { DPFM(5,FRAG,(" DoResolve(%d,%d) %d\n",iX,iY,CountFrags(pFrag))) // read buffer color for background blend RRColor PixelColor; m_pRenderTarget->ReadPixelColor( iX,iY, PixelColor); // do resolve RRColor ResolvedColor; RRDepth ResolvedDepth(pFrag->Depth.GetSType()); DoFragResolve( ResolvedColor, ResolvedDepth, pFrag, PixelColor ); // write color back to buffer; write frontmost depth back to // pixel buffer (it's at least a little better than the backmost // opaque sample...) WritePixel( iX,iY, ResolvedColor, ResolvedDepth ); // show frags freed (free happens during resolve) *(m_ppFragBuf + (m_pRenderTarget->m_iWidth*iY) + iX) = NULL; } } } DPFM(3,FRAG,(" DoBufferResolve - done\n")) } /////////////////////////////////////////////////////////////////////////////// // // // Fragment Resolve Accumulator // // // /////////////////////////////////////////////////////////////////////////////// // // FragResolveAccum - This object is the fragment resolver used when // non-opaque fragments are present. This has the effect of resolving // each of the 16 subpixel locations independently to produce the fully // correct result. Several optimizations are used to minimize the actual // number of accumulation computations that need to occur. // //----------------------------------------------------------------------------- // // Reset - Called prior to resolving a list of fragments to initialize the // internal state. // //----------------------------------------------------------------------------- void FragResolveAccum::Reset(void) { DPFM(5, FRAG, (" FragResolveAccum: reset\n")) m_ArrayUsageMask = 0x0001; // use first array entry only (at first) m_CvgArray[0].Mask = TL_CVGFULL; m_CvgArray[0].fAlpha = 1.; m_fA = 0.; m_fR = 0.; m_fG = 0.; m_fB = 0.; m_CvgOpaqueMask = 0x0000; } //----------------------------------------------------------------------------- // // Accum - Called for each fragment. Fragments must be accumulated in front- // to-back order (sort is done prior to accumulation). // // Returns TRUE if full coverage has been achieved and thus subsequent // fragments will have no further contribution to the final pixel color and // opacity. // //----------------------------------------------------------------------------- BOOL FragResolveAccum::Accum( const CVGMASK CvgMask, const RRColor& ColorFrag) { DPFM(6, FRAG, (" FragResolveAccum: accum %04x %08x\n", CvgMask, UINT32(ColorFrag) ) ) FLOAT fAlphaFrag = FLOAT(ColorFrag.A); // exit (don't accum) if all covered if (TL_CVGFULL == m_CvgOpaqueMask) { return TRUE; } // controls for doing (up to) 4 accumulations at a time INT32 iAccumsDeferred = 0; // the current number of deferred accumulations FLOAT fColorScaleAccum; // the accumulated color scale for the deferred accums // compute ArrayCheck - each set bit indicates a coverage mask // bit for which an accumulation needs to be done (indicated by // a valid entry in the coverage array which is not opaque and // for which the corresponding bit is set in this fragment's // coverage mask) CVGMASK ArrayCheck = 0x0; for (INT32 i=0; i<16; i++) { if (m_CvgArray[i].Mask & CvgMask) { ArrayCheck |= ((0x1 << i) & ~(m_CvgOpaqueMask)); } } INT32 iIdx; CVGMASK ArrayMaskT = m_ArrayUsageMask; while (0x0 != ArrayMaskT) { // track from MSB to LSB of usage mask iIdx = FindLastSetBit(ArrayMaskT,TL_CVGBITS); ArrayMaskT &= ~(0x1 << iIdx); // compute masks for overlapped coverage (requiring // accumulation) and non-overlapped area (which may // require an updated coverage/alpha entry) CVGMASK AccumCvgMask = m_CvgArray[iIdx].Mask & CvgMask; CVGMASK UpdateCvgMask = m_CvgArray[iIdx].Mask & ~CvgMask; // remove bits in the overlapped coverage mask for subsamples // which already have opaque alphas AccumCvgMask &= ~(m_CvgOpaqueMask); // read alpha old here - the location that this is stored // may be changed in the accumulate step but needs to be // remembered for the update (non-covered area) step FLOAT fAlphaOld = m_CvgArray[iIdx].fAlpha; // compute alpha scale value - this is used to scale color // for accumulation and to compute updated alpha for overlap FLOAT fAlphaScale = fAlphaOld * fAlphaFrag; // new alpha for overlapped area (this cannot go negative // since 0 < AlphaScale < AlphaOld) // AlphaNext = AlphaOld(1 - Alpha) = AlphaOld - AlphaOld*Alpha = FLOAT fAlphaNext = fAlphaOld - fAlphaScale; if (0x0 != AccumCvgMask) { // contribution to accumulate - this is the portion // the previous mask starting at the uIdx bit location // which is covered by the new fragment, so accumulate // this coverage and update the mask and alpha UINT32 iIdxT = FindFirstSetBit(AccumCvgMask,TL_CVGBITS); m_ArrayUsageMask |= (0x1 << iIdxT); m_CvgArray[iIdxT].Mask = AccumCvgMask; // set the alpha of the overlapped area m_CvgArray[iIdxT].fAlpha = fAlphaNext; // compute scale for color channels - depends on if // we want pre-multiplied alphas or not... // // base for scale is either array value alone or product // of array value and Afrag (AlphaScale) FLOAT fColorScaleBase = (g_bPreMultAlpha) ? (fAlphaOld) : (fAlphaScale); // do either multiply or bypass for full coverage FLOAT fColorScale = fColorScaleBase; if ( TL_CVGFULL != AccumCvgMask ) { FLOAT fCvgFraction = (FLOAT)(CountSetBits(AccumCvgMask, TL_CVGBITS)) * (1./TL_CVGBITS); fColorScale *= fCvgFraction; } // accumulate up to four accumulations to do at once - the accumulated // value is the color scale to be applied to the multiple locations // update color scale accum - either set (1st deferral) or // accumulate (subsequent deferrals) fColorScaleAccum = (0 == iAccumsDeferred) ? (fColorScale) : (fColorScale + fColorScaleAccum); // track number of deferrals and bypass accumulation if not // up to 4 (or if this is the last one) if ( (++iAccumsDeferred != 4) && (0x0 != (ArrayMaskT & ArrayCheck)) ) { goto _update_CvgMask_Location; } // start over on deferral iAccumsDeferred = 0; // clamp color scale to max before accumulation fColorScale = MIN( fColorScaleAccum, 1. ); // do accumulation and write back to accumulator // decide what to use for alpha accumulate - if we are using // pre-multiplied alphas, then AFrag is not incorporated into // color scale, thus mult by AFrag FLOAT fAPartial = fColorScale * ( (g_bPreMultAlpha) ? (fAlphaFrag) : (1.) ); FLOAT fRPartial = fColorScale * FLOAT(ColorFrag.R); FLOAT fGPartial = fColorScale * FLOAT(ColorFrag.G); FLOAT fBPartial = fColorScale * FLOAT(ColorFrag.B); m_fA += fAPartial; m_fR += fRPartial; m_fG += fGPartial; m_fB += fBPartial; } _update_CvgMask_Location: if (0x0 != UpdateCvgMask) { // mask to update - this is the portion of the // previous mask starting at the uIdx bit location // which is still visible, so update the coverage // (the alpha stays the same) UINT32 iIdxT = FindFirstSetBit(UpdateCvgMask,TL_CVGBITS); m_ArrayUsageMask |= (0x1 << iIdxT); m_CvgArray[iIdxT].Mask = UpdateCvgMask; m_CvgArray[iIdxT].fAlpha = fAlphaOld; } } // determine if this new fragment is has an opaque alpha // if so then update opaque mask - this must be done after // the accumulations because the opaque mask refers to the // current state of the coverage array and should apply only to // accumulations of subsequent fragments // // g_bDoCoverageOnly overrides this to always act as if fragments' // alphas are opaque for the purposes of generating antialiased // shadow attenuation surfaces { if ((fAlphaFrag >= 1.) || (g_bDoCoverageOnly)) { m_CvgOpaqueMask |= CvgMask; } } // check opaque mask for return boolean - returns TRUE if we // are done return (TL_CVGFULL == m_CvgOpaqueMask) ? (TRUE) : (FALSE); } //----------------------------------------------------------------------------- // // GetColor - Called after accumulating a series of fragments to get the final // pixel color and alpha. // //----------------------------------------------------------------------------- void FragResolveAccum::GetColor( RRColor& Color ) { // clamp and assign for return Color.A = (FLOAT)MIN( m_fA, 1. ); Color.R = (FLOAT)MIN( m_fR, 1. ); Color.G = (FLOAT)MIN( m_fG, 1. ); Color.B = (FLOAT)MIN( m_fB, 1. ); } /////////////////////////////////////////////////////////////////////////////// // end