From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../rust/wpf-gpu-raster/src/hwrasterizer.rs | 1455 ++++++++++++++++++++ 1 file changed, 1455 insertions(+) create mode 100644 third_party/rust/wpf-gpu-raster/src/hwrasterizer.rs (limited to 'third_party/rust/wpf-gpu-raster/src/hwrasterizer.rs') diff --git a/third_party/rust/wpf-gpu-raster/src/hwrasterizer.rs b/third_party/rust/wpf-gpu-raster/src/hwrasterizer.rs new file mode 100644 index 0000000000..49fed1a1bf --- /dev/null +++ b/third_party/rust/wpf-gpu-raster/src/hwrasterizer.rs @@ -0,0 +1,1455 @@ +// 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); +} + +} -- cgit v1.2.3