// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #![allow(unused_parens)] use crate::aacoverage::{CCoverageBuffer, c_rInvShiftSize, c_antiAliasMode, c_nShift, CCoverageInterval, c_nShiftMask, c_nShiftSize, c_nHalfShiftSize}; use crate::hwvertexbuffer::CHwVertexBufferBuilder; use crate::matrix::{CMILMatrix, CMatrix}; use crate::nullable_ref::Ref; use crate::aarasterizer::*; use crate::geometry_sink::IGeometrySink; use crate::helpers::Int32x32To64; use crate::types::*; use typed_arena_nomut::Arena; //----------------------------------------------------------------------------- // // // Description: // Trapezoidal anti-aliasing implementation // // >>>> Note that some of this code is duplicated in sw\aarasterizer.cpp, // >>>> so changes to this file may need to propagate. // // pursue reduced code duplication // macro_rules! MIL_THR { ($e: expr) => { $e//assert_eq!($e, S_OK); } } // // Optimize for speed instead of size for these critical methods // //------------------------------------------------------------------------- // // Coordinate system encoding // // All points/coordinates are named as follows: // // [X|Y][Left|Right|Top|Bottom]VariableName // // Common hungarian types: // n - INT // u - UINT // r - FLOAT // // Coordinate systems: // Pixel - Device pixel space assuming integer coordinates in the pixel top left corner. // Subpixel - Overscaled space. // // To convert between Pixel to Subpixel, we have: // nSubpixelCoordinate = nPixelCoordinate << c_nShift; // nPixelCoordinate = nSubpixelCoordinate >> c_nShift; // // Note that the conversion to nPixelCoordinate needs to also track // (nSubpixelCoordinate & c_nShiftMask) to maintain the full value. // // Note that since trapezoidal only supports 8x8, c_nShiftSize is always equal to 8. So, // (1, 2) in pixel space would become (8, 16) in subpixel space. // // [X|Y] // Indicates which coordinate is being referred to. // // [Left|Right|Top|Bottom] // When referring to trapezoids or rectangular regions, this // component indicates which edge is being referred to. // // VariableName // Descriptive portion of the variable name // //------------------------------------------------------------------------- //------------------------------------------------------------------------- // // Function: IsFractionGreaterThan // // Synopsis: // Determine if nNumeratorA/nDenominatorA > nNumeratorB/nDenominatorB // // Note that we assume all denominators are strictly greater than zero. // //------------------------------------------------------------------------- fn IsFractionGreaterThan( nNumeratorA: INT, // Left hand side numerator /* __in_range(>=, 1) */ nDenominatorA: INT, // Left hand side denominator nNumeratorB: INT, // Right hand side numerator /* __in_range(>=, 1) */ nDenominatorB: INT, // Right hand side denominator ) -> bool { // // nNumeratorA/nDenominatorA > nNumeratorB/nDenominatorB // iff nNumeratorA*nDenominatorB/nDenominatorA > nNumeratorB, since nDenominatorB > 0 // iff nNumeratorA*nDenominatorB > nNumeratorB*nDenominatorA, since nDenominatorA > 0 // // Now, all input parameters are 32-bit integers, so we need to use // a 64-bit result to compute the product. // let lNumeratorAxDenominatorB = Int32x32To64(nNumeratorA, nDenominatorB); let lNumeratorBxDenominatorA = Int32x32To64(nNumeratorB, nDenominatorA); return (lNumeratorAxDenominatorB > lNumeratorBxDenominatorA); } //------------------------------------------------------------------------- // // Function: IsFractionLessThan // // Synopsis: // Determine if nNumeratorA/nDenominatorA < nNumeratorB/nDenominatorB // // Note that we assume all denominators are strictly greater than zero. // //------------------------------------------------------------------------- fn IsFractionLessThan( nNumeratorA: INT, // Left hand side numerator /* __in_range(>=, 1) */ nDenominatorA: INT, // Left hand side denominator nNumeratorB: INT, // Right hand side numerator /* __in_range(>=, 1) */ nDenominatorB: INT, // Right hand side denominator ) -> bool { // // Same check as previous function with less than comparision instead of // a greater than comparison. // let lNumeratorAxDenominatorB = Int32x32To64(nNumeratorA, nDenominatorB); let lNumeratorBxDenominatorA = Int32x32To64(nNumeratorB, nDenominatorA); return (lNumeratorAxDenominatorB < lNumeratorBxDenominatorA); } //------------------------------------------------------------------------- // // Function: AdvanceDDAMultipleSteps // // Synopsis: // Advance the DDA by multiple steps // //------------------------------------------------------------------------- fn AdvanceDDAMultipleSteps( pEdgeLeft: &CEdge, // Left edge from active edge list pEdgeRight: &CEdge, // Right edge from active edge list nSubpixelYAdvance: INT, // Number of steps to advance the DDA nSubpixelXLeftBottom: &mut INT, // Resulting left x position nSubpixelErrorLeftBottom: &mut INT, // Resulting left x position error nSubpixelXRightBottom: &mut INT, // Resulting right x position nSubpixelErrorRightBottom: &mut INT // Resulting right x position error ) { // // In this method, we need to be careful of overflow. Expected input ranges for values are: // // edge points: x and y subpixel space coordinates are between [-2^26, 2^26] // since we start with 28.4 space (and are now in subpixel space, // i.e., no 16x scale) and assume 2 bits of working space. // // This assumption is ensured by TransformRasterizerPointsTo28_4. // #[cfg(debug_assertions)] { let nDbgPixelCoordinateMax = (1 << 26); let nDbgPixelCoordinateMin = -nDbgPixelCoordinateMax; assert!(pEdgeLeft.X.get() >= nDbgPixelCoordinateMin && pEdgeLeft.X.get() <= nDbgPixelCoordinateMax); assert!(pEdgeLeft.EndY >= nDbgPixelCoordinateMin && pEdgeLeft.EndY <= nDbgPixelCoordinateMax); assert!(pEdgeRight.X.get() >= nDbgPixelCoordinateMin && pEdgeRight.X.get() <= nDbgPixelCoordinateMax); assert!(pEdgeRight.EndY >= nDbgPixelCoordinateMin && pEdgeRight.EndY <= nDbgPixelCoordinateMax); // // errorDown: (0, 2^30) // Since errorDown is the edge delta y in 28.4 space (not subpixel space // like the end points), we have a larger range of (0, 2^32) for the positive // error down. With 2 bits of work space (which TransformRasterizerPointsTo28_4 // ensures), we know we are between (0, 2^30) // let nDbgErrorDownMax: INT = (1 << 30); assert!(pEdgeLeft.ErrorDown > 0 && pEdgeLeft.ErrorDown < nDbgErrorDownMax); assert!(pEdgeRight.ErrorDown > 0 && pEdgeRight.ErrorDown < nDbgErrorDownMax); // // errorUp: [0, errorDown) // assert!(pEdgeLeft.ErrorUp >= 0 && pEdgeLeft.ErrorUp < pEdgeLeft.ErrorDown); assert!(pEdgeRight.ErrorUp >= 0 && pEdgeRight.ErrorUp < pEdgeRight.ErrorDown); } // // Advance the left edge // // Since each point on the edge is withing 28.4 space, the following computation can't overflow. *nSubpixelXLeftBottom = pEdgeLeft.X.get() + nSubpixelYAdvance*pEdgeLeft.Dx; // Since the error values can be close to 2^30, we can get an overflow by multiplying with yAdvance. // So, we need to use a 64-bit temporary in this case. let mut llSubpixelErrorBottom: LONGLONG = pEdgeLeft.Error.get() as LONGLONG + Int32x32To64(nSubpixelYAdvance, pEdgeLeft.ErrorUp); if (llSubpixelErrorBottom >= 0) { let llSubpixelXLeftDelta = llSubpixelErrorBottom / (pEdgeLeft.ErrorDown as LONGLONG); // The delta should remain in range since it still represents a delta along the edge which // we know fits entirely in 28.4. Note that we add one here since the error must end up // less than 0. assert!(llSubpixelXLeftDelta < INT::MAX as LONGLONG); let nSubpixelXLeftDelta: INT = (llSubpixelXLeftDelta as INT) + 1; *nSubpixelXLeftBottom += nSubpixelXLeftDelta; llSubpixelErrorBottom -= Int32x32To64(pEdgeLeft.ErrorDown, nSubpixelXLeftDelta); } // At this point, the subtraction above should have generated an error that is within // (-pLeft->ErrorDown, 0) assert!((llSubpixelErrorBottom > -pEdgeLeft.ErrorDown as LONGLONG) && (llSubpixelErrorBottom < 0)); *nSubpixelErrorLeftBottom = (llSubpixelErrorBottom as INT); // // Advance the right edge // // Since each point on the edge is withing 28.4 space, the following computation can't overflow. *nSubpixelXRightBottom = pEdgeRight.X.get() + nSubpixelYAdvance*pEdgeRight.Dx; // Since the error values can be close to 2^30, we can get an overflow by multiplying with yAdvance. // So, we need to use a 64-bit temporary in this case. llSubpixelErrorBottom = pEdgeRight.Error.get() as LONGLONG + Int32x32To64(nSubpixelYAdvance, pEdgeRight.ErrorUp); if (llSubpixelErrorBottom >= 0) { let llSubpixelXRightDelta: LONGLONG = llSubpixelErrorBottom / (pEdgeRight.ErrorDown as LONGLONG); // The delta should remain in range since it still represents a delta along the edge which // we know fits entirely in 28.4. Note that we add one here since the error must end up // less than 0. assert!(llSubpixelXRightDelta < INT::MAX as LONGLONG); let nSubpixelXRightDelta: INT = (llSubpixelXRightDelta as INT) + 1; *nSubpixelXRightBottom += nSubpixelXRightDelta; llSubpixelErrorBottom -= Int32x32To64(pEdgeRight.ErrorDown, nSubpixelXRightDelta); } // At this point, the subtraction above should have generated an error that is within // (-pRight->ErrorDown, 0) assert!((llSubpixelErrorBottom > -pEdgeRight.ErrorDown as LONGLONG) && (llSubpixelErrorBottom < 0)); *nSubpixelErrorRightBottom = (llSubpixelErrorBottom as INT); } //------------------------------------------------------------------------- // // Function: ComputeDeltaUpperBound // // Synopsis: // Compute some value that is >= nSubpixelAdvanceY*|1/m| where m is the // slope defined by the edge below. // //------------------------------------------------------------------------- fn ComputeDeltaUpperBound( pEdge: &CEdge, // Edge containing 1/m value used for computation nSubpixelYAdvance: INT // Multiplier in synopsis expression ) -> INT { let nSubpixelDeltaUpperBound: INT; // // Compute the delta bound // if (pEdge.ErrorUp == 0) { // // No errorUp, so simply compute bound based on dx value // nSubpixelDeltaUpperBound = nSubpixelYAdvance*(pEdge.Dx).abs(); } else { let nAbsDx: INT; let nAbsErrorUp: INT; // // Compute abs of (dx, error) // // Here, we can assume errorUp > 0 // assert!(pEdge.ErrorUp > 0); if (pEdge.Dx >= 0) { nAbsDx = pEdge.Dx; nAbsErrorUp = pEdge.ErrorUp; } else { // // Dx < 0, so negate (dx, errorUp) // // Note that since errorUp > 0, we know -errorUp < 0 and that // we need to add errorDown to get an errorUp >= 0 which // also means substracting one from dx. // nAbsDx = -pEdge.Dx - 1; nAbsErrorUp = -pEdge.ErrorUp + pEdge.ErrorDown; } // // Compute the bound of nSubpixelAdvanceY*|1/m| // // Note that the +1 below is included to bound any left over errorUp that we are dropping here. // nSubpixelDeltaUpperBound = nSubpixelYAdvance*nAbsDx + (nSubpixelYAdvance*nAbsErrorUp)/pEdge.ErrorDown + 1; } return nSubpixelDeltaUpperBound; } //------------------------------------------------------------------------- // // Function: ComputeDistanceLowerBound // // Synopsis: // Compute some value that is <= distance between // (pEdgeLeft->X, pEdgeLeft->Error) and (pEdgeRight->X, pEdgeRight->Error) // //------------------------------------------------------------------------- fn ComputeDistanceLowerBound( pEdgeLeft: &CEdge, // Left edge containing the position for the distance computation pEdgeRight: &CEdge // Right edge containing the position for the distance computation ) -> INT { // // Note: In these comments, error1 and error2 are theoretical. The actual Error members // are biased by -1. // // distance = (x2 + error2/errorDown2) - (x1 + error1/errorDown1) // = x2 - x1 + error2/errorDown2 - error1/errorDown1 // >= x2 - x1 + error2/errorDown2 , since error1 < 0 // >= x2 - x1 - 1 , since error2 < 0 // = pEdgeRight->X - pEdgeLeft->X - 1 // // In the special case where error2/errorDown2 >= error1/errorDown1, we // can get a tigher bound of: // // pEdgeRight->X - pEdgeLeft->X // // This case occurs often in thin strokes, so we check for it here. // assert!(pEdgeLeft.Error.get() < 0); assert!(pEdgeRight.Error.get() < 0); assert!(pEdgeLeft.X <= pEdgeRight.X); let mut nSubpixelXDistanceLowerBound: INT = pEdgeRight.X.get() - pEdgeLeft.X.get(); // // If error2/errorDown2 < error1/errorDown1, we need to subtract one from the bound. // Note that error's are actually baised by -1, we so we have to add one before // we do the comparison. // if (IsFractionLessThan( pEdgeRight.Error.get()+1, pEdgeRight.ErrorDown, pEdgeLeft.Error.get()+1, pEdgeLeft.ErrorDown )) { // We can't use the tighter lower bound described above, so we need to subtract one to // ensure we have a lower bound. nSubpixelXDistanceLowerBound -= 1; } return nSubpixelXDistanceLowerBound; } pub struct CHwRasterizer<'x, 'y, 'z> { m_rcClipBounds: MilPointAndSizeL, m_matWorldToDevice: CMILMatrix, m_pIGeometrySink: &'x mut CHwVertexBufferBuilder<'y, 'z>, m_fillMode: MilFillMode, /* DynArray *m_prgPoints; DynArray *m_prgTypes; MilPointAndSizeL m_rcClipBounds; CMILMatrix m_matWorldToDevice; IGeometrySink *m_pIGeometrySink; MilFillMode::Enum m_fillMode; // // Complex scan coverage buffer // CCoverageBuffer m_coverageBuffer; CD3DDeviceLevel1 * m_pDeviceNoRef;*/ //m_coverageBuffer: CCoverageBuffer, } //------------------------------------------------------------------------- // // Function: CHwRasterizer::ConvertSubpixelXToPixel // // Synopsis: // Convert from our subpixel coordinate (x + error/errorDown) // to a floating point value. // //------------------------------------------------------------------------- fn ConvertSubpixelXToPixel( x: INT, error: INT, rErrorDown: f32 ) -> f32 { assert!(rErrorDown > f32::EPSILON); return ((x as f32) + (error as f32)/rErrorDown)*c_rInvShiftSize; } //------------------------------------------------------------------------- // // Function: CHwRasterizer::ConvertSubpixelYToPixel // // Synopsis: // Convert from our subpixel space to pixel space assuming no // error. // //------------------------------------------------------------------------- fn ConvertSubpixelYToPixel( nSubpixel: i32 ) -> f32 { return (nSubpixel as f32)*c_rInvShiftSize; } impl<'x, 'y, 'z> CHwRasterizer<'x, 'y, 'z> { //------------------------------------------------------------------------- // // Function: CHwRasterizer::RasterizePath // // Synopsis: // Internal rasterizer fill path. Note that this method follows the // same basic structure as the software rasterizer in aarasterizer.cpp. // // The general algorithm used for rasterization is a vertical sweep of // the shape that maintains an active edge list. The sweep is done // at a sub-scanline resolution and results in either: // 1. Sub-scanlines being combined in the coverage buffer and output // as "complex scans". // 2. Simple trapezoids being recognized in the active edge list // and output using a faster simple trapezoid path. // // This method consists of the setup to the main rasterization loop // which includes: // // 1. Setup of the clip rectangle // 2. Calling FixedPointPathEnumerate to populate our inactive // edge list. // 3. Delegating to RasterizePath to execute the main loop. // //------------------------------------------------------------------------- pub fn RasterizePath( &mut self, rgpt: &[POINT], rgTypes: &[BYTE], cPoints: UINT, pmatWorldTransform: &CMILMatrix ) -> HRESULT { let mut hr; // Default is not implemented for arrays of size 40 so we need to use map let mut inactiveArrayStack: [CInactiveEdge; INACTIVE_LIST_NUMBER!()] = [(); INACTIVE_LIST_NUMBER!()].map(|_| Default::default()); let mut pInactiveArray: &mut [CInactiveEdge]; let mut pInactiveArrayAllocation: Vec; let mut edgeHead: CEdge = Default::default(); let mut edgeTail: CEdge = Default::default(); let pEdgeActiveList: Ref; let mut edgeStore = Arena::new(); //edgeStore.init(); let mut edgeContext: CInitializeEdgesContext = CInitializeEdgesContext::new(&mut edgeStore); edgeContext.ClipRect = None; edgeTail.X.set(i32::MAX); // Terminator to active list edgeTail.StartY = i32::MAX; // Terminator to inactive list edgeTail.EndY = i32::MIN; edgeHead.X.set(i32::MIN); // Beginning of active list edgeContext.MaxY = i32::MIN; edgeHead.Next.set(Ref::new(&edgeTail)); pEdgeActiveList = Ref::new(&mut edgeHead); //edgeContext.Store = &mut edgeStore; edgeContext.AntiAliasMode = c_antiAliasMode; assert!(edgeContext.AntiAliasMode != MilAntiAliasMode::None); // If the path contains 0 or 1 points, we can ignore it. if (cPoints < 2) { return S_OK; } let nPixelYClipBottom: INT = self.m_rcClipBounds.Y + self.m_rcClipBounds.Height; // Scale the clip bounds rectangle by 16 to account for our // scaling to 28.4 coordinates: let mut clipBounds : RECT = Default::default(); clipBounds.left = self.m_rcClipBounds.X * FIX4_ONE!(); clipBounds.top = self.m_rcClipBounds.Y * FIX4_ONE!(); clipBounds.right = (self.m_rcClipBounds.X + self.m_rcClipBounds.Width) * FIX4_ONE!(); clipBounds.bottom = (self.m_rcClipBounds.Y + self.m_rcClipBounds.Height) * FIX4_ONE!(); edgeContext.ClipRect = Some(&clipBounds); ////////////////////////////////////////////////////////////////////////// // Convert all our points to 28.4 fixed point: let mut matrix: CMILMatrix = (*pmatWorldTransform).clone(); AppendScaleToMatrix(&mut matrix, TOREAL!(16), TOREAL!(16)); let coverageBuffer: CCoverageBuffer = Default::default(); // Initialize the coverage buffer coverageBuffer.Initialize(); // Enumerate the path and construct the edge table: hr = MIL_THR!(FixedPointPathEnumerate( rgpt, rgTypes, cPoints, &matrix, edgeContext.ClipRect, &mut edgeContext )); if (FAILED(hr)) { if (hr == WGXERR_VALUEOVERFLOW) { // Draw nothing on value overflow and return hr = S_OK; } return hr; } let nTotalCount: UINT; nTotalCount = edgeContext.Store.len() as u32; if (nTotalCount == 0) { hr = S_OK; // We're outta here (empty path or entirely clipped) return hr; } // At this point, there has to be at least two edges. If there's only // one, it means that we didn't do the trivially rejection properly. assert!((nTotalCount >= 2) && (nTotalCount <= (UINT::MAX - 2))); pInactiveArray = &mut inactiveArrayStack[..]; if (nTotalCount > (INACTIVE_LIST_NUMBER!() as u32 - 2)) { pInactiveArrayAllocation = vec![Default::default(); nTotalCount as usize + 2]; pInactiveArray = &mut pInactiveArrayAllocation; } // Initialize and sort the inactive array: let nSubpixelYCurrent = InitializeInactiveArray( edgeContext.Store, pInactiveArray, nTotalCount, Ref::new(&edgeTail) ); let mut nSubpixelYBottom = edgeContext.MaxY; assert!(nSubpixelYBottom > 0); // Skip the head sentinel on the inactive array: pInactiveArray = &mut pInactiveArray[1..]; // // Rasterize the path // // 'nPixelYClipBottom' is in screen space and needs to be converted to the // format we use for antialiasing. nSubpixelYBottom = nSubpixelYBottom.min(nPixelYClipBottom << c_nShift); // 'nTotalCount' should have been zero if all the edges were // clipped out (RasterizeEdges assumes there's at least one edge // to be drawn): assert!(nSubpixelYBottom > nSubpixelYCurrent); IFC!(self.RasterizeEdges( pEdgeActiveList, pInactiveArray, &coverageBuffer, nSubpixelYCurrent, nSubpixelYBottom )); return hr; } //------------------------------------------------------------------------- // // Function: CHwRasterizer::new // // Synopsis: // 1. Ensure clean state // 2. Convert path to internal format // //------------------------------------------------------------------------- pub fn new( pIGeometrySink: &'x mut CHwVertexBufferBuilder<'y, 'z>, fillMode: MilFillMode, pmatWorldToDevice: Option>, clipRect: MilPointAndSizeL, ) -> Self { // // PS#856364-2003/07/01-ashrafm Remove pixel center fixup // // Incoming coordinate space uses integers at upper-left of pixel (pixel // center are half integers) at device level. // // Rasterizer uses the coordinate space with integers at pixel center. // // To convert from center (1/2, 1/2) to center (0, 0) we need to subtract // 1/2 from each coordinate in device space. // // See InitializeEdges in aarasterizer.ccp to see how we unconvert for // antialiased rendering. // let mut matWorldHPCToDeviceIPC = pmatWorldToDevice.unwrap_or(CMatrix::Identity()); matWorldHPCToDeviceIPC.SetDx(matWorldHPCToDeviceIPC.GetDx() - 0.5); matWorldHPCToDeviceIPC.SetDy(matWorldHPCToDeviceIPC.GetDy() - 0.5); // // Set local state. // // There's an opportunity for early clipping here // // However, since the rasterizer itself does a reasonable job of clipping some // cases, we don't early clip yet. Self { m_fillMode: fillMode, m_rcClipBounds: clipRect, m_pIGeometrySink: pIGeometrySink, m_matWorldToDevice: matWorldHPCToDeviceIPC, } } //------------------------------------------------------------------------- // // Function: CHwRasterizer::SendGeometry // // Synopsis: // Tessellate and send geometry to the pipeline // //------------------------------------------------------------------------- pub fn SendGeometry(&mut self, points: &[POINT], types: &[BYTE], ) -> HRESULT { let mut hr = S_OK; // // Rasterize the path // let count = points.len() as u32; IFR!(self.RasterizePath( points, types, count, &self.m_matWorldToDevice.clone(), )); /* IFC!(self.RasterizePath( self.m_prgPoints.as_ref().unwrap().GetDataBuffer(), self.m_prgTypes.as_ref().unwrap().GetDataBuffer(), self.m_prgPoints.as_ref().unwrap().GetCount() as u32, &self.m_matWorldToDevice, self.m_fillMode ));*/ // // It's possible that we output no triangles. For example, if we tried to fill a // line instead of stroke it. Since we have no efficient way to detect all these cases // up front, we simply rasterize and see if we generated anything. // if (self.m_pIGeometrySink.IsEmpty()) { hr = WGXHR_EMPTYFILL; } RRETURN1!(hr, WGXHR_EMPTYFILL); } /* //------------------------------------------------------------------------- // // Function: CHwRasterizer::SendGeometryModifiers // // Synopsis: Send an AA color source to the pipeline. // //------------------------------------------------------------------------- fn SendGeometryModifiers(&self, pPipelineBuilder: &mut CHwPipelineBuilder ) -> HRESULT { let hr = S_OK; let pAntiAliasColorSource = None; self.m_pDeviceNoRef.GetColorComponentSource( CHwColorComponentSource::Diffuse, &pAntiAliasColorSource ); IFC!(pPipelineBuilder.Set_AAColorSource( pAntiAliasColorSource )); return hr; }*/ //------------------------------------------------------------------------- // // Function: CHwRasterizer::GenerateOutputAndClearCoverage // // Synopsis: // Collapse output and generate span data // //------------------------------------------------------------------------- fn GenerateOutputAndClearCoverage<'a>(&mut self, coverageBuffer: &'a CCoverageBuffer<'a>, nSubpixelY: INT ) -> HRESULT { let hr = S_OK; let nPixelY = nSubpixelY >> c_nShift; let pIntervalSpanStart: Ref = coverageBuffer.m_pIntervalStart.get(); IFC!(self.m_pIGeometrySink.AddComplexScan(nPixelY, pIntervalSpanStart)); coverageBuffer.Reset(); return hr; } //------------------------------------------------------------------------- // // Function: CHwRasterizer::ComputeTrapezoidsEndScan // // Synopsis: // This methods takes the current active edge list (and ycurrent) // and will determine: // // 1. Can we output some list of simple trapezoids for this active // edge list? If the answer is no, then we simply return // nSubpixelYCurrent indicating this condition. // // 2. If we can output some set of trapezoids, then what is the // next ycurrent, i.e., how tall are our trapezoids. // // Note that all trapezoids output for a particular active edge list // are all the same height. // // To further understand the conditions for making this decision, it // is important to consider the simple trapezoid tessellation: // // ___+_________________+___ // / + / \ + \ '+' marks active edges // / + / \ + \ // / + / \ + \ // /__+__/___________________\__+__\ // 1+1/m + // // Note that 1+1/edge_slope is the required expand distance to ensure // that we cover all pixels required. // // Now, we can fail to output any trapezoids under the following conditions: // 1. The expand regions along the top edge of the trapezoid overlap. // 2. The expand regions along the bottom edge of the trapezoid overlap // within the current scanline. Note that if the bottom edges overlap // at some later point, we can shorten our trapezoid to remove the // overlapping. // // The key to the algorithm at this point is to detect the above condition // in our active edge list and either update the returned end y position // or reject all together based on overlapping. // //------------------------------------------------------------------------- fn ComputeTrapezoidsEndScan(&mut self, pEdgeCurrent: Ref, nSubpixelYCurrent: INT, nSubpixelYNextInactive: INT ) -> INT { let mut nSubpixelYBottomTrapezoids; let mut pEdgeLeft: Ref; let mut pEdgeRight: Ref; // // Trapezoids should always start at scanline boundaries // assert!((nSubpixelYCurrent & c_nShiftMask) == 0); // // If we are doing a winding mode fill, check that we can ignore mode and do an // alternating fill in OutputTrapezoids. This condition occurs when winding is // equivalent to alternating which happens if the pairwise edges have different // winding directions. // if (self.m_fillMode == MilFillMode::Winding) { let mut pEdge = pEdgeCurrent; while pEdge.EndY != INT::MIN { // The active edge list always has an even number of edges which we actually // assert in ASSERTACTIVELIST. assert!(pEdge.Next.get().EndY != INT::MIN); // If not alternating winding direction, we can't fill with alternate mode if (pEdge.WindingDirection == pEdge.Next.get().WindingDirection) { // Give up until we handle winding mode nSubpixelYBottomTrapezoids = nSubpixelYCurrent; return nSubpixelYBottomTrapezoids; } pEdge = pEdge.Next.get().Next.get(); } } // // For each edge, we: // // 1. Set the new trapezoid bottom to the min of the current // one and the edge EndY // // 2. Check if edges will intersect during trapezoidal shrink/expand // nSubpixelYBottomTrapezoids = nSubpixelYNextInactive; let mut pEdge = pEdgeCurrent; while pEdge.EndY != INT::MIN { // // Step 1 // // Updated nSubpixelYBottomTrapezoids based on edge EndY. // // Since edges are clipped to the current clip rect y bounds, we also know // that pEdge->EndY <= nSubpixelYBottom so there is no need to check for that here. // nSubpixelYBottomTrapezoids = nSubpixelYBottomTrapezoids.min(pEdge.EndY); // // Step 2 // // Check that edges will not overlap during trapezoid shrink/expand. // pEdgeLeft = pEdge; pEdgeRight = pEdge.Next.get(); if (pEdgeRight.EndY != INT::MIN) { // // __A__A'___________________B'_B__ // \ + \ / + / '+' marks active edges // \ + \ / + / // \ + \ / + / // \__+__\____________/__+__/ // 1+1/m C C' D' D // // We need to determine if position A' <= position B' and that position C' <= position D' // in the above diagram. So, we need to ensure that both the distance between // A and B and the distance between C and D is greater than or equal to: // // 0.5 + |0.5/m1| + 0.5 + |0.5/m2| (pixel space) // = shiftsize + halfshiftsize*(|1/m1| + |1/m2|) (subpixel space) // // So, we'll start by computing this distance. Note that we can compute a distance // that is too large here since the self-intersection detection is simply used to // recognize trapezoid opportunities and isn't required for visual correctness. // let nSubpixelExpandDistanceUpperBound: INT = c_nShiftSize + ComputeDeltaUpperBound(&*pEdgeLeft, c_nHalfShiftSize) + ComputeDeltaUpperBound(&*pEdgeRight, c_nHalfShiftSize); // // Compute a top edge distance that is <= to the distance between A' and B' as follows: // lowerbound(distance(A, B)) - nSubpixelExpandDistanceUpperBound // let nSubpixelXTopDistanceLowerBound: INT = ComputeDistanceLowerBound(&*pEdgeLeft, &*pEdgeRight) - nSubpixelExpandDistanceUpperBound; // // Check if the top edges cross // if (nSubpixelXTopDistanceLowerBound < 0) { // The top edges have crossed, so we are out of luck. We can't // start a trapezoid on this scanline nSubpixelYBottomTrapezoids = nSubpixelYCurrent; return nSubpixelYBottomTrapezoids; } // // If the edges are converging, we need to check if they cross at // nSubpixelYBottomTrapezoids // // // 1) \ / 2) \ \ 3) / / // \ / \ \ / / // \ / \ \ / / // // The edges converge iff (dx1 > dx2 || (dx1 == dx2 && errorUp1/errorDown1 > errorUp2/errorDown2). // // Note that in the case where the edges do not converge, the code below will end up computing // the DDA at the end points and checking for intersection again. This code doesn't rely on // the fact that the edges don't converge, so we can be too conservative here. // if (pEdgeLeft.Dx > pEdgeRight.Dx || ((pEdgeLeft.Dx == pEdgeRight.Dx) && IsFractionGreaterThan(pEdgeLeft.ErrorUp, pEdgeLeft.ErrorDown, pEdgeRight.ErrorUp, pEdgeRight.ErrorDown))) { let nSubpixelYAdvance: INT = nSubpixelYBottomTrapezoids - nSubpixelYCurrent; assert!(nSubpixelYAdvance > 0); // // Compute the edge position at nSubpixelYBottomTrapezoids // let mut nSubpixelXLeftAdjustedBottom = 0; let mut nSubpixelErrorLeftBottom = 0; let mut nSubpixelXRightBottom = 0; let mut nSubpixelErrorRightBottom = 0; AdvanceDDAMultipleSteps( &*pEdgeLeft, &*pEdgeRight, nSubpixelYAdvance, &mut nSubpixelXLeftAdjustedBottom, &mut nSubpixelErrorLeftBottom, &mut nSubpixelXRightBottom, &mut nSubpixelErrorRightBottom ); // // Adjust the bottom left position by the expand distance for all the math // that follows. Note that since we adjusted the top distance by that // same expand distance, this adjustment is equivalent to moving the edges // nSubpixelExpandDistanceUpperBound closer together. // nSubpixelXLeftAdjustedBottom += nSubpixelExpandDistanceUpperBound; // // Check if the bottom edge crosses. // // To avoid checking error1/errDown1 and error2/errDown2, we assume the // edges cross if nSubpixelXLeftAdjustedBottom == nSubpixelXRightBottom // and thus produce a result that is too conservative. // if (nSubpixelXLeftAdjustedBottom >= nSubpixelXRightBottom) { // // At this point, we have the following scenario // // ____d1____ // \ / | | // \ / h1 | // \/ | | nSubpixelYAdvance // / \ | // /__d2__\ | // // We want to compute h1. We know that: // // h1 / nSubpixelYAdvance = d1 / (d1 + d2) // h1 = nSubpixelYAdvance * d1 / (d1 + d2) // // Now, if we approximate d1 with some d1' <= d1, we get // // h1 = nSubpixelYAdvance * d1 / (d1 + d2) // h1 >= nSubpixelYAdvance * d1' / (d1' + d2) // // Similarly, if we approximate d2 with some d2' >= d2, we get // // h1 >= nSubpixelYAdvance * d1' / (d1' + d2) // >= nSubpixelYAdvance * d1' / (d1' + d2') // // Since we are allowed to be too conservative with h1 (it can be // less than the actual value), we'll construct such approximations // for simplicity. // // Note that d1' = nSubpixelXTopDistanceLowerBound which we have already // computed. // // d2 = (x1 + error1/errorDown1) - (x2 + error2/errorDown2) // = x1 - x2 + error1/errorDown1 - error2/errorDown2 // <= x1 - x2 - error2/errorDown2 , since error1 < 0 // <= x1 - x2 + 1 , since error2 < 0 // = nSubpixelXLeftAdjustedBottom - nSubpixelXRightBottom + 1 // let nSubpixelXBottomDistanceUpperBound: INT = nSubpixelXLeftAdjustedBottom - nSubpixelXRightBottom + 1; assert!(nSubpixelXTopDistanceLowerBound >= 0); assert!(nSubpixelXBottomDistanceUpperBound > 0); #[cfg(debug_assertions)] let nDbgPreviousSubpixelXBottomTrapezoids: INT = nSubpixelYBottomTrapezoids; nSubpixelYBottomTrapezoids = nSubpixelYCurrent + (nSubpixelYAdvance * nSubpixelXTopDistanceLowerBound) / (nSubpixelXTopDistanceLowerBound + nSubpixelXBottomDistanceUpperBound); #[cfg(debug_assertions)] assert!(nDbgPreviousSubpixelXBottomTrapezoids >= nSubpixelYBottomTrapezoids); if (nSubpixelYBottomTrapezoids < nSubpixelYCurrent + c_nShiftSize) { // We no longer have a trapezoid that is at least one scanline high, so // abort nSubpixelYBottomTrapezoids = nSubpixelYCurrent; return nSubpixelYBottomTrapezoids; } } } } pEdge = pEdge.Next.get(); } // // Snap to pixel boundary // nSubpixelYBottomTrapezoids = nSubpixelYBottomTrapezoids & (!c_nShiftMask); // // Ensure that we are never less than nSubpixelYCurrent // assert!(nSubpixelYBottomTrapezoids >= nSubpixelYCurrent); // // Return trapezoid end scan // //Cleanup: return nSubpixelYBottomTrapezoids; } //------------------------------------------------------------------------- // // Function: CHwRasterizer::OutputTrapezoids // // Synopsis: // Given the current active edge list, output a list of // trapezoids. // // _________________________ // / / \ \ // / / \ \ // / / \ \ // /_____/___________________\_____\ // 1+1/m // // We output a trapezoid where the distance in X is 1+1/m slope on either edge. // Note that we actually do a linear interpolation for coverage along the // entire falloff region which comes within 12.5% error when compared to our // 8x8 coverage output for complex scans. What is happening here is // that we are applying a linear approximation to the coverage function // based on slope. It is possible to get better linear interpolations // by varying the expanded region, but it hasn't been necessary to apply // these quality improvements yet. // //------------------------------------------------------------------------- fn OutputTrapezoids(&mut self, pEdgeCurrent: Ref, nSubpixelYCurrent: INT, // inclusive nSubpixelYNext: INT // exclusive ) -> HRESULT { let hr = S_OK; let nSubpixelYAdvance: INT; let mut rSubpixelLeftErrorDown: f32; let mut rSubpixelRightErrorDown: f32; let mut rPixelXLeft: f32; let mut rPixelXRight: f32; let mut rSubpixelLeftInvSlope: f32; let mut rSubpixelLeftAbsInvSlope: f32; let mut rSubpixelRightInvSlope: f32; let mut rSubpixelRightAbsInvSlope: f32; let mut rPixelXLeftDelta: f32; let mut rPixelXRightDelta: f32; let mut pEdgeLeft = pEdgeCurrent; let mut pEdgeRight = (*pEdgeCurrent).Next.get(); assert!((nSubpixelYCurrent & c_nShiftMask) == 0); assert!(pEdgeLeft.EndY != INT::MIN); assert!(pEdgeRight.EndY != INT::MIN); // // Compute the height our trapezoids // nSubpixelYAdvance = nSubpixelYNext - nSubpixelYCurrent; // // Output each trapezoid // loop { // // Compute x/error for end of trapezoid // let mut nSubpixelXLeftBottom: INT = 0; let mut nSubpixelErrorLeftBottom: INT = 0; let mut nSubpixelXRightBottom: INT = 0; let mut nSubpixelErrorRightBottom: INT = 0; AdvanceDDAMultipleSteps( &*pEdgeLeft, &*pEdgeRight, nSubpixelYAdvance, &mut nSubpixelXLeftBottom, &mut nSubpixelErrorLeftBottom, &mut nSubpixelXRightBottom, &mut nSubpixelErrorRightBottom ); // The above computation should ensure that we are a simple // trapezoid at this point assert!(nSubpixelXLeftBottom <= nSubpixelXRightBottom); // We know we have a simple trapezoid now. Now, compute the end of our current trapezoid assert!(nSubpixelYAdvance > 0); // // Computation of edge data // rSubpixelLeftErrorDown = pEdgeLeft.ErrorDown as f32; rSubpixelRightErrorDown = pEdgeRight.ErrorDown as f32; rPixelXLeft = ConvertSubpixelXToPixel(pEdgeLeft.X.get(), pEdgeLeft.Error.get(), rSubpixelLeftErrorDown); rPixelXRight = ConvertSubpixelXToPixel(pEdgeRight.X.get(), pEdgeRight.Error.get(), rSubpixelRightErrorDown); rSubpixelLeftInvSlope = pEdgeLeft.Dx as f32 + pEdgeLeft.ErrorUp as f32/rSubpixelLeftErrorDown; rSubpixelLeftAbsInvSlope = rSubpixelLeftInvSlope.abs(); rSubpixelRightInvSlope = pEdgeRight.Dx as f32 + pEdgeRight.ErrorUp as f32/rSubpixelRightErrorDown; rSubpixelRightAbsInvSlope = rSubpixelRightInvSlope.abs(); rPixelXLeftDelta = 0.5 + 0.5 * rSubpixelLeftAbsInvSlope; rPixelXRightDelta = 0.5 + 0.5 * rSubpixelRightAbsInvSlope; let rPixelYTop = ConvertSubpixelYToPixel(nSubpixelYCurrent); let rPixelYBottom = ConvertSubpixelYToPixel(nSubpixelYNext); let rPixelXBottomLeft = ConvertSubpixelXToPixel( nSubpixelXLeftBottom, nSubpixelErrorLeftBottom, pEdgeLeft.ErrorDown as f32 ); let rPixelXBottomRight = ConvertSubpixelXToPixel( nSubpixelXRightBottom, nSubpixelErrorRightBottom, pEdgeRight.ErrorDown as f32 ); // // Output the trapezoid // IFC!(self.m_pIGeometrySink.AddTrapezoid( rPixelYTop, // In: y coordinate of top of trapezoid rPixelXLeft, // In: x coordinate for top left rPixelXRight, // In: x coordinate for top right rPixelYBottom, // In: y coordinate of bottom of trapezoid rPixelXBottomLeft, // In: x coordinate for bottom left rPixelXBottomRight, // In: x coordinate for bottom right rPixelXLeftDelta, // In: trapezoid expand radius for left edge rPixelXRightDelta // In: trapezoid expand radius for right edge )); // // Update the edge data // // no need to do this if edges are stale pEdgeLeft.X.set(nSubpixelXLeftBottom); pEdgeLeft.Error.set(nSubpixelErrorLeftBottom); pEdgeRight.X.set(nSubpixelXRightBottom); pEdgeRight.Error.set(nSubpixelErrorRightBottom); // // Check for termination // if (pEdgeRight.Next.get().EndY == INT::MIN) { break; } // // Advance edge data // pEdgeLeft = pEdgeRight.Next.get(); pEdgeRight = pEdgeLeft.Next.get(); } return hr; } //------------------------------------------------------------------------- // // Function: CHwRasterizer::RasterizeEdges // // Synopsis: // Rasterize using trapezoidal AA // //------------------------------------------------------------------------- fn RasterizeEdges<'a, 'b>(&mut self, pEdgeActiveList: Ref<'a, CEdge<'a>>, mut pInactiveEdgeArray: &'a mut [CInactiveEdge<'a>], coverageBuffer: &'b CCoverageBuffer<'b>, mut nSubpixelYCurrent: INT, nSubpixelYBottom: INT ) -> HRESULT { let hr: HRESULT = S_OK; let mut pEdgePrevious: Ref; let mut pEdgeCurrent: Ref; let mut nSubpixelYNextInactive: INT = 0; let mut nSubpixelYNext: INT; pInactiveEdgeArray = InsertNewEdges( pEdgeActiveList, nSubpixelYCurrent, pInactiveEdgeArray, &mut nSubpixelYNextInactive ); while (nSubpixelYCurrent < nSubpixelYBottom) { ASSERTACTIVELIST!(pEdgeActiveList, nSubpixelYCurrent); // // Detect trapezoidal case // pEdgePrevious = pEdgeActiveList; pEdgeCurrent = pEdgeActiveList.Next.get(); nSubpixelYNext = nSubpixelYCurrent; if (!IsTagEnabled!(tagDisableTrapezoids) && (nSubpixelYCurrent & c_nShiftMask) == 0 && pEdgeCurrent.EndY != INT::MIN && nSubpixelYNextInactive >= nSubpixelYCurrent + c_nShiftSize ) { // Edges are paired, so we can assert we have another one assert!(pEdgeCurrent.Next.get().EndY != INT::MIN); // // Given an active edge list, we compute the furthest we can go in the y direction // without creating self-intersection or going past the edge EndY. Note that if we // can't even go one scanline, then nSubpixelYNext == nSubpixelYCurrent // nSubpixelYNext = self.ComputeTrapezoidsEndScan(Ref::new(&*pEdgeCurrent), nSubpixelYCurrent, nSubpixelYNextInactive); assert!(nSubpixelYNext >= nSubpixelYCurrent); // // Attempt to output a trapezoid. If it turns out we don't have any // potential trapezoids, then nSubpixelYNext == nSubpixelYCurent // indicating that we need to fall back to complex scans. // if (nSubpixelYNext >= nSubpixelYCurrent + c_nShiftSize) { IFC!(self.OutputTrapezoids( pEdgeCurrent, nSubpixelYCurrent, nSubpixelYNext )); } } // // Rasterize simple trapezoid or a complex scanline // if (nSubpixelYNext > nSubpixelYCurrent) { // If we advance, it must be by at least one scan line assert!(nSubpixelYNext - nSubpixelYCurrent >= c_nShiftSize); // Advance nSubpixelYCurrent nSubpixelYCurrent = nSubpixelYNext; // Remove stale edges. Note that the DDA is incremented in OutputTrapezoids. while (pEdgeCurrent.EndY != INT::MIN) { if (pEdgeCurrent.EndY <= nSubpixelYCurrent) { // Unlink and advance pEdgeCurrent = pEdgeCurrent.Next.get(); pEdgePrevious.Next.set(pEdgeCurrent); } else { // Advance pEdgePrevious = pEdgeCurrent; pEdgeCurrent = pEdgeCurrent.Next.get(); } } } else { // // Trapezoid rasterization failed, so // 1) Handle case with no active edges, or // 2) fall back to scan rasterization // if (pEdgeCurrent.EndY == INT::MIN) { nSubpixelYNext = nSubpixelYNextInactive; } else { nSubpixelYNext = nSubpixelYCurrent + 1; if (self.m_fillMode == MilFillMode::Alternate) { IFC!(coverageBuffer.FillEdgesAlternating(pEdgeActiveList, nSubpixelYCurrent)); } else { IFC!(coverageBuffer.FillEdgesWinding(pEdgeActiveList, nSubpixelYCurrent)); } } // If the next scan is done, output what's there: if (nSubpixelYNext > (nSubpixelYCurrent | c_nShiftMask)) { IFC!(self.GenerateOutputAndClearCoverage(coverageBuffer, nSubpixelYCurrent)); } // Advance nSubpixelYCurrent nSubpixelYCurrent = nSubpixelYNext; // Advance DDA and update edge list AdvanceDDAAndUpdateActiveEdgeList(nSubpixelYCurrent, pEdgeActiveList); } // // Update edge list // if (nSubpixelYCurrent == nSubpixelYNextInactive) { pInactiveEdgeArray = InsertNewEdges( pEdgeActiveList, nSubpixelYCurrent, pInactiveEdgeArray, &mut nSubpixelYNextInactive ); } } // // Output the last scanline that has partial coverage // if ((nSubpixelYCurrent & c_nShiftMask) != 0) { IFC!(self.GenerateOutputAndClearCoverage(coverageBuffer, nSubpixelYCurrent)); } RRETURN!(hr); } }