summaryrefslogtreecommitdiffstats
path: root/third_party/rust/wpf-gpu-raster
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/wpf-gpu-raster')
-rw-r--r--third_party/rust/wpf-gpu-raster/.cargo-checksum.json1
-rw-r--r--third_party/rust/wpf-gpu-raster/.github/workflows/coverage.yml23
-rw-r--r--third_party/rust/wpf-gpu-raster/.github/workflows/rust.yml39
-rw-r--r--third_party/rust/wpf-gpu-raster/CHANGES.md22
-rw-r--r--third_party/rust/wpf-gpu-raster/Cargo.toml32
-rw-r--r--third_party/rust/wpf-gpu-raster/LICENSE23
-rw-r--r--third_party/rust/wpf-gpu-raster/README.md22
-rw-r--r--third_party/rust/wpf-gpu-raster/examples/draw.rs354
-rw-r--r--third_party/rust/wpf-gpu-raster/examples/obj-output.rs26
-rw-r--r--third_party/rust/wpf-gpu-raster/examples/simple.rs11
-rw-r--r--third_party/rust/wpf-gpu-raster/notes8
-rw-r--r--third_party/rust/wpf-gpu-raster/src/aacoverage.rs647
-rw-r--r--third_party/rust/wpf-gpu-raster/src/aarasterizer.rs1768
-rw-r--r--third_party/rust/wpf-gpu-raster/src/bezier.rs990
-rw-r--r--third_party/rust/wpf-gpu-raster/src/c_bindings.rs158
-rw-r--r--third_party/rust/wpf-gpu-raster/src/fix.rs9
-rw-r--r--third_party/rust/wpf-gpu-raster/src/geometry_sink.rs92
-rw-r--r--third_party/rust/wpf-gpu-raster/src/helpers.rs55
-rw-r--r--third_party/rust/wpf-gpu-raster/src/hwrasterizer.rs1455
-rw-r--r--third_party/rust/wpf-gpu-raster/src/hwvertexbuffer.rs3075
-rw-r--r--third_party/rust/wpf-gpu-raster/src/lib.rs691
-rw-r--r--third_party/rust/wpf-gpu-raster/src/matrix.rs37
-rw-r--r--third_party/rust/wpf-gpu-raster/src/notes12
-rw-r--r--third_party/rust/wpf-gpu-raster/src/nullable_ref.rs53
-rw-r--r--third_party/rust/wpf-gpu-raster/src/real.rs163
-rw-r--r--third_party/rust/wpf-gpu-raster/src/tri_rasterize.rs190
-rw-r--r--third_party/rust/wpf-gpu-raster/src/types.rs181
27 files changed, 10137 insertions, 0 deletions
diff --git a/third_party/rust/wpf-gpu-raster/.cargo-checksum.json b/third_party/rust/wpf-gpu-raster/.cargo-checksum.json
new file mode 100644
index 0000000000..90a4069198
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{".github/workflows/coverage.yml":"90aaa068c16cb778b24badaff78baf2a313637780a723be09596abde0f4c827a",".github/workflows/rust.yml":"905954be896d052ced621eedb9d5b9d35795490f27071ac1147e75ac3b3711ec","CHANGES.md":"5f54e553a1c4ef21c5be6109b25df9d1d63c4547627723fe044c73dbddf0db2f","Cargo.toml":"912553cc7b6024b0acbba42d6b64020fc98086596e99765ae95c1808ad0ab2d1","LICENSE":"ae48df11a335dc1a615f4f938b69cba73bcf4485c4f97af49b38efb0f216353b","README.md":"e14b7ddbd29b6f87d956921999da1cf7bc3add0166cacf21e8b1ac1d9092a90d","examples/draw.rs":"52fee9e2f2c11e1c891b30cb460be2a0ec65974f38dc0c08fd48391caf1e4247","examples/obj-output.rs":"6fc549022aa715eee74ea1cafb89ca33189e9dbe914ea6b2c46160049bda68f3","examples/simple.rs":"99fb566414cbd4a0eb69a2774c9780d7cd17e5cdaa14837b280fba319c053f22","notes":"48e636c646d697e213b3a79e31063e11b6ffc7493592d31f3929b1db495870b8","src/aacoverage.rs":"fdadadd208caa986cc386797f937a976b5a315174c7c0782b87c0334d6474a97","src/aarasterizer.rs":"283bed1e22917118f332b24731cb6bd11334a4f0ba0d88821cfeb6b607de12da","src/bezier.rs":"f089ab04e30077ce4e0fe59dfa602948b989aa53d51ad207fbc30c1edd24086b","src/c_bindings.rs":"9c5ab638cf0a14220d93528e37cdc0f6d83277eaa10acf9ce36f32a28e30c02b","src/fix.rs":"7ccf63db5bab4ab0135d92691f7c2272a27866b9792dd55ec98b2d1c1b7c0358","src/geometry_sink.rs":"9025569f77f475a1e47fd470e8f53dcdf88ef57e3a5b8a51268fff892da8b1a7","src/helpers.rs":"220294dac335943518f249c4a27ad803f8226ed62cd780f517e95be6343a1f2f","src/hwrasterizer.rs":"82b2d6d35488a6ad7de4d82f3ee38c6f09f4b6de06b4f98eea61b3abdd72eb62","src/hwvertexbuffer.rs":"f3dd54f17570eb530c9c827b24a53b755a2dfa6028e9b83f9d7a4ba9945c2ecf","src/lib.rs":"8cd659ff6e2165884eaa1657462e1a5bf15a68a83cb768c2dff2f433ff0a022c","src/matrix.rs":"1ac44bc5d073f96ab64b1b5c6077fd0d47fe61db8243bd9a55fc91d8eae1dd92","src/notes":"d50d49e0b5660bc6350d8055f25f26700c937558de0af690e1fc4f50ed7e05c9","src/nullable_ref.rs":"789fe0e59b7d4a925faecbf2362be93643ea8382b4424ca0e60866f9bf83c3cd","src/real.rs":"73a2d1a77613364e9514fd7ead4d708a554d2b7343645cdb4cb8a2b3b640e057","src/tri_rasterize.rs":"30821a3465cea3c5ac578590013b530c03ea3010225f580d6cf609e39910c412","src/types.rs":"b840212a99a212ef38211aaf1bd801ec83416569541941d15fd95285d1342b99"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/wpf-gpu-raster/.github/workflows/coverage.yml b/third_party/rust/wpf-gpu-raster/.github/workflows/coverage.yml
new file mode 100644
index 0000000000..3f2e6f2523
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/.github/workflows/coverage.yml
@@ -0,0 +1,23 @@
+name: Coverage
+
+on: [pull_request, push]
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ env:
+ CARGO_TERM_COLOR: always
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Rust
+ run: rustup toolchain install stable --component llvm-tools-preview
+ - name: Install cargo-llvm-cov
+ uses: taiki-e/install-action@cargo-llvm-cov
+ - name: Generate code coverage
+ run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
+ files: lcov.info
+ fail_ci_if_error: true
diff --git a/third_party/rust/wpf-gpu-raster/.github/workflows/rust.yml b/third_party/rust/wpf-gpu-raster/.github/workflows/rust.yml
new file mode 100644
index 0000000000..1f38bfc1f7
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/.github/workflows/rust.yml
@@ -0,0 +1,39 @@
+name: Rust
+
+on:
+ push:
+ pull_request:
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Build
+ run: cargo build --verbose
+ - name: Run tests
+ run: cargo test --verbose
+
+ aarch64:
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Install toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+ target: aarch64-unknown-linux-gnu
+
+ - name: Install cross
+ run: cargo install cross
+
+ - name: Run tests with Neon
+ run: cross test --target aarch64-unknown-linux-gnu
diff --git a/third_party/rust/wpf-gpu-raster/CHANGES.md b/third_party/rust/wpf-gpu-raster/CHANGES.md
new file mode 100644
index 0000000000..8de9816947
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/CHANGES.md
@@ -0,0 +1,22 @@
+Changes for Safety
+------------------
+
+`CEdgeStore` is replaced by `typed_arena_nomut::Arena<CEdge>`.
+
+`CEdgeStore` is an arena with built-in stack storage for the first allocation
+of the arena. It exposes the allocated buffers to support very fast allocation,
+and supports fast enumeration by returning pointers to each allocation.
+
+`CCoverageBuffer` also now uses a `typed_arena_nomut::Arena<CEdge>` but uses it
+to allocate `CCoverageIntervalBuffer`'s. We currently lack support for
+the builtin stack storage. Storing these in an Arena is not ideal, we'd rather
+just heap allocate them individually.
+
+
+Changes for performance
+-----------------------
+
+Switched from using triangle strips to triangle lists. This lets
+us use a single triangle to draw each line segement which reduces
+the amount of geometry per line segment from 6 vertices to 3.
+Direct2D also made this switch in later versions.
diff --git a/third_party/rust/wpf-gpu-raster/Cargo.toml b/third_party/rust/wpf-gpu-raster/Cargo.toml
new file mode 100644
index 0000000000..79ec71f76c
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/Cargo.toml
@@ -0,0 +1,32 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "wpf-gpu-raster"
+version = "0.1.0"
+readme = "README.md"
+license = "MIT"
+
+[profile.release]
+debug = true
+
+[dependencies]
+typed-arena-nomut = "0.1.0"
+
+[dev-dependencies]
+euclid = "0.22.6"
+png = "0.17.2"
+usvg = "0.4"
+
+[features]
+c_bindings = []
+default = ["c_bindings"]
diff --git a/third_party/rust/wpf-gpu-raster/LICENSE b/third_party/rust/wpf-gpu-raster/LICENSE
new file mode 100644
index 0000000000..a616ed188d
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/LICENSE
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) .NET Foundation and Contributors
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/third_party/rust/wpf-gpu-raster/README.md b/third_party/rust/wpf-gpu-raster/README.md
new file mode 100644
index 0000000000..1d4756b13f
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/README.md
@@ -0,0 +1,22 @@
+This is a port of the WPF hardware rasterizer code to Rust. That
+rasterizer is predecessor to the Direct2D rasterizer. Direct2D still
+uses a similar technique when run on hardware that does not support
+Target Independent Rasterization.
+
+Design
+======
+
+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". These are emitted as lines constructed out
+ of triangle strips.
+ 2. Simple trapezoids being recognized in the active edge list
+ and output using a faster simple trapezoid path.
+
+Bezier flattening is done using an approach that uses forward differencing
+of the error metric to compute a flattened version that would match a traditional
+adaptive recursive flattening.
+
+
diff --git a/third_party/rust/wpf-gpu-raster/examples/draw.rs b/third_party/rust/wpf-gpu-raster/examples/draw.rs
new file mode 100644
index 0000000000..828449ee72
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/examples/draw.rs
@@ -0,0 +1,354 @@
+/* The rasterization code here is based off of piglit/tests/general/triangle-rasterization.cpp:
+
+ /**************************************************************************
+ *
+ * Copyright 2012 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ **************************************************************************/
+
+*/
+
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+use euclid::{default::Transform2D, point2};
+use wpf_gpu_raster::{PathBuilder};
+
+
+use std::ops::Index;
+
+
+const WIDTH: u32 = 800;
+const HEIGHT: u32 = 800;
+
+
+fn over(src: u32, dst: u32) -> u32 {
+ let a = src >> 24;
+ let a = 255 - a;
+ let mask = 0xff00ff;
+ let t = (dst & mask) * a + 0x800080;
+ let mut rb = (t + ((t >> 8) & mask)) >> 8;
+ rb &= mask;
+
+ rb += src & mask;
+
+ // saturate
+ rb |= 0x1000100 - ((rb >> 8) & mask);
+ rb &= mask;
+
+ let t = ((dst >> 8) & mask) * a + 0x800080;
+ let mut ag = (t + ((t >> 8) & mask)) >> 8;
+ ag &= mask;
+ ag += (src >> 8) & mask;
+
+ // saturate
+ ag |= 0x1000100 - ((ag >> 8) & mask);
+ ag &= mask;
+
+ (ag << 8) + rb
+}
+
+pub fn alpha_mul(x: u32, a: u32) -> u32 {
+ let mask = 0xFF00FF;
+
+ let src_rb = ((x & mask) * a) >> 8;
+ let src_ag = ((x >> 8) & mask) * a;
+
+ (src_rb & mask) | (src_ag & !mask)
+}
+
+fn write_image(data: &[u32], path: &str) {
+ use std::path::Path;
+ use std::fs::File;
+ use std::io::BufWriter;
+
+ let mut png_data: Vec<u8> = vec![0; (WIDTH * HEIGHT * 3) as usize];
+ let mut i = 0;
+ for pixel in data {
+ png_data[i] = ((pixel >> 16) & 0xff) as u8;
+ png_data[i + 1] = ((pixel >> 8) & 0xff) as u8;
+ png_data[i + 2] = ((pixel >> 0) & 0xff) as u8;
+ i += 3;
+ }
+
+
+ let path = Path::new(path);
+ let file = File::create(path).unwrap();
+ let w = &mut BufWriter::new(file);
+
+ let mut encoder = png::Encoder::new(w, WIDTH, HEIGHT); // Width is 2 pixels and height is 1.
+ encoder.set_color(png::ColorType::Rgb);
+ encoder.set_depth(png::BitDepth::Eight);
+ let mut writer = encoder.write_header().unwrap();
+
+ writer.write_image_data(&png_data).unwrap(); // Save
+}
+
+#[derive(Debug)]
+struct Vertex {
+ x: f32,
+ y: f32,
+ coverage: f32
+}
+#[derive(Debug)]
+struct Triangle {
+ v: [Vertex; 3],
+}
+
+impl Index<usize> for Triangle {
+ type Output = Vertex;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.v[index]
+ }
+}
+
+// D3D11 mandates 8 bit subpixel precision:
+// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#CoordinateSnapping
+const FIXED_SHIFT: i32 = 8;
+const FIXED_ONE: f32 = (1 << FIXED_SHIFT) as f32;
+
+/* Proper rounding of float to integer */
+fn iround(mut v: f32) -> i64 {
+ if v > 0.0 {
+ v += 0.5;
+ }
+ if v < 0.0 {
+ v -= 0.5;
+ }
+ return v as i64
+}
+
+/* Based on http://devmaster.net/forums/topic/1145-advanced-rasterization */
+fn rast_triangle(buffer: &mut [u32], stride: usize, tri: &Triangle, color: u32) {
+ let center_offset = -0.5;
+
+ let mut coverage1 = tri[0].coverage;
+ let mut coverage2 = tri[1].coverage;
+ let mut coverage3 = tri[2].coverage;
+
+ /* fixed point coordinates */
+ let mut x1 = iround(FIXED_ONE * (tri[0].x + center_offset));
+ let x2 = iround(FIXED_ONE * (tri[1].x + center_offset));
+ let mut x3 = iround(FIXED_ONE * (tri[2].x + center_offset));
+
+ let mut y1 = iround(FIXED_ONE * (tri[0].y + center_offset));
+ let y2 = iround(FIXED_ONE * (tri[1].y + center_offset));
+ let mut y3 = iround(FIXED_ONE * (tri[2].y + center_offset));
+
+
+ /* Force correct vertex order */
+ let cross = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2);
+ if cross > 0 {
+ std::mem::swap(&mut x1, &mut x3);
+ std::mem::swap(&mut y1, &mut y3);
+ // I don't understand why coverage 2 and 3 are swapped instead of 1 and 3
+ std::mem::swap(&mut coverage2, &mut coverage3);
+ } else {
+ std::mem::swap(&mut coverage1, &mut coverage3);
+ }
+
+ /* Deltas */
+ let dx12 = x1 - x2;
+ let dx23 = x2 - x3;
+ let dx31 = x3 - x1;
+
+ let dy12 = y1 - y2;
+ let dy23 = y2 - y3;
+ let dy31 = y3 - y1;
+
+ /* Fixed-point deltas */
+ let fdx12 = dx12 << FIXED_SHIFT;
+ let fdx23 = dx23 << FIXED_SHIFT;
+ let fdx31 = dx31 << FIXED_SHIFT;
+
+ let fdy12 = dy12 << FIXED_SHIFT;
+ let fdy23 = dy23 << FIXED_SHIFT;
+ let fdy31 = dy31 << FIXED_SHIFT;
+
+ /* Bounding rectangle */
+ let mut minx = x1.min(x2).min(x3) >> FIXED_SHIFT;
+ let mut maxx = x1.max(x2).max(x3) >> FIXED_SHIFT;
+
+ let mut miny = y1.min(y2).min(y3) >> FIXED_SHIFT;
+ let mut maxy = y1.max(y2).max(y3) >> FIXED_SHIFT;
+
+ minx = minx.max(0);
+ maxx = maxx.min(WIDTH as i64 - 1);
+
+ miny = miny.max(0);
+ maxy = maxy.min(HEIGHT as i64 - 1);
+
+ /* Half-edge constants */
+ let mut c1 = dy12 * x1 - dx12 * y1;
+ let mut c2 = dy23 * x2 - dx23 * y2;
+ let mut c3 = dy31 * x3 - dx31 * y3;
+
+ /* Correct for top-left filling convention */
+ if dy12 < 0 || (dy12 == 0 && dx12 < 0) { c1 += 1 }
+ if dy23 < 0 || (dy23 == 0 && dx23 < 0) { c2 += 1 }
+ if dy31 < 0 || (dy31 == 0 && dx31 < 0) { c3 += 1 }
+
+ let mut cy1 = c1 + dx12 * (miny << FIXED_SHIFT) - dy12 * (minx << FIXED_SHIFT);
+ let mut cy2 = c2 + dx23 * (miny << FIXED_SHIFT) - dy23 * (minx << FIXED_SHIFT);
+ let mut cy3 = c3 + dx31 * (miny << FIXED_SHIFT) - dy31 * (minx << FIXED_SHIFT);
+
+ /* Perform rasterization */
+ let mut buffer = &mut buffer[miny as usize * stride..];
+ for _y in miny..=maxy {
+ let mut cx1 = cy1;
+ let mut cx2 = cy2;
+ let mut cx3 = cy3;
+
+ for x in minx..=maxx {
+ if cx1 > 0 && cx2 > 0 && cx3 > 0 {
+ // cross is equal to 2*area of the triangle.
+ // we can normalize cx by 2*area to get barycentric coords.
+ let area = cross.abs() as f32;
+ let bary = (cx1 as f32 / area, cx2 as f32/ area, cx3 as f32 / area);
+
+ let coverages = coverage1 * bary.0 + coverage2 * bary.1 + coverage3 * bary.2;
+
+ let color = alpha_mul(color, (coverages * 256. + 0.5) as u32);
+ buffer[x as usize] = over(color, buffer[x as usize]);
+ }
+
+ cx1 -= fdy12;
+ cx2 -= fdy23;
+ cx3 -= fdy31;
+ }
+
+ cy1 += fdx12;
+ cy2 += fdx23;
+ cy3 += fdx31;
+
+ buffer = &mut buffer[stride..];
+ }
+}
+
+
+fn main() {
+ let opt = usvg::Options::default();
+
+ let rtree = usvg::Tree::from_file("tiger.svg", &opt).unwrap();
+
+ let mut image = vec![0; (WIDTH * HEIGHT) as usize];
+ for _ in 0..1 {
+ let mut total_vertex_count = 0;
+ let mut total_time = std::time::Duration::default();
+ for node in rtree.root().descendants() {
+ use usvg::NodeExt;
+ let t = node.transform();
+ let transform = Transform2D::new(
+ t.a as f32, t.b as f32,
+ t.c as f32, t.d as f32,
+ t.e as f32, t.f as f32,
+ );
+
+
+ let s = 1.;
+ if let usvg::NodeKind::Path(ref usvg_path) = *node.borrow() {
+ let color = match usvg_path.fill {
+ Some(ref fill) => {
+ match fill.paint {
+ usvg::Paint::Color(c) => 0xff000000 | (c.red as u32) << 16 | (c.green as u32) << 8 | c.blue as u32,
+ _ => 0xff00ff00,
+ }
+ }
+ None => {
+ continue;
+ }
+ };
+ let mut builder = PathBuilder::new();
+ //dbg!(&usvg_path.segments);
+ for segment in &usvg_path.segments {
+ match *segment {
+ usvg::PathSegment::MoveTo { x, y } => {
+ let p = transform.transform_point(point2(x as f32, y as f32)) * s;
+ builder.move_to(p.x, p.y);
+ }
+ usvg::PathSegment::LineTo { x, y } => {
+ let p = transform.transform_point(point2(x as f32, y as f32)) * s;
+ builder.line_to(p.x, p.y);
+ }
+ usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y, } => {
+ let c1 = transform.transform_point(point2(x1 as f32, y1 as f32)) * s;
+ let c2 = transform.transform_point(point2(x2 as f32, y2 as f32)) * s;
+ let p = transform.transform_point(point2(x as f32, y as f32)) * s;
+ builder.curve_to(
+ c1.x, c1.y,
+ c2.x, c2.y,
+ p.x, p.y,
+ );
+ }
+ usvg::PathSegment::ClosePath => {
+ builder.close();
+ }
+ }
+ }
+ let start = std::time::Instant::now();
+ let result = builder.rasterize_to_tri_list(0, 0, WIDTH as i32, HEIGHT as i32);
+ let end = std::time::Instant::now();
+ total_time += end - start;
+
+ println!("vertices {}", result.len());
+ total_vertex_count += result.len();
+ if result.len() == 0 {
+ continue;
+ }
+
+ for n in (0..result.len()).step_by(3) {
+ let vertices = {
+ [&result[n], &result[n+1], &result[n+2]]
+ };
+
+ let src = color;
+ let tri = Triangle { v: [
+ Vertex { x: vertices[0].x, y: vertices[0].y, coverage: vertices[0].coverage},
+ Vertex { x: vertices[1].x, y: vertices[1].y, coverage: vertices[1].coverage},
+ Vertex { x: vertices[2].x, y: vertices[2].y, coverage: vertices[2].coverage}
+ ]
+ };
+ rast_triangle(&mut image, WIDTH as usize, &tri, src);
+ }
+ }
+ }
+
+ println!("total vertex count {}, took {}ms", total_vertex_count, total_time.as_secs_f32()*1000.);
+ }
+
+
+ write_image(&image, "out.png");
+ use std::{hash::{Hash, Hasher}, collections::hash_map::DefaultHasher};
+ use crate::*;
+ fn calculate_hash<T: Hash>(t: &T) -> u64 {
+ let mut s = DefaultHasher::new();
+ t.hash(&mut s);
+ s.finish()
+ }
+
+ assert_eq!(calculate_hash(&image),
+ if cfg!(debug_assertions) { 0x5973c52a1c0232f3 } else { 0xf15821a5bebc5ecf});
+
+
+}
diff --git a/third_party/rust/wpf-gpu-raster/examples/obj-output.rs b/third_party/rust/wpf-gpu-raster/examples/obj-output.rs
new file mode 100644
index 0000000000..c7ec8d7686
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/examples/obj-output.rs
@@ -0,0 +1,26 @@
+// Output an .obj file of the generated mesh. Viewable at https://3dviewer.net/
+
+fn output_obj_file(data: &[OutputVertex]) {
+ for v in data {
+ let color = v.coverage;
+ println!("v {} {} {} {} {} {}", v.x, v.y, 0., color, color, color);
+ }
+
+ // output a standard triangle strip face list
+ for n in (1..data.len()-1).step_by(3) {
+ println!("f {} {} {}", n, n+1, n+2);
+ }
+}
+
+use wpf_gpu_raster::{PathBuilder, OutputVertex};
+fn main() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.0);
+ p.line_to(30., 10.);
+ p.line_to(50., 20.);
+ p.line_to(30., 30.);
+ p.line_to(10., 30.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ output_obj_file(&result)
+}
diff --git a/third_party/rust/wpf-gpu-raster/examples/simple.rs b/third_party/rust/wpf-gpu-raster/examples/simple.rs
new file mode 100644
index 0000000000..5b82cdd941
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/examples/simple.rs
@@ -0,0 +1,11 @@
+use wpf_gpu_raster::PathBuilder;
+fn main() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(10., 30.);
+ p.line_to(30., 30.);
+ p.line_to(30., 10.);
+ p.close();
+ let _result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ //dbg!(result);
+}
diff --git a/third_party/rust/wpf-gpu-raster/notes b/third_party/rust/wpf-gpu-raster/notes
new file mode 100644
index 0000000000..8550376eac
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/notes
@@ -0,0 +1,8 @@
+bezier flattening
+-----------------
+if we make sure we flatten beziers to integer y values we can avoid having to hit
+the slow complex coverage path
+
+We can probably do this by using a skia style flattener.
+Normally we compute a series of line segments using partial differencing.
+I think we can adjust the line towards an integer y value by having small partial differences that we can move by.
diff --git a/third_party/rust/wpf-gpu-raster/src/aacoverage.rs b/third_party/rust/wpf-gpu-raster/src/aacoverage.rs
new file mode 100644
index 0000000000..7b165a9da0
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/aacoverage.rs
@@ -0,0 +1,647 @@
+// 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.
+
+
+//------------------------------------------------------------------------------
+//
+
+use std::cell::Cell;
+
+use typed_arena_nomut::Arena;
+
+//
+// Description:
+// Coverage buffer implementation
+#[cfg(debug_assertions)]
+use crate::aarasterizer::AssertActiveList;
+use crate::aarasterizer::CEdge;
+use crate::nullable_ref::Ref;
+use crate::types::*;
+//struct CEdge;
+//struct CInactiveEdge;
+
+//-------------------------------------------------------------------------
+//
+// TrapezoidalAA only supports 8x8 mode, so the shifts/masks are all
+// constants. Also, since we must be symmetrical, x and y shifts are
+// merged into one shift unlike the implementation in aarasterizer.
+//
+//-------------------------------------------------------------------------
+
+pub const c_nShift: INT = 3;
+pub const c_nShiftSize: INT = 8;
+pub const c_nShiftSizeSquared: INT = c_nShiftSize * c_nShiftSize;
+pub const c_nHalfShiftSize: INT = 4;
+pub const c_nShiftMask: INT = 7;
+//pub const c_rShiftSize: f32 = 8.0;
+//pub const c_rHalfShiftSize: f32 = 4.0;
+pub const c_rInvShiftSize: f32 = 1.0/8.0;
+pub const c_antiAliasMode: MilAntiAliasMode = MilAntiAliasMode::EightByEight;
+
+//
+// Interval coverage descriptor for our antialiased filler
+//
+
+pub struct CCoverageInterval<'a>
+{
+ pub m_pNext: Cell<Ref<'a, CCoverageInterval<'a>>>, // m_pNext interval (look for sentinel, not NULL)
+ pub m_nPixelX: Cell<INT>, // Interval's left edge (m_pNext->X is the right edge)
+ pub m_nCoverage: Cell<INT>, // Pixel coverage for interval
+}
+
+impl<'a> Default for CCoverageInterval<'a> {
+ fn default() -> Self {
+ Self { m_pNext: Cell::new(unsafe { Ref::null() } ), m_nPixelX: Default::default(), m_nCoverage: Default::default() }
+ }
+}
+
+// Define our on-stack storage use. The 'free' versions are nicely tuned
+// to avoid allocations in most common scenarios, while at the same time
+// not chewing up toooo much stack space.
+//
+// We make the debug versions small so that we hit the 'grow' cases more
+// frequently, for better testing:
+
+#[cfg(debug_assertions)]
+ // Must be at least 6 now: 4 for the "minus4" logic in hwrasterizer.*, and then
+ // 1 each for the head and tail sentinels (since their allocation doesn't use Grow).
+ const INTERVAL_BUFFER_NUMBER: usize = 8;
+#[cfg(not(debug_assertions))]
+ const INTERVAL_BUFFER_NUMBER: usize = 32;
+
+
+//
+// Allocator structure for the antialiased fill interval data
+//
+
+struct CCoverageIntervalBuffer<'a>
+{
+ m_pNext: Cell<Option<& 'a CCoverageIntervalBuffer<'a>>>,
+ m_interval: [CCoverageInterval<'a>; INTERVAL_BUFFER_NUMBER],
+}
+
+impl<'a> Default for CCoverageIntervalBuffer<'a> {
+ fn default() -> Self {
+ Self { m_pNext: Cell::new(None), m_interval: Default::default() }
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// Class: CCoverageBuffer
+//
+// Description:
+// Coverage buffer implementation that maintains coverage information
+// for one scanline.
+//
+// This implementation will maintain a linked list of intervals consisting
+// of x value in pixel space and a coverage value that applies for all pixels
+// between pInterval->X and pInterval->Next->X.
+//
+// For example, if we add the following interval (assuming 8x8 anti-aliasing)
+// to the coverage buffer:
+// _____ _____ _____ _____
+// | | | | |
+// | ------------------- |
+// |_____|_____|_____|_____|
+// (0,0) (1,0) (2,0) (3,0) (4,0)
+//
+// Then we will get the following coverage buffer:
+//
+// m_nPixelX: INT_MIN | 0 | 1 | 3 | 4 | INT_MAX
+// m_nCoverage: 0 | 4 | 8 | 4 | 0 | 0xdeadbeef
+// m_pNext: -------->|---->|---->|---->|---->| NULL
+//
+//------------------------------------------------------------------------------
+pub struct CCoverageBuffer<'a>
+{
+ /*
+public:
+ //
+ // Init/Destroy methods
+ //
+
+ VOID Initialize();
+ VOID Destroy();
+
+ //
+ // Setup the buffer so that it can accept another scanline
+ //
+
+ VOID Reset();
+
+ //
+ // Add a subpixel interval to the coverage buffer
+ //
+
+ HRESULT FillEdgesAlternating(
+ __in_ecount(1) const CEdge *pEdgeActiveList,
+ INT nSubpixelYCurrent
+ );
+
+ HRESULT FillEdgesWinding(
+ __in_ecount(1) const CEdge *pEdgeActiveList,
+ INT nSubpixelYCurrent
+ );
+
+ HRESULT AddInterval(INT nSubpixelXLeft, INT nSubpixelXRight);
+
+private:
+
+ HRESULT Grow(
+ __deref_out_ecount(1) CCoverageInterval **ppIntervalNew,
+ __deref_out_ecount(1) CCoverageInterval **ppIntervalEndMinus4
+ );
+
+public:*/
+ pub m_pIntervalStart: Cell<Ref<'a, CCoverageInterval<'a>>>, // Points to list head entry
+
+//private:
+ m_pIntervalNew: Cell<Ref<'a, CCoverageInterval<'a>>>,
+ interval_new_index: Cell<usize>,
+
+ // The Minus4 in the below variable refers to the position at which
+ // we need to Grow the buffer. The buffer is grown once before an
+ // AddInterval, so the Grow has to ensure that there are enough
+ // intervals for the AddInterval worst case which is the following:
+ //
+ // 1 2 3 4
+ // *_____*_____ _____*_____*
+ // | | | | |
+ // | ---|-----------|--- |
+ // |_____|_____|_____|_____|
+ //
+ // Note that the *'s above mark potentional insert points in the list,
+ // so we need to ensure that at least 4 intervals can be allocated.
+ //
+
+ m_pIntervalEndMinus4: Cell<Ref<'a, CCoverageInterval<'a>>>,
+
+ // Cache the next-to-last added interval to accelerate insertion.
+ m_pIntervalLast: Cell<Ref<'a, CCoverageInterval<'a>>>,
+
+ m_pIntervalBufferBuiltin: CCoverageIntervalBuffer<'a>,
+ m_pIntervalBufferCurrent: Cell<Ref<'a, CCoverageIntervalBuffer<'a>>>,
+
+ arena: Arena<CCoverageIntervalBuffer<'a>>
+
+ // Disable instrumentation checks within all methods of this class
+ //SET_MILINSTRUMENTATION_FLAGS(MILINSTRUMENTATIONFLAGS_DONOTHING);
+}
+
+impl<'a> Default for CCoverageBuffer<'a> {
+ fn default() -> Self {
+ Self {
+ m_pIntervalStart: Cell::new(unsafe { Ref::null() }),
+ m_pIntervalNew: Cell::new(unsafe { Ref::null() }),
+ m_pIntervalEndMinus4: Cell::new(unsafe { Ref::null() }),
+ m_pIntervalLast: Cell::new(unsafe { Ref::null() }),
+ m_pIntervalBufferBuiltin: Default::default(),
+ m_pIntervalBufferCurrent: unsafe { Cell::new(Ref::null()) },
+ arena: Arena::new(),
+ interval_new_index: Cell::new(0),
+ }
+ }
+}
+
+
+//
+// Inlines
+//
+impl<'a> CCoverageBuffer<'a> {
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::AddInterval
+//
+// Synopsis: Add a subpixel resolution interval to the coverage buffer
+//
+//-------------------------------------------------------------------------
+pub fn AddInterval(&'a self, nSubpixelXLeft: INT, nSubpixelXRight: INT) -> HRESULT
+{
+ let hr: HRESULT = S_OK;
+ let mut nPixelXNext: INT;
+ let nPixelXLeft: INT;
+ let nPixelXRight: INT;
+ let nCoverageLeft: INT; // coverage from right edge of pixel for interval start
+ let nCoverageRight: INT; // coverage from left edge of pixel for interval end
+
+ let mut pInterval = self.m_pIntervalStart.get();
+ let mut pIntervalNew = self.m_pIntervalNew.get();
+ let mut interval_new_index = self.interval_new_index.get();
+ let mut pIntervalEndMinus4 = self.m_pIntervalEndMinus4.get();
+
+ // Make sure we have enough room to add two intervals if
+ // necessary:
+
+ if (pIntervalNew >= pIntervalEndMinus4)
+ {
+ IFC!(self.Grow(&mut pIntervalNew, &mut pIntervalEndMinus4, &mut interval_new_index));
+ }
+
+ // Convert interval to pixel space so that we can insert it
+ // into the coverage buffer
+
+ debug_assert!(nSubpixelXLeft < nSubpixelXRight);
+ nPixelXLeft = nSubpixelXLeft >> c_nShift;
+ nPixelXRight = nSubpixelXRight >> c_nShift;
+
+ // Try to resume searching from the last searched interval.
+ if self.m_pIntervalLast.get().m_nPixelX.get() < nPixelXLeft {
+ pInterval = self.m_pIntervalLast.get();
+ }
+
+ // Skip any intervals less than 'nPixelLeft':
+
+ loop {
+ let nextInterval = pInterval.m_pNext.get();
+ nPixelXNext = nextInterval.m_nPixelX.get();
+ if !(nPixelXNext < nPixelXLeft) { break }
+
+ pInterval = nextInterval;
+ }
+
+ // Remember the found interval.
+ self.m_pIntervalLast.set(pInterval);
+
+ // Insert a new interval if necessary:
+
+ if (nPixelXNext != nPixelXLeft)
+ {
+ pIntervalNew.m_nPixelX.set(nPixelXLeft);
+ pIntervalNew.m_nCoverage.set(pInterval.m_nCoverage.get());
+
+ pIntervalNew.m_pNext.set(pInterval.m_pNext.get());
+ pInterval.m_pNext.set(pIntervalNew);
+
+ pInterval = pIntervalNew;
+
+ interval_new_index += 1;
+ pIntervalNew = Ref::new(&Ref::get_ref(self.m_pIntervalBufferCurrent.get()).m_interval[interval_new_index])
+
+ }
+ else
+ {
+ pInterval = (*pInterval).m_pNext.get();
+ }
+
+ //
+ // Compute coverage for left segment as shown by the *'s below
+ //
+ // |_____|_____|_____|_
+ // | | | |
+ // | ***---------- |
+ // |_____|_____|_____|
+ //
+
+ nCoverageLeft = c_nShiftSize - (nSubpixelXLeft & c_nShiftMask);
+
+ // If nCoverageLeft == 0, then the value of nPixelXLeft is wrong
+ // and should have been equal to nPixelXLeft+1.
+ debug_assert!(nCoverageLeft > 0);
+
+ // If we have partial coverage, then ensure that we have a position
+ // for the end of the pixel
+
+ if ((nCoverageLeft < c_nShiftSize || (nPixelXLeft == nPixelXRight))
+ && nPixelXLeft + 1 != pInterval.m_pNext.get().m_nPixelX.get())
+ {
+ pIntervalNew.m_nPixelX.set(nPixelXLeft + 1);
+ pIntervalNew.m_nCoverage.set(pInterval.m_nCoverage.get());
+
+ pIntervalNew.m_pNext.set(pInterval.m_pNext.get());
+ pInterval.m_pNext.set(pIntervalNew);
+
+ interval_new_index += 1;
+ pIntervalNew = Ref::new(&Ref::get_ref(self.m_pIntervalBufferCurrent.get()).m_interval[interval_new_index])
+ }
+
+ //
+ // If the interval only includes one pixel, then the coverage is
+ // nSubpixelXRight - nSubpixelXLeft
+ //
+
+ if (nPixelXLeft == nPixelXRight)
+ {
+ pInterval.m_nCoverage.set(pInterval.m_nCoverage.get() + nSubpixelXRight - nSubpixelXLeft);
+ debug_assert!(pInterval.m_nCoverage.get() <= c_nShiftSize*c_nShiftSize);
+ //goto Cleanup;
+
+ //Cleanup:
+ // Update the coverage buffer new interval
+ self.interval_new_index.set(interval_new_index);
+ self.m_pIntervalNew.set(pIntervalNew);
+ return hr;
+ }
+
+ // Update coverage of current interval
+ pInterval.m_nCoverage.set(pInterval.m_nCoverage.get() + nCoverageLeft);
+ debug_assert!(pInterval.m_nCoverage.get() <= c_nShiftSize*c_nShiftSize);
+
+ // Increase the coverage for any intervals between 'nPixelXLeft'
+ // and 'nPixelXRight':
+
+ loop {
+ let nextInterval = pInterval.m_pNext.get();
+ (nPixelXNext = nextInterval.m_nPixelX.get());
+
+ if !(nPixelXNext < nPixelXRight) {
+ break;
+ }
+ pInterval = nextInterval;
+ pInterval.m_nCoverage.set(pInterval.m_nCoverage.get() + c_nShiftSize);
+ debug_assert!(pInterval.m_nCoverage.get() <= c_nShiftSize*c_nShiftSize);
+ }
+
+ // Remember the found interval.
+ self.m_pIntervalLast.set(pInterval);
+
+ // Insert another new interval if necessary:
+
+ if (nPixelXNext != nPixelXRight)
+ {
+ pIntervalNew.m_nPixelX.set(nPixelXRight);
+ pIntervalNew.m_nCoverage.set(pInterval.m_nCoverage.get() - c_nShiftSize);
+
+ pIntervalNew.m_pNext.set(pInterval.m_pNext.get());
+ pInterval.m_pNext.set(pIntervalNew);
+
+ pInterval = pIntervalNew;
+
+ interval_new_index += 1;
+ pIntervalNew = Ref::new(&Ref::get_ref(self.m_pIntervalBufferCurrent.get()).m_interval[interval_new_index])
+ }
+ else
+ {
+ pInterval = pInterval.m_pNext.get();
+ }
+
+ //
+ // Compute coverage for right segment as shown by the *'s below
+ //
+ // |_____|_____|_____|_
+ // | | | |
+ // | ---------**** |
+ // |_____|_____|_____|
+ //
+
+ nCoverageRight = nSubpixelXRight & c_nShiftMask;
+ if (nCoverageRight > 0)
+ {
+ if (nPixelXRight + 1 != (*(*pInterval).m_pNext.get()).m_nPixelX.get())
+ {
+ pIntervalNew.m_nPixelX.set(nPixelXRight + 1);
+ pIntervalNew.m_nCoverage.set(pInterval.m_nCoverage.get());
+
+ pIntervalNew.m_pNext.set(pInterval.m_pNext.get());
+ pInterval.m_pNext.set(pIntervalNew);
+
+ interval_new_index += 1;
+ pIntervalNew = Ref::new(&Ref::get_ref(self.m_pIntervalBufferCurrent.get()).m_interval[interval_new_index])
+ }
+
+ pInterval.m_nCoverage.set((*pInterval).m_nCoverage.get() + nCoverageRight);
+ debug_assert!(pInterval.m_nCoverage.get() <= c_nShiftSize*c_nShiftSize);
+ }
+
+//Cleanup:
+ // Update the coverage buffer new interval
+ self.interval_new_index.set(interval_new_index);
+ self.m_pIntervalNew.set(pIntervalNew);
+
+ return hr;
+}
+
+
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::FillEdgesAlternating
+//
+// Synopsis:
+// Given the active edge list for the current scan, do an alternate-mode
+// antialiased fill.
+//
+//-------------------------------------------------------------------------
+pub fn FillEdgesAlternating(&'a self,
+ pEdgeActiveList: Ref<CEdge>,
+ nSubpixelYCurrent: INT
+ ) -> HRESULT
+{
+
+ let hr: HRESULT = S_OK;
+ let mut pEdgeStart: Ref<CEdge> = (*pEdgeActiveList).Next.get();
+ let mut pEdgeEnd: Ref<CEdge>;
+ let mut nSubpixelXLeft: INT;
+ let mut nSubpixelXRight: INT;
+
+ ASSERTACTIVELIST!(pEdgeActiveList, nSubpixelYCurrent);
+
+ while (pEdgeStart.X.get() != INT::MAX)
+ {
+ pEdgeEnd = pEdgeStart.Next.get();
+
+ // We skip empty pairs:
+ (nSubpixelXLeft = pEdgeStart.X.get());
+ if (nSubpixelXLeft != pEdgeEnd.X.get())
+ {
+ // We now know we have a non-empty interval. Skip any
+ // empty interior pairs:
+
+ while ({(nSubpixelXRight = pEdgeEnd.X.get()); pEdgeEnd.X == pEdgeEnd.Next.get().X})
+ {
+ pEdgeEnd = pEdgeEnd.Next.get().Next.get();
+ }
+
+ debug_assert!((nSubpixelXLeft < nSubpixelXRight) && (nSubpixelXRight < INT::MAX));
+
+ IFC!(self.AddInterval(nSubpixelXLeft, nSubpixelXRight));
+ }
+
+ // Prepare for the next iteration:
+ pEdgeStart = pEdgeEnd.Next.get();
+ }
+
+//Cleanup:
+ return hr
+
+}
+
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::FillEdgesWinding
+//
+// Synopsis:
+// Given the active edge list for the current scan, do an alternate-mode
+// antialiased fill.
+//
+//-------------------------------------------------------------------------
+pub fn FillEdgesWinding(&'a self,
+ pEdgeActiveList: Ref<CEdge>,
+ nSubpixelYCurrent: INT
+ ) -> HRESULT
+{
+
+ let hr: HRESULT = S_OK;
+ let mut pEdgeStart: Ref<CEdge> = pEdgeActiveList.Next.get();
+ let mut pEdgeEnd: Ref<CEdge>;
+ let mut nSubpixelXLeft: INT;
+ let mut nSubpixelXRight: INT;
+ let mut nWindingValue: INT;
+
+ ASSERTACTIVELIST!(pEdgeActiveList, nSubpixelYCurrent);
+
+ while (pEdgeStart.X.get() != INT::MAX)
+ {
+ pEdgeEnd = pEdgeStart.Next.get();
+
+ nWindingValue = pEdgeStart.WindingDirection;
+ while ({nWindingValue += pEdgeEnd.WindingDirection; nWindingValue != 0})
+ {
+ pEdgeEnd = pEdgeEnd.Next.get();
+ }
+
+ debug_assert!(pEdgeEnd.X.get() != INT::MAX);
+
+ // We skip empty pairs:
+
+ if ({nSubpixelXLeft = pEdgeStart.X.get(); nSubpixelXLeft != pEdgeEnd.X.get()})
+ {
+ // We now know we have a non-empty interval. Skip any
+ // empty interior pairs:
+
+ while ({nSubpixelXRight = pEdgeEnd.X.get(); nSubpixelXRight == pEdgeEnd.Next.get().X.get()})
+ {
+ pEdgeStart = pEdgeEnd.Next.get();
+ pEdgeEnd = pEdgeStart.Next.get();
+
+ nWindingValue = pEdgeStart.WindingDirection;
+ while ({nWindingValue += pEdgeEnd.WindingDirection; nWindingValue != 0})
+ {
+ pEdgeEnd = pEdgeEnd.Next.get();
+ }
+ }
+
+ debug_assert!((nSubpixelXLeft < nSubpixelXRight) && (nSubpixelXRight < INT::MAX));
+
+ IFC!(self.AddInterval(nSubpixelXLeft, nSubpixelXRight));
+ }
+
+ // Prepare for the next iteration:
+
+ pEdgeStart = pEdgeEnd.Next.get();
+ }
+
+//Cleanup:
+ return hr;//RRETURN(hr);
+}
+
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::Initialize
+//
+// Synopsis: Set the coverage buffer to a valid initial state
+//
+//-------------------------------------------------------------------------
+pub fn Initialize(&'a self)
+{
+ self.m_pIntervalBufferBuiltin.m_interval[0].m_nPixelX.set(INT::MIN);
+ self.m_pIntervalBufferBuiltin.m_interval[0].m_nCoverage.set(0);
+ self.m_pIntervalBufferBuiltin.m_interval[0].m_pNext.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[1]));
+
+ self.m_pIntervalBufferBuiltin.m_interval[1].m_nPixelX.set(INT::MAX);
+ self.m_pIntervalBufferBuiltin.m_interval[1].m_nCoverage.set(0xdeadbeef);
+ self.m_pIntervalBufferBuiltin.m_interval[1].m_pNext.set(unsafe { Ref::null() });
+
+ self.m_pIntervalBufferBuiltin.m_pNext.set(None);
+ self.m_pIntervalBufferCurrent.set(Ref::new(&self.m_pIntervalBufferBuiltin));
+
+ self.m_pIntervalStart.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[0]));
+ self.m_pIntervalNew.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[2]));
+ self.interval_new_index.set(2);
+ self.m_pIntervalEndMinus4.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[INTERVAL_BUFFER_NUMBER - 4]));
+ self.m_pIntervalLast.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[1]));
+}
+
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::Destroy
+//
+// Synopsis: Free all allocated buffers
+//
+//-------------------------------------------------------------------------
+pub fn Destroy(&mut self)
+{
+ // Free the linked-list of allocations (skipping 'm_pIntervalBufferBuiltin',
+ // which is built into the class):
+
+
+}
+
+
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::Reset
+//
+// Synopsis: Reset the coverage buffer
+//
+//-------------------------------------------------------------------------
+pub fn Reset(&'a self)
+{
+ // Reset our coverage structure. Point the head back to the tail,
+ // and reset where the next new entry will be placed:
+
+ self.m_pIntervalBufferBuiltin.m_interval[0].m_pNext.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[1]));
+
+ self.m_pIntervalBufferCurrent.set(Ref::new(&self.m_pIntervalBufferBuiltin));
+ self.m_pIntervalNew.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[2]));
+ self.interval_new_index.set(2);
+ self.m_pIntervalEndMinus4.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[INTERVAL_BUFFER_NUMBER - 4]));
+ self.m_pIntervalLast.set(Ref::new(&self.m_pIntervalBufferBuiltin.m_interval[1]));
+}
+
+//-------------------------------------------------------------------------
+//
+// Function: CCoverageBuffer::Grow
+//
+// Synopsis:
+// Grow our interval buffer.
+//
+//-------------------------------------------------------------------------
+fn Grow(&'a self,
+ ppIntervalNew: &mut Ref<'a, CCoverageInterval<'a>>,
+ ppIntervalEndMinus4: &mut Ref<'a, CCoverageInterval<'a>>,
+ interval_new_index: &mut usize
+ ) -> HRESULT
+{
+ let hr: HRESULT = S_OK;
+ let pIntervalBufferNew = (*self.m_pIntervalBufferCurrent.get()).m_pNext.get();
+
+ let pIntervalBufferNew = pIntervalBufferNew.unwrap_or_else(||
+ {
+ let pIntervalBufferNew = self.arena.alloc(Default::default());
+
+ (*pIntervalBufferNew).m_pNext.set(None);
+ (*self.m_pIntervalBufferCurrent.get()).m_pNext.set(Some(pIntervalBufferNew));
+ pIntervalBufferNew
+ });
+
+ self.m_pIntervalBufferCurrent.set(Ref::new(pIntervalBufferNew));
+
+ self.m_pIntervalNew.set(Ref::new(&(*pIntervalBufferNew).m_interval[2]));
+ self.interval_new_index.set(2);
+ self.m_pIntervalEndMinus4.set(Ref::new(&(*pIntervalBufferNew).m_interval[INTERVAL_BUFFER_NUMBER - 4]));
+
+ *ppIntervalNew = self.m_pIntervalNew.get();
+ *ppIntervalEndMinus4 = self.m_pIntervalEndMinus4.get();
+ *interval_new_index = 2;
+
+ return hr;
+}
+
+}
+/*
+impl<'a> Drop for CCoverageBuffer<'a> {
+ fn drop(&mut self) {
+ self.Destroy();
+ }
+}*/
diff --git a/third_party/rust/wpf-gpu-raster/src/aarasterizer.rs b/third_party/rust/wpf-gpu-raster/src/aarasterizer.rs
new file mode 100644
index 0000000000..ad9617a42d
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/aarasterizer.rs
@@ -0,0 +1,1768 @@
+// 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 std::cell::Cell;
+
+use crate::aacoverage::c_nShift;
+use crate::bezier::CMILBezier;
+use crate::helpers::Int32x32To64;
+use crate::matrix::CMILMatrix;
+use crate::nullable_ref::Ref;
+use crate::real::CFloatFPU;
+//use crate::types::PathPointType::*;
+use crate::types::*;
+use typed_arena_nomut::Arena;
+
+const S_OK: HRESULT = 0;
+
+#[cfg(debug_assertions)]
+macro_rules! EDGE_STORE_STACK_NUMBER {
+ () => {
+ 10
+ };
+}
+#[cfg(debug_assertions)]
+macro_rules! EDGE_STORE_ALLOCATION_NUMBER { () => { 11 }; }
+#[cfg(debug_assertions)]
+macro_rules! INACTIVE_LIST_NUMBER { () => { 12 }; }
+#[cfg(debug_assertions)]
+macro_rules! ENUMERATE_BUFFER_NUMBER { () => { 15 }; }
+
+#[cfg(not(debug_assertions))]
+macro_rules! EDGE_STORE_STACK_NUMBER { () => { (1600 / std::mem::size_of::<CEdge>()) }; }
+#[cfg(not(debug_assertions))]
+macro_rules! EDGE_STORE_ALLOCATION_NUMBER { () => { (4032 / std::mem::size_of::<CEdge>()) as u32 }; }
+#[cfg(not(debug_assertions))]
+macro_rules! INACTIVE_LIST_NUMBER { () => { EDGE_STORE_STACK_NUMBER!() }; }
+#[cfg(not(debug_assertions))]
+macro_rules! ENUMERATE_BUFFER_NUMBER { () => { 32 }; }
+
+macro_rules! ASSERTACTIVELIST {
+ ($list: expr, $y: expr) => {
+ // make sure we use y even in non debug builds
+ _ = $y;
+ #[cfg(debug_assertions)]
+ AssertActiveList($list, $y);
+ };
+}
+pub struct CEdge<'a> {
+ pub Next: Cell<Ref<'a, CEdge<'a>>>, // Next active edge (don't check for NULL,
+ // look for tail sentinel instead)
+ pub X: Cell<INT>, // Current X location
+ pub Dx: INT, // X increment
+ pub Error: Cell<INT>, // Current DDA error
+ pub ErrorUp: INT, // Error increment
+ pub ErrorDown: INT, // Error decrement when the error rolls over
+ pub StartY: INT, // Y-row start
+ pub EndY: INT, // Y-row end
+ pub WindingDirection: INT, // -1 or 1
+}
+
+impl<'a> std::default::Default for CEdge<'a> {
+ fn default() -> Self {
+ Self {
+ Next: Cell::new(unsafe { Ref::null() }),
+ X: Default::default(),
+ Dx: Default::default(),
+ Error: Default::default(),
+ ErrorUp: Default::default(),
+ ErrorDown: Default::default(),
+ StartY: Default::default(),
+ EndY: Default::default(),
+ WindingDirection: Default::default(),
+ }
+ }
+}
+
+// We the inactive-array separate from the edge allocations so that
+// we can more easily do in-place sorts on it:
+#[derive(Clone)]
+pub struct CInactiveEdge<'a> {
+ Edge: Ref<'a, CEdge<'a>>, // Associated edge
+ Yx: LONGLONG, // Sorting key, StartY and X packed into an lword
+}
+
+impl<'a> Default for CInactiveEdge<'a> {
+ fn default() -> Self {
+ Self {
+ Edge: unsafe { Ref::null() },
+ Yx: Default::default(),
+ }
+ }
+}
+macro_rules! ASSERTACTIVELISTORDER {
+ ($list: expr) => {
+ #[cfg(debug_assertions)]
+ AssertActiveListOrder($list)
+ };
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Advance DDA and update active edge list
+*
+* Created:
+*
+* 06/20/2003 ashrafm
+*
+\**************************************************************************/
+pub fn AdvanceDDAAndUpdateActiveEdgeList(nSubpixelYCurrent: INT, pEdgeActiveList: Ref<CEdge>) {
+ let mut outOfOrder = false;
+ let mut pEdgePrevious: Ref<CEdge> = pEdgeActiveList;
+ let mut pEdgeCurrent: Ref<CEdge> = pEdgeActiveList.Next.get();
+ let mut prevX = pEdgePrevious.X.get();
+
+ // Advance DDA and update edge list
+
+ loop {
+ if (pEdgeCurrent.EndY <= nSubpixelYCurrent) {
+ // If we've hit the sentinel, our work here is done:
+
+ if (pEdgeCurrent.EndY == INT::MIN) {
+ break; // ============>
+ }
+ // This edge is stale, remove it from the list:
+
+ pEdgeCurrent = pEdgeCurrent.Next.get();
+ pEdgePrevious.Next.set(pEdgeCurrent);
+ continue; // ============>
+ }
+
+ // Advance the DDA:
+
+ let mut x = pEdgeCurrent.X.get() + pEdgeCurrent.Dx;
+ let mut error = pEdgeCurrent.Error.get() + pEdgeCurrent.ErrorUp;
+ if (error >= 0) {
+ error -= pEdgeCurrent.ErrorDown;
+ x += 1;
+ }
+ pEdgeCurrent.X.set(x);
+ pEdgeCurrent.Error.set(error);
+
+ // Is this entry out-of-order with respect to the previous one?
+ outOfOrder |= (prevX > x);
+
+ // Advance:
+
+ pEdgePrevious = pEdgeCurrent;
+ pEdgeCurrent = pEdgeCurrent.Next.get();
+ prevX = x;
+ }
+
+ // It turns out that having any out-of-order edges at this point
+ // is extremely rare in practice, so only call the bubble-sort
+ // if it's truly needed.
+ //
+ // NOTE: If you're looking at this code trying to fix a bug where
+ // the edges are out of order when the filler is called, do
+ // NOT simply change the code to always do the bubble-sort!
+ // Instead, figure out what caused our 'outOfOrder' logic
+ // above to get messed up.
+
+ if (outOfOrder) {
+ SortActiveEdges(pEdgeActiveList);
+ }
+ ASSERTACTIVELISTORDER!(pEdgeActiveList);
+
+}
+
+//+----------------------------------------------------------------------------
+//
+
+//
+// Description: Code for rasterizing the fill of a path.
+//
+// >>>> Note that some of this code is duplicated in hw\hwrasterizer.cpp,
+// >>>> so changes to this file may need to propagate.
+//
+// pursue reduced code duplication
+//
+
+// This option may potentially increase performance for many
+// paths that have edges adjacent at their top point and cover
+// more than one span. The code has been tested, but performance
+// has not been thoroughly investigated.
+const SORT_EDGES_INCLUDING_SLOPE: bool = false;
+
+/////////////////////////////////////////////////////////////////////////
+// The x86 C compiler insists on making a divide and modulus operation
+// into two DIVs, when it can in fact be done in one. So we use this
+// macro.
+//
+// Note: QUOTIENT_REMAINDER implicitly takes unsigned arguments.
+//
+// QUOTIENT_REMAINDER_64_32 takes a 64-bit numerator and produces 32-bit
+// results.
+
+macro_rules! QUOTIENT_REMAINDER {
+ ($ulNumerator: ident, $ulDenominator: ident, $ulQuotient: ident, $ulRemainder: ident) => {
+ $ulQuotient = (($ulNumerator as ULONG) / ($ulDenominator as ULONG)) as _;
+ $ulRemainder = (($ulNumerator as ULONG) % ($ulDenominator as ULONG)) as _;
+ };
+}
+
+macro_rules! QUOTIENT_REMAINDER_64_32 {
+ ($ulNumerator: ident, $ulDenominator: ident, $ulQuotient: ident, $ulRemainder: ident) => {
+ $ulQuotient = (($ulNumerator as ULONGLONG) / (($ulDenominator as ULONG) as ULONGLONG)) as _;
+ $ulRemainder =
+ (($ulNumerator as ULONGLONG) % (($ulDenominator as ULONG) as ULONGLONG)) as _;
+ };
+}
+
+// SWAP macro:
+macro_rules! SWAP {
+ ($temp: ident, $a: expr, $b: expr) => {
+ $temp = $a;
+ $a = $b;
+ $b = $temp;
+ };
+}
+
+struct CEdgeAllocation {
+ Next: *mut CEdgeAllocation, // Next allocation batch (may be NULL)
+ /*__field_range(<=, EDGE_STORE_ALLOCATION_NUMBER)*/ Count: UINT,
+ EdgeArray: [CEdge<'static>; EDGE_STORE_STACK_NUMBER!()],
+}
+
+impl Default for CEdgeAllocation {
+ fn default() -> Self {
+ Self { Next: NULL(), Count: Default::default(), EdgeArray: [(); EDGE_STORE_STACK_NUMBER!()].map(|_| Default::default()) }
+ }
+}
+/*
+pub struct CEdgeStore {
+ /* __field_range(<=, UINT_MAX - 2) */ TotalCount: UINT, // Total edge count in store
+ /* __field_range(<=, CurrentBuffer->Count) */
+ CurrentRemaining: UINT, // How much room remains in current buffer
+ CurrentBuffer: *mut CEdgeAllocation, // Current buffer
+ CurrentEdge: *mut CEdge<'static>, // Current edge in current buffer
+ Enumerator: *mut CEdgeAllocation, // For enumerating all the edges
+ EdgeHead: CEdgeAllocation, // Our built-in allocation
+}
+
+impl Default for CEdgeStore {
+ fn default() -> Self {
+ Self { TotalCount: Default::default(), CurrentRemaining: Default::default(), CurrentBuffer: NULL(), CurrentEdge: NULL(), Enumerator: NULL(), EdgeHead: Default::default() }
+ }
+}
+
+impl CEdgeStore {
+ pub fn init(&mut self) {
+ self.TotalCount = 0;
+ self.CurrentBuffer = NULL();
+ self.CurrentEdge = NULL();
+ self.Enumerator = NULL();
+ self.CurrentRemaining = EDGE_STORE_STACK_NUMBER!() as u32;
+
+ self.EdgeHead = CEdgeAllocation {
+ Count: EDGE_STORE_STACK_NUMBER!() as u32,
+ // hack to work around limited Default implementation for arrays
+ EdgeArray: [(); EDGE_STORE_STACK_NUMBER!()].map(|_| Default::default()),
+ Next: NULL(),
+ };
+ self.CurrentBuffer = &mut self.EdgeHead;
+ self.CurrentEdge = &mut self.EdgeHead.EdgeArray[0];
+ }
+}
+
+impl Drop for CEdgeStore {
+ fn drop(&mut self) {
+ // Free our allocation list, skipping the head, which is not
+ // dynamically allocated:
+
+ let mut allocation: *mut CEdgeAllocation = self.EdgeHead.Next;
+ while (allocation != NULL()) {
+ let next = unsafe { (*allocation).Next };
+ drop(unsafe { Box::from_raw(allocation) });
+ allocation = next;
+ }
+ }
+}
+
+impl CEdgeStore {
+ pub fn StartEnumeration(&mut self) -> UINT {
+ unsafe {
+ self.Enumerator = &mut self.EdgeHead;
+
+ // Update the count and make sure nothing more gets added (in
+ // part because this Count would have to be re-computed):
+
+ (*self.CurrentBuffer).Count -= self.CurrentRemaining;
+
+ // This will never overflow because NextAddBuffer always ensures that TotalCount has
+ // space remaining to describe the capacity of all new buffers added to the edge list.
+ self.TotalCount += (*self.CurrentBuffer).Count;
+
+ // Prevent this from being called again, because bad things would
+ // happen:
+
+ self.CurrentBuffer = NULL();
+
+ return self.TotalCount;
+ }
+ }
+
+ fn Enumerate(
+ &mut self,
+ /*__deref_out_ecount(*ppEndEdge - *ppStartEdge)*/ ppStartEdge: &mut *mut CEdge,
+ /* __deref_out_ecount(0) */ ppEndEdge: &mut *mut CEdge,
+ ) -> bool {
+ /*
+ unsafe {
+ let enumerator: *mut CEdgeAllocation = self.Enumerator;
+
+ // Might return startEdge == endEdge:
+
+ *ppStartEdge = &mut (*enumerator).EdgeArray[0];
+ *ppEndEdge = (*ppStartEdge).offset((*enumerator).Count as isize);
+
+ self.Enumerator = (*enumerator).Next;
+ return (self.Enumerator != NULL());
+ }*/
+ return true;
+ }
+
+ fn StartAddBuffer(
+ &self,
+ /*__deref_out_ecount(*puRemaining)*/ ppCurrentEdge: &mut *mut CEdge,
+ /* __deref_out_range(==, (this->CurrentRemaining)) */ puRemaining: &mut UINT,
+ ) {
+ panic!()
+ // *ppCurrentEdge = self.CurrentEdge;
+ // *puRemaining = self.CurrentRemaining;
+ }
+
+ fn EndAddBuffer(
+ &mut self,
+ /*__in_ecount(remaining) */ pCurrentEdge: *mut CEdge,
+ /* __range(0, (this->CurrentBuffer->Count)) */ remaining: UINT,
+ ) {
+ panic!();
+ //self.CurrentEdge = pCurrentEdge;
+ //self.CurrentRemaining = remaining;
+ }
+
+ // Disable instrumentation checks within all methods of this class
+ //SET_MILINSTRUMENTATION_FLAGS(MILINSTRUMENTATIONFLAGS_DONOTHING);
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* The edge initializer is out of room in its current 'store' buffer;
+* get it a new one.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+impl CEdgeStore {
+ fn NextAddBuffer(
+ &mut self,
+ /*__deref_out_ecount(*puRemaining)*/ ppCurrentEdge: &mut *mut CEdge,
+ puRemaining: &mut UINT,
+ ) -> HRESULT {
+ panic!()
+ /*
+ unsafe {
+ let hr = S_OK;
+
+ let mut cNewTotalCount: u32 = 0;
+
+ // The caller has completely filled up this chunk:
+
+ assert!(*puRemaining == 0);
+
+ // Check to make sure that "TotalCount" will be able to represent the current capacity
+ cNewTotalCount = self.TotalCount + (*self.CurrentBuffer).Count;
+
+ if (cNewTotalCount < self.TotalCount) {
+ return WINCODEC_ERR_VALUEOVERFLOW;
+ }
+
+ // And that it can represent the new capacity as well, with at least 2 to spare.
+ // This "magic" 2 comes from the fact that the usage pattern of this class has callers
+ // needing to allocate space for TotalCount + 2 edges.
+ if (cNewTotalCount + ((EDGE_STORE_ALLOCATION_NUMBER!() + 2) as UINT) < cNewTotalCount) {
+ return WINCODEC_ERR_VALUEOVERFLOW;
+ }
+
+ // We have to grow our data structure by adding a new buffer
+ // and adding it to the list:
+
+ let newBuffer: *mut CEdgeAllocation = Box::into_raw(Box::<CEdgeAllocation>::new(Default::default()));/*static_cast<CEdgeAllocation*>
+ (GpMalloc(Mt(MAARasterizerEdge),
+ sizeof(CEdgeAllocation) +
+ sizeof(CEdge) * (EDGE_STORE_ALLOCATION_NUMBER
+ - EDGE_STORE_STACK_NUMBER)));*/
+ IFCOOM!(newBuffer);
+
+ (*newBuffer).Next = NULL();
+ (*newBuffer).Count = EDGE_STORE_STACK_NUMBER!() as u32;//EDGE_STORE_ALLOCATION_NUMBER!() as u32;
+
+ self.TotalCount = cNewTotalCount;
+
+ (*self.CurrentBuffer).Next = newBuffer;
+ self.CurrentBuffer = newBuffer;
+
+ self.CurrentEdge = &mut (*newBuffer).EdgeArray[0];
+ *ppCurrentEdge = panic!();//self.CurrentEdge;
+ self.CurrentRemaining = EDGE_STORE_STACK_NUMBER!() as u32;//EDGE_STORE_ALLOCATION_NUMBER!();
+ *puRemaining = EDGE_STORE_STACK_NUMBER!() as u32; //EDGE_STORE_ALLOCATION_NUMBER!();
+
+ return hr;
+ }*/
+ }
+}
+*/
+/**************************************************************************\
+*
+* Function Description:
+*
+* Some debug code for verifying the state of the active edge list.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+pub fn AssertActiveList(mut list: Ref<CEdge>, yCurrent: INT) -> bool {
+
+ let mut b = true;
+ let mut activeCount = 0;
+
+ assert!((*list).X.get() == INT::MIN);
+ b &= ((*list).X.get() == INT::MIN);
+
+ // Skip the head sentinel:
+
+ list = (*list).Next.get();
+
+ while ((*list).X.get() != INT::MAX) {
+ assert!((*list).X.get() != INT::MIN);
+ b &= ((*list).X.get() != INT::MIN);
+
+ assert!((*list).X <= (*(*list).Next.get()).X);
+ b &= ((*list).X <= (*(*list).Next.get()).X);
+
+ assert!(((*list).StartY <= yCurrent) && (yCurrent < (*list).EndY));
+ b &= (((*list).StartY <= yCurrent) && (yCurrent < (*list).EndY));
+
+ activeCount += 1;
+ list = (*list).Next.get();
+ }
+
+ assert!((*list).X.get() == INT::MAX);
+ b &= ((*list).X.get() == INT::MAX);
+
+ // There should always be a multiple of 2 edges in the active list.
+ //
+ // NOTE: If you hit this assert, do NOT simply comment it out!
+ // It usually means that all the edges didn't get initialized
+ // properly. For every scan-line, there has to be a left edge
+ // and a right edge (or a multiple thereof). So if you give
+ // even a single bad edge to the edge initializer (or you miss
+ // one), you'll probably hit this assert.
+
+ assert!((activeCount & 1) == 0);
+ b &= ((activeCount & 1) == 0);
+
+ return (b);
+
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Some debug code for verifying the state of the active edge list.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+fn AssertActiveListOrder(mut list: Ref<CEdge>) {
+
+ assert!((*list).X.get() == INT::MIN);
+
+ // Skip the head sentinel:
+
+ list = (*list).Next.get();
+
+ while ((*list).X.get() != INT::MAX) {
+ assert!((*list).X.get() != INT::MIN);
+ assert!((*list).X <= (*(*list).Next.get()).X);
+
+ list = (*list).Next.get();
+ }
+
+ assert!((*list).X.get() == INT::MAX);
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Clip the edge vertically.
+*
+* We've pulled this routine out-of-line from InitializeEdges mainly
+* because it needs to call inline Asm, and when there is in-line
+* Asm in a routine the compiler generally does a much less efficient
+* job optimizing the whole routine. InitializeEdges is rather
+* performance critical, so we avoid polluting the whole routine
+* by having this functionality out-of-line.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+fn ClipEdge(edgeBuffer: &mut CEdge, yClipTopInteger: INT, dMOriginal: INT) {
+ let mut xDelta: INT;
+ let mut error: INT;
+
+ // Cases where bigNumerator will exceed 32-bits in precision
+ // will be rare, but could happen, and we can't fall over
+ // in those cases.
+
+ let dN: INT = edgeBuffer.ErrorDown;
+ let mut bigNumerator: LONGLONG = Int32x32To64(dMOriginal, yClipTopInteger - edgeBuffer.StartY)
+ + (edgeBuffer.Error.get() + dN) as LONGLONG;
+ if (bigNumerator >= 0) {
+ QUOTIENT_REMAINDER_64_32!(bigNumerator, dN, xDelta, error);
+ } else {
+ bigNumerator = -bigNumerator;
+ QUOTIENT_REMAINDER_64_32!(bigNumerator, dN, xDelta, error);
+
+ xDelta = -xDelta;
+ if (error != 0) {
+ xDelta -= 1;
+ error = dN - error;
+ }
+ }
+
+ // Update the edge data structure with the results:
+
+ edgeBuffer.StartY = yClipTopInteger;
+ edgeBuffer.X.set(edgeBuffer.X.get() + xDelta);
+ edgeBuffer.Error.set(error - dN); // Renormalize error
+}
+
+pub fn CheckValidRange28_4(x: f32, y: f32) -> bool {
+ //
+ // We want coordinates in the 28.4 range in the end. The matrix we get
+ // as input includes the scale by 16 to get to 28.4, so we want to
+ // ensure that we are in integer range. Assuming a sign bit and
+ // five bits for the rasterizer working range, we want coordinates in the
+ // -2^26 to 2^26.
+ //
+ // Note that the 5-bit requirement comes from the
+ // implementation of InitializeEdges.
+ // (See line with "error -= dN * (16 - (xStart & 15))")
+ //
+ // Anti-aliasing uses another c_nShift bits, so we get a
+ // desired range of -2^(26-c_nShift) to 2^(26-c_nShift)
+ //
+ let rPixelCoordinateMax = (1 << (26 - c_nShift)) as f32;
+ let rPixelCoordinateMin = -rPixelCoordinateMax;
+ return x <= rPixelCoordinateMax && x >= rPixelCoordinateMin
+ && y <= rPixelCoordinateMax && y >= rPixelCoordinateMin;
+}
+
+//+-----------------------------------------------------------------------------
+//
+// Function: TransformRasterizerPointsTo28_4
+//
+// Synopsis:
+// Transform rasterizer points to 28.4. If overflow occurs, return that
+// information.
+//
+//------------------------------------------------------------------------------
+fn TransformRasterizerPointsTo28_4(
+ pmat: &CMILMatrix,
+ // Transform to take us to 28.4
+ mut pPtsSource: &[MilPoint2F],
+ // Source points
+ mut cPoints: UINT,
+ // Count of points
+ mut pPtsDest: &mut [POINT], // Destination points
+) -> HRESULT {
+ let hr = S_OK;
+
+ debug_assert!(cPoints > 0);
+
+ while {
+ //
+ // Transform coordinates
+ //
+
+ let rPixelX =
+ (pmat.GetM11() * pPtsSource[0].X) + (pmat.GetM21() * pPtsSource[0].Y) + pmat.GetDx();
+ let rPixelY =
+ (pmat.GetM12() * pPtsSource[0].X) + (pmat.GetM22() * pPtsSource[0].Y) + pmat.GetDy();
+
+ //
+ // Check for NaNs or overflow
+ //
+
+ if !CheckValidRange28_4(rPixelX, rPixelY) {
+ return WGXERR_BADNUMBER;
+ }
+
+ //
+ // Assign coordinates
+ //
+
+ pPtsDest[0].x = CFloatFPU::Round(rPixelX);
+ pPtsDest[0].y = CFloatFPU::Round(rPixelY);
+
+ pPtsDest = &mut pPtsDest[1..];
+ pPtsSource = &pPtsSource[1..];
+ cPoints -= 1;
+ cPoints != 0
+ } {}
+
+ return hr;
+}
+
+pub fn AppendScaleToMatrix(pmat: &mut CMILMatrix, scaleX: REAL, scaleY: REAL) {
+ pmat.SetM11(pmat.GetM11() * scaleX);
+ pmat.SetM21(pmat.GetM21() * scaleX);
+ pmat.SetM12(pmat.GetM12() * scaleY);
+ pmat.SetM22(pmat.GetM22() * scaleY);
+ pmat.SetDx(pmat.GetDx() * scaleX);
+ pmat.SetDy(pmat.GetDy() * scaleY);
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Add edges to the edge list.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+pub struct CInitializeEdgesContext<'a> {
+ pub MaxY: INT, // Maximum 'y' found, should be INT_MIN on
+ // first call to 'InitializeEdges'
+ pub ClipRect: Option<&'a RECT>, // Bounding clip rectangle in 28.4 format
+ pub Store: &'a Arena<CEdge<'a>>, // Where to stick the edges
+ pub AntiAliasMode: MilAntiAliasMode,
+}
+
+impl<'a> CInitializeEdgesContext<'a> {
+ pub fn new(store: &'a Arena<CEdge<'a>>) -> Self {
+ CInitializeEdgesContext { MaxY: Default::default(), ClipRect: Default::default(), Store: store, AntiAliasMode: MilAntiAliasMode::None }
+ }
+}
+
+fn InitializeEdges(
+ pEdgeContext: &mut CInitializeEdgesContext,
+ /*__inout_ecount(vertexCount)*/
+ mut pointArray: &mut [POINT], // Points to a 28.4 array of size 'vertexCount'
+ // Note that we may modify the contents!
+ /*__in_range(>=, 2)*/ vertexCount: UINT,
+) -> HRESULT {
+ // Disable instrumentation checks for this function
+ //SET_MILINSTRUMENTATION_FLAGS(MILINSTRUMENTATIONFLAGS_DONOTHING);
+
+ let hr = S_OK;
+
+ let mut xStart;
+ let mut yStart;
+ let mut yStartInteger;
+ let mut yEndInteger;
+ let mut dMOriginal;
+ let mut dM: i32;
+ let mut dN: i32;
+ let mut dX: i32;
+ let mut errorUp: i32;
+ let mut quotient: i32;
+ let mut remainder: i32;
+ let mut error: i32;
+ let mut windingDirection;
+ //let mut edgeBuffer: *mut CEdge = NULL();
+ let bufferCount: UINT = 0;
+ let mut yClipTopInteger;
+ let mut yClipTop;
+ let mut yClipBottom;
+ let mut xClipLeft;
+ let mut xClipRight;
+
+ let mut yMax = pEdgeContext.MaxY;
+ let _store = &mut pEdgeContext.Store;
+ let clipRect = pEdgeContext.ClipRect;
+
+ let mut edgeCount = vertexCount - 1;
+ assert!(edgeCount >= 1);
+
+ if let Some(clipRect) = clipRect {
+ yClipTopInteger = clipRect.top >> 4;
+ yClipTop = clipRect.top;
+ yClipBottom = clipRect.bottom;
+ xClipLeft = clipRect.left;
+ xClipRight = clipRect.right;
+
+ assert!(yClipBottom > 0);
+ assert!(yClipTop <= yClipBottom);
+ } else {
+ yClipBottom = 0;
+ yClipTopInteger = INT::MIN >> c_nShift;
+
+ // These 3 values are only used when clipRect is non-NULL
+ yClipTop = 0;
+ xClipLeft = 0;
+ xClipRight = 0;
+ }
+
+ if (pEdgeContext.AntiAliasMode != MilAntiAliasMode::None) {
+ // If antialiasing, apply the supersampling scaling here before we
+ // calculate the DDAs. We do this here and not in the Matrix
+ // transform we give to FixedPointPathEnumerate mainly so that the
+ // Bezier flattener can continue to operate in its optimal 28.4
+ // format.
+ //
+ // PS#856364-2003/07/01-JasonHa Remove pixel center fixup
+ //
+ // We also apply a half-pixel offset here so that the antialiasing
+ // code can assume that the pixel centers are at half-pixel
+ // coordinates, not on the integer coordinates.
+
+ let mut point = &mut *pointArray;
+ let mut i = vertexCount;
+
+ while {
+ point[0].x = (point[0].x + 8) << c_nShift;
+ point[0].y = (point[0].y + 8) << c_nShift;
+ point = &mut point[1..];
+ i -= 1;
+ i != 0
+ } {}
+
+ yClipTopInteger <<= c_nShift;
+ yClipTop <<= c_nShift;
+ yClipBottom <<= c_nShift;
+ xClipLeft <<= c_nShift;
+ xClipRight <<= c_nShift;
+ }
+
+ // Make 'yClipBottom' inclusive by subtracting off one pixel
+ // (keeping in mind that we're in 28.4 device space):
+
+ yClipBottom -= 16;
+
+ // Warm up the store where we keep the edge data:
+
+ //store.StartAddBuffer(&mut edgeBuffer, &mut bufferCount);
+
+ 'outer: loop { loop {
+ // Handle trivial rejection:
+
+ if (yClipBottom >= 0) {
+ // Throw out any edges that are above or below the clipping.
+ // This has to be a precise check, because we assume later
+ // on that every edge intersects in the vertical dimension
+ // with the clip rectangle. That asssumption is made in two
+ // places:
+ //
+ // 1. When we sort the edges, we assume either zero edges,
+ // or two or more.
+ // 2. When we start the DDAs, we assume either zero edges,
+ // or that there's at least one scan of DDAs to output.
+ //
+ // Plus, of course, it's less efficient if we let things
+ // through.
+ //
+ // Note that 'yClipBottom' is inclusive:
+
+ let clipHigh = ((pointArray[0]).y <= yClipTop) && ((pointArray[1]).y <= yClipTop);
+
+ let clipLow = ((pointArray[0]).y > yClipBottom) && ((pointArray[1]).y > yClipBottom);
+
+ #[cfg(debug_assertions)]
+ {
+ let (mut yRectTop, mut yRectBottom, y0, y1, yTop, yBottom);
+
+ // Getting the trivial rejection code right is tricky.
+ // So on checked builds let's verify that we're doing it
+ // correctly, using a different approach:
+
+ let mut clipped = false;
+ if let Some(clipRect) = clipRect {
+ yRectTop = clipRect.top >> 4;
+ yRectBottom = clipRect.bottom >> 4;
+ if (pEdgeContext.AntiAliasMode != MilAntiAliasMode::None) {
+ yRectTop <<= c_nShift;
+ yRectBottom <<= c_nShift;
+ }
+ y0 = ((pointArray[0]).y + 15) >> 4;
+ y1 = ((pointArray[1]).y + 15) >> 4;
+ yTop = y0.min(y1);
+ yBottom = y0.max(y1);
+
+ clipped = ((yTop >= yRectBottom) || (yBottom <= yRectTop));
+ }
+
+ assert!(clipped == (clipHigh || clipLow));
+ }
+
+ if (clipHigh || clipLow) {
+ break; // ======================>
+ }
+
+ if (edgeCount > 1) {
+ // Here we'll collapse two edges down to one if both are
+ // to the left or to the right of the clipping rectangle.
+
+ if (((pointArray[0]).x < xClipLeft)
+ && ((pointArray[1]).x < xClipLeft)
+ && ((pointArray[2]).x < xClipLeft))
+ {
+ // Note this is one reason why 'pointArray' can't be 'const':
+
+ pointArray[1] = pointArray[0];
+
+ break; // ======================>
+ }
+
+ if (((pointArray[0]).x > xClipRight)
+ && ((pointArray[1]).x > xClipRight)
+ && ((pointArray[2]).x > xClipRight))
+ {
+ // Note this is one reason why 'pointArray' can't be 'const':
+
+ pointArray[1] = pointArray[0];
+
+ break; // ======================>
+ }
+ }
+ }
+
+ dM = (pointArray[1]).x - (pointArray[0]).x;
+ dN = (pointArray[1]).y - (pointArray[0]).y;
+
+ if (dN >= 0) {
+ // The vector points downward:
+
+ xStart = (pointArray[0]).x;
+ yStart = (pointArray[0]).y;
+
+ yStartInteger = (yStart + 15) >> 4;
+ yEndInteger = ((pointArray[1]).y + 15) >> 4;
+
+ windingDirection = 1;
+ } else {
+ // The vector points upward, so we have to essentially
+ // 'swap' the end points:
+
+ dN = -dN;
+ dM = -dM;
+
+ xStart = (pointArray[1]).x;
+ yStart = (pointArray[1]).y;
+
+ yStartInteger = (yStart + 15) >> 4;
+ yEndInteger = ((pointArray[0]).y + 15) >> 4;
+
+ windingDirection = -1;
+ }
+
+ // The edgeBuffer must span an integer y-value in order to be
+ // added to the edgeBuffer list. This serves to get rid of
+ // horizontal edges, which cause trouble for our divides.
+
+ if (yEndInteger > yStartInteger) {
+ yMax = yMax.max(yEndInteger);
+
+ dMOriginal = dM;
+ if (dM < 0) {
+ dM = -dM;
+ if (dM < dN)
+ // Can't be '<='
+ {
+ dX = -1;
+ errorUp = dN - dM;
+ } else {
+ QUOTIENT_REMAINDER!(dM, dN, quotient, remainder);
+
+ dX = -quotient;
+ errorUp = remainder;
+ if (remainder > 0) {
+ dX = -quotient - 1;
+ errorUp = dN - remainder;
+ }
+ }
+ } else {
+ if (dM < dN) {
+ dX = 0;
+ errorUp = dM;
+ } else {
+ QUOTIENT_REMAINDER!(dM, dN, quotient, remainder);
+
+ dX = quotient;
+ errorUp = remainder;
+ }
+ }
+
+ error = -1; // Error is initially zero (add dN - 1 for
+ // the ceiling, but subtract off dN so that
+ // we can check the sign instead of comparing
+ // to dN)
+
+ if ((yStart & 15) != 0) {
+ // Advance to the next integer y coordinate
+
+ let mut i = 16 - (yStart & 15);
+ while i != 0 {
+ xStart += dX;
+ error += errorUp;
+ if (error >= 0)
+ {
+ error -= dN;
+ xStart += 1;
+ }
+ i -= 1;
+ }
+ }
+
+ if ((xStart & 15) != 0) {
+ error -= dN * (16 - (xStart & 15));
+ xStart += 15; // We'll want the ceiling in just a bit...
+ }
+
+ xStart >>= 4;
+ error >>= 4;
+
+ if (bufferCount == 0) {
+ //IFC!(store.NextAddBuffer(&mut edgeBuffer, &mut bufferCount));
+ }
+
+ let mut edge = CEdge {
+ Next: Cell::new(unsafe { Ref::null() } ),
+ X: Cell::new(xStart),
+ Dx: dX,
+ Error: Cell::new(error),
+ ErrorUp: errorUp,
+ ErrorDown: dN,
+ WindingDirection: windingDirection,
+ StartY: yStartInteger,
+ EndY: yEndInteger,// Exclusive of end
+ };
+
+ assert!(error < 0);
+
+ // Here we handle the case where the edge starts above the
+ // clipping rectangle, and we need to jump down in the 'y'
+ // direction to the first unclipped scan-line.
+ //
+ // Consequently, we advance the DDA here:
+
+ if (yClipTopInteger > yStartInteger) {
+ assert!(edge.EndY > yClipTopInteger);
+
+ ClipEdge(&mut edge, yClipTopInteger, dMOriginal);
+ }
+
+ // Advance to handle the next edge:
+
+ //edgeBuffer = unsafe { edgeBuffer.offset(1) };
+ pEdgeContext.Store.alloc(edge);
+ //bufferCount -= 1;
+ }
+ break;
+ }
+ pointArray = &mut pointArray[1..];
+ edgeCount -= 1;
+ if edgeCount == 0 {
+ break 'outer;
+ }
+ }
+
+ // We're done with this batch. Let the store know how many edges
+ // we ended up with:
+
+ //store.EndAddBuffer(edgeBuffer, bufferCount);
+
+ pEdgeContext.MaxY = yMax;
+
+ return hr;
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Does complete parameter checking on the 'types' array of a path.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+fn ValidatePathTypes(typesArray: &[BYTE], mut count: INT) -> bool {
+ let mut types = typesArray;
+
+ if (count == 0) {
+ return (true);
+ }
+
+ loop {
+ // The first point in every subpath has to be an unadorned
+ // 'start' point:
+
+ if ((types[0] & PathPointTypePathTypeMask) != PathPointTypeStart) {
+ TraceTag!((tagMILWarning, "Bad subpath start"));
+ return (false);
+ }
+
+ // Advance to the first point after the 'start' point:
+ count -= 1;
+ if (count == 0) {
+ TraceTag!((tagMILWarning, "Path ended after start-path"));
+ return (false);
+ }
+
+ if ((types[1] & PathPointTypePathTypeMask) == PathPointTypeStart) {
+ TraceTag!((tagMILWarning, "Can't have a start followed by a start!"));
+ return (false);
+ }
+
+ // Process runs of lines and Bezier curves:
+
+ loop {
+ match (types[1] & PathPointTypePathTypeMask) {
+ PathPointTypeLine => {
+ types = &types[1..];
+ count -= 1;
+ if (count == 0) {
+ return (true);
+ }
+ }
+
+ PathPointTypeBezier => {
+ if (count < 3) {
+ TraceTag!((
+ tagMILWarning,
+ "Path ended before multiple of 3 Bezier points"
+ ));
+ return (false);
+ }
+
+ if ((types[1] & PathPointTypePathTypeMask) != PathPointTypeBezier) {
+ TraceTag!((tagMILWarning, "Bad subpath start"));
+ return (false);
+ }
+
+ types = &types[1..];
+ count -= 3;
+ if (count == 0) {
+ return (true);
+ }
+ }
+
+ _ => {
+ TraceTag!((tagMILWarning, "Illegal type"));
+ return (false);
+ }
+ }
+
+ // A close-subpath marker or a start-subpath marker marks the
+ // end of a subpath:
+ if !(!((types[0] & PathPointTypeCloseSubpath) != 0)
+ && ((types[1] & PathPointTypePathTypeMask) != PathPointTypeStart)) {
+ types = &types[1..];
+ break;
+ }
+ }
+ }
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Some debug code for verifying the path.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+macro_rules! ASSERTPATH {
+ ($types: expr, $points: expr) => {
+ #[cfg(debug_assertions)]
+ AssertPath($types, $points)
+ };
+}
+fn AssertPath(rgTypes: &[BYTE], cPoints: UINT) {
+ // Make sure that the 'types' array is well-formed, otherwise we
+ // may fall over in the FixedPointPathEnumerate function.
+ //
+ // NOTE: If you hit this assert, DO NOT SIMPLY COMMENT THIS Assert OUT!
+ //
+ // Instead, fix the ValidatePathTypes code if it's letting through
+ // valid paths, or (more likely) fix the code that's letting bogus
+ // paths through. The FixedPointPathEnumerate routine has some
+ // subtle assumptions that require the path to be perfectly valid!
+ //
+ // No internal code should be producing invalid paths, and all
+ // paths created by the application must be parameter checked!
+ assert!(ValidatePathTypes(rgTypes, cPoints as INT));
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member:
+// FixedPointPathEnumerate
+//
+// Synopsis:
+//
+// Enumerate the path.
+//
+// NOTE: The 'enumerateFunction' function is allowed to modify the
+// contents of our call-back buffer! (This is mainly done to allow
+// 'InitializeEdges' to be simpler for some clipping trivial
+// rejection cases.)
+//
+// NOTICE-2006/03/22-milesc This function was initially built to be a
+// general path enumeration function. However, we were only using it for
+// one specific purpose... for Initializing edges of a path to be filled.
+// In doing security work, I simplified this function to just do edge
+// initialization. The name is therefore now overly general. I have kept
+// the name to be a reminder that this function has been written to be
+// more general than would otherwise be evident.
+//
+
+pub fn FixedPointPathEnumerate(
+ rgpt: &[POINT],
+ rgTypes: &[BYTE],
+ cPoints: UINT,
+ _matrix: &CMILMatrix,
+ clipRect: Option<&RECT>, // In scaled 28.4 format
+ enumerateContext: &mut CInitializeEdgesContext,
+) -> HRESULT {
+ let hr = S_OK;
+ let mut bufferStart: [POINT; ENUMERATE_BUFFER_NUMBER!()] = [(); ENUMERATE_BUFFER_NUMBER!()].map(|_| Default::default());
+ let mut bezierBuffer: [POINT; 4] = Default::default();
+ let mut buffer: &mut [POINT];
+ let mut bufferSize: usize;
+ let mut startFigure: [POINT; 1] = Default::default();
+ // The current point offset in rgpt
+ let mut iPoint: usize;
+ // The current type offset in rgTypes
+ let mut iType: usize;
+ let mut runSize: usize;
+ let mut thisCount: usize;
+ let mut isMore: bool = false;
+ let mut xLast: INT;
+ let mut yLast: INT;
+
+ ASSERTPATH!(rgTypes, cPoints);
+
+ // Every valid subpath has at least two vertices in it, hence the
+ // check of 'cPoints - 1':
+
+ iPoint = 0;
+ iType = 0;
+
+ assert!(cPoints > 1);
+ while (iPoint < cPoints as usize - 1) {
+ assert!((rgTypes[iType] & PathPointTypePathTypeMask) == PathPointTypeStart);
+ assert!((rgTypes[iType + 1] & PathPointTypePathTypeMask) != PathPointTypeStart);
+
+ // Add the start point to the beginning of the batch, and
+ // remember it for handling the close figure:
+
+ startFigure[0] = rgpt[iPoint];
+
+ bufferStart[0].x = startFigure[0].x;
+ bufferStart[0].y = startFigure[0].y;
+ let bufferStartPtr = bufferStart.as_ptr();
+ buffer = &mut bufferStart[1..];
+ bufferSize = ENUMERATE_BUFFER_NUMBER!() - 1;
+
+ // We need to enter our loop with 'iType' pointing one past
+ // the start figure:
+
+ iPoint += 1;
+ iType += 1;
+
+ while {
+ // Try finding a run of lines:
+
+ if ((rgTypes[iType] & PathPointTypePathTypeMask) == PathPointTypeLine) {
+ runSize = 1;
+
+ while ((iPoint + runSize < cPoints as usize)
+ && ((rgTypes[iType + runSize] & PathPointTypePathTypeMask) == PathPointTypeLine))
+ {
+ runSize += 1;
+ }
+
+ // Okay, we've found a run of lines. Break it up into our
+ // buffer size:
+
+ loop {
+ thisCount = bufferSize.min(runSize);
+
+ buffer[0 .. thisCount].copy_from_slice(&rgpt[iPoint .. iPoint + thisCount]);
+
+ __analysis_assume!(
+ buffer + bufferSize == bufferStart + ENUMERATE_BUFFER_NUMBER
+ );
+ assert!(buffer.as_ptr().wrapping_offset(bufferSize as isize) == bufferStartPtr.wrapping_offset(ENUMERATE_BUFFER_NUMBER!()) );
+
+ iPoint += thisCount;
+ iType += thisCount;
+ buffer = &mut buffer[thisCount..];
+ runSize -= thisCount;
+ bufferSize -= thisCount;
+
+ if (bufferSize > 0) {
+ break;
+ }
+
+ xLast = bufferStart[ENUMERATE_BUFFER_NUMBER!() - 1].x;
+ yLast = bufferStart[ENUMERATE_BUFFER_NUMBER!() - 1].y;
+ IFR!(InitializeEdges(
+ enumerateContext,
+ &mut bufferStart,
+ ENUMERATE_BUFFER_NUMBER!()
+ ));
+
+ // Continue the last vertex as the first in the new batch:
+
+ bufferStart[0].x = xLast;
+ bufferStart[0].y = yLast;
+ buffer = &mut bufferStart[1..];
+ bufferSize = ENUMERATE_BUFFER_NUMBER!() - 1;
+ if !(runSize != 0) {
+ break;
+ }
+ }
+ } else {
+ assert!(iPoint + 3 <= cPoints as usize);
+ assert!((rgTypes[iType] & PathPointTypePathTypeMask) == PathPointTypeBezier);
+
+ bezierBuffer.copy_from_slice(&rgpt[(iPoint - 1) .. iPoint + 3]);
+
+ // Prepare for the next iteration:
+
+ iPoint += 3;
+ iType += 1;
+
+ // Process the Bezier:
+
+ let mut bezier = CMILBezier::new(&bezierBuffer, clipRect);
+ loop {
+ thisCount = bezier.Flatten(buffer, &mut isMore) as usize;
+
+ __analysis_assume!(
+ buffer + bufferSize == bufferStart + ENUMERATE_BUFFER_NUMBER!()
+ );
+ assert!(buffer.as_ptr().wrapping_offset(bufferSize as isize) == bufferStartPtr.wrapping_offset(ENUMERATE_BUFFER_NUMBER!()));
+
+ buffer = &mut buffer[thisCount..];
+ bufferSize -= thisCount;
+
+ if (bufferSize > 0) {
+ break;
+ }
+
+ xLast = bufferStart[ENUMERATE_BUFFER_NUMBER!() - 1].x;
+ yLast = bufferStart[ENUMERATE_BUFFER_NUMBER!() - 1].y;
+ IFR!(InitializeEdges(
+ enumerateContext,
+ &mut bufferStart,
+ ENUMERATE_BUFFER_NUMBER!()
+ ));
+
+ // Continue the last vertex as the first in the new batch:
+
+ bufferStart[0].x = xLast;
+ bufferStart[0].y = yLast;
+ buffer = &mut bufferStart[1..];
+ bufferSize = ENUMERATE_BUFFER_NUMBER!() - 1;
+ if !isMore {
+ break;
+ }
+ }
+ }
+
+ ((iPoint < cPoints as usize)
+ && ((rgTypes[iType] & PathPointTypePathTypeMask) != PathPointTypeStart))
+ } {}
+
+ // Okay, the subpath is done. But we still have to handle the
+ // 'close figure' (which is implicit for a fill):
+ // Add the close-figure point:
+
+ buffer[0].x = startFigure[0].x;
+ buffer[0].y = startFigure[0].y;
+ bufferSize -= 1;
+
+ // We have to flush anything we might have in the batch, unless
+ // there's only one vertex in there! (The latter case may happen
+ // for the stroke case with no close figure if we just flushed a
+ // batch.)
+ // If we're flattening, we must call the one additional time to
+ // correctly handle closing the subpath, even if there is only
+ // one entry in the batch. The flattening callback handles the
+ // one point case and closes the subpath properly without adding
+ // extraneous points.
+
+ let verticesInBatch = ENUMERATE_BUFFER_NUMBER!() - bufferSize;
+ if (verticesInBatch > 1) {
+ IFR!(InitializeEdges(
+ enumerateContext,
+ &mut bufferStart,
+ (verticesInBatch) as UINT
+ ));
+ }
+ }
+
+ return hr;
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* We want to sort in the inactive list; the primary key is 'y', and
+* the secondary key is 'x'. This routine creates a single LONGLONG
+* key that represents both.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+fn YX(x: INT, y: INT, p: &mut LONGLONG) {
+ // Bias 'x' by INT_MAX so that it's effectively unsigned:
+ /*
+ reinterpret_cast<LARGE_INTEGER*>(p)->HighPart = y;
+ reinterpret_cast<LARGE_INTEGER*>(p)->LowPart = x + INT_MAX;
+ */
+ *p = (((y as u64) << 32) | (((x as i64 + i32::MAX as i64) as u64) & 0xffffffff)) as i64;
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Recursive function to quick-sort our inactive edge list. Note that
+* for performance, the results are not completely sorted; an insertion
+* sort has to be run after the quicksort in order to do a lighter-weight
+* sort of the subtables.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+const QUICKSORT_THRESHOLD: isize = 8;
+
+fn QuickSortEdges(inactive: &mut [CInactiveEdge],
+ /*__inout_xcount(f - l + 1 elements)*/ f: usize,
+ /*__inout_xcount(array starts at f)*/ l: usize,
+) {
+ let mut e: Ref<CEdge>;
+ let mut y: LONGLONG;
+ let mut first: LONGLONG;
+ let mut second: LONGLONG;
+ let mut last: LONGLONG;
+
+ // Find the median of the first, middle, and last elements:
+
+ let m = f + ((l - f) >> 1);
+
+ SWAP!(y, inactive[f + 1].Yx, inactive[m].Yx);
+ SWAP!(e, inactive[f + 1].Edge, inactive[m].Edge);
+
+ if {second = inactive[f + 1].Yx; second > {last = inactive[l].Yx; last}} {
+ inactive[f + 1].Yx = last;
+ inactive[l].Yx = second;
+
+ SWAP!(e, inactive[f + 1].Edge, inactive[l].Edge);
+ }
+ if {first = inactive[f].Yx; first} > {last = inactive[l].Yx; last} {
+ inactive[f].Yx = last;
+ inactive[l].Yx = first;
+
+ SWAP!(e, inactive[f].Edge, inactive[l].Edge);
+ }
+ if {second = inactive[f + 1].Yx; second} > {first = inactive[f].Yx; first} {
+ inactive[f + 1].Yx = first;
+ inactive[f].Yx = second;
+
+ SWAP!(e, inactive[f + 1].Edge, inactive[f].Edge);
+ }
+
+ // f->Yx is now the desired median, and (f + 1)->Yx <= f->Yx <= l->Yx
+
+ debug_assert!((inactive[f + 1].Yx <= inactive[f].Yx) && (inactive[f].Yx <= inactive[l].Yx));
+
+ let median = inactive[f].Yx;
+
+ let mut i = f + 2;
+ while (inactive[i].Yx < median) {
+ i += 1;
+ }
+
+ let mut j = l - 1;
+ while (inactive[j].Yx > median) {
+ j -= 1;
+ }
+
+ while (i < j) {
+ SWAP!(y, inactive[i].Yx, inactive[j].Yx);
+ SWAP!(e, inactive[i].Edge, inactive[j].Edge);
+
+ while {
+ i = i + 1;
+ inactive[i].Yx < median
+ } {}
+
+ while {
+ j = j - 1 ;
+ inactive[j].Yx > median
+ } {}
+ }
+
+ SWAP!(y, inactive[f].Yx, inactive[j].Yx);
+ SWAP!(e, inactive[f].Edge, inactive[j].Edge);
+
+ let a = j - f;
+ let b = l - j;
+
+ // Use less stack space by recursing on the shorter subtable. Also,
+ // have the less-overhead insertion-sort handle small subtables.
+
+ if (a <= b) {
+ if (a > QUICKSORT_THRESHOLD as usize) {
+ // 'a' is the smallest, so do it first:
+
+ QuickSortEdges(inactive, f, j - 1);
+ QuickSortEdges(inactive, j + 1, l);
+ } else if (b > QUICKSORT_THRESHOLD as usize) {
+ QuickSortEdges(inactive, j + 1, l);
+ }
+ } else {
+ if (b > QUICKSORT_THRESHOLD as usize) {
+ // 'b' is the smallest, so do it first:
+
+ QuickSortEdges(inactive, j + 1 , l);
+ QuickSortEdges(inactive, f, j + 1);
+ } else if (a > QUICKSORT_THRESHOLD as usize) {
+ QuickSortEdges(inactive, f, j -1);
+ }
+ }
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Do a sort of the inactive table using an insertion-sort. Expects
+* large tables to have already been sorted via quick-sort.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+fn InsertionSortEdges(
+ /* __inout_xcount(count forward & -1 back)*/ mut inactive: &mut [CInactiveEdge],
+ mut count: INT,
+) {
+ let mut e: Ref<CEdge>;
+ let mut y: LONGLONG;
+ let mut yPrevious: LONGLONG;
+
+ assert!(inactive[0].Yx == i64::MIN);
+ assert!(count >= 2);
+ //inactive = &mut inactive[1..];
+
+ let mut indx = 2; // Skip first entry (by definition it's already in order!)
+ count -= 1;
+
+ while {
+ let mut p = indx;
+
+ // Copy the current stuff to temporary variables to make a hole:
+
+ e = (inactive[indx]).Edge;
+ y = (inactive[indx]).Yx;
+
+ // Shift everything one slot to the right (effectively moving
+ // the hole one position to the left):
+
+ while (y < {yPrevious = inactive[p-1].Yx; yPrevious}) {
+ inactive[p].Yx = yPrevious;
+ inactive[p].Edge = inactive[p-1].Edge;
+ p -= 1;
+ }
+
+ // Drop the temporary stuff into the final hole:
+
+ inactive[p].Yx = y;
+ inactive[p].Edge = e;
+
+ // The quicksort should have ensured that we don't have to move
+ // any entry terribly far:
+
+ assert!((indx - p) <= QUICKSORT_THRESHOLD as usize);
+
+ indx += 1;
+ count -= 1;
+ count != 0
+ } {}
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Assert the state of the inactive array.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+macro_rules! ASSERTINACTIVEARRAY {
+ ($inactive: expr, $count: expr) => {
+ #[cfg(debug_assertions)]
+ AssertInactiveArray($inactive, $count);
+ };
+}
+fn AssertInactiveArray(
+ /*__in_ecount(count)*/
+ mut inactive: &[CInactiveEdge], // Annotation should allow the -1 element
+ mut count: INT,
+) {
+ // Verify the head:
+
+ /*#if !ANALYSIS*/
+ // #if needed because prefast don't know that the -1 element is avaliable
+ assert!(inactive[0].Yx == i64::MIN);
+ /*#endif*/
+ assert!(inactive[1].Yx != i64::MIN);
+
+ while {
+ let mut yx: LONGLONG = 0;
+ YX((*inactive[1].Edge).X.get(), (*inactive[1].Edge).StartY, &mut yx);
+
+ assert!(inactive[1].Yx == yx);
+ /*#if !ANALYSIS*/
+ // #if needed because tools don't know that the -1 element is avaliable
+ assert!(inactive[1].Yx >= inactive[0].Yx);
+ /*#endif*/
+ inactive = &inactive[1..];
+ count -= 1;
+ count != 0
+ } {}
+
+ // Verify that the tail is setup appropriately:
+
+ assert!((*inactive[1].Edge).StartY == INT::MAX);
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Initialize and sort the inactive array.
+*
+* Returns:
+*
+* 'y' value of topmost edge.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+pub fn InitializeInactiveArray<'a>(
+ pEdgeStore: &'a Arena<CEdge<'a>>,
+ /*__in_ecount(count+2)*/ rgInactiveArray: &mut [CInactiveEdge<'a>],
+ count: UINT,
+ tailEdge: Ref<'a, CEdge<'a>> // Tail sentinel for inactive list
+) -> INT {
+ let rgInactiveArrayPtr = rgInactiveArray.as_mut_ptr();
+
+ // First initialize the inactive array. Skip the first entry,
+ // which we reserve as a head sentinel for the insertion sort:
+
+ let mut pInactiveEdge = &mut rgInactiveArray[1..];
+
+ for e in pEdgeStore.iter() {
+
+ pInactiveEdge[0].Edge = Ref::new(e);
+ YX(e.X.get(), e.StartY, &mut pInactiveEdge[0].Yx);
+ pInactiveEdge = &mut pInactiveEdge[1..];
+ }
+
+ assert!(unsafe { pInactiveEdge.as_mut_ptr().offset_from(rgInactiveArrayPtr) } as UINT == count + 1);
+
+ // Add the tail, which is used when reading back the array. This
+ // is why we had to allocate the array as 'count + 1':
+
+ pInactiveEdge[0].Edge = tailEdge;
+
+ // Add the head, which is used for the insertion sort. This is why
+ // we had to allocate the array as 'count + 2':
+
+ rgInactiveArray[0].Yx = i64::MIN;
+
+ // Only invoke the quicksort routine if it's worth the overhead:
+
+ if (count as isize > QUICKSORT_THRESHOLD) {
+ // Quick-sort this, skipping the first and last elements,
+ // which are sentinels.
+ //
+ // We do 'inactiveArray + count' to be inclusive of the last
+ // element:
+
+ QuickSortEdges(rgInactiveArray, 1, count as usize);
+ }
+
+ // Do a quick sort to handle the mostly sorted result:
+
+ InsertionSortEdges(rgInactiveArray, count as i32);
+
+ ASSERTINACTIVEARRAY!(rgInactiveArray, count as i32);
+
+ // Return the 'y' value of the topmost edge:
+
+ return (*rgInactiveArray[1].Edge).StartY;
+
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Insert edges into the active edge list.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+pub fn InsertNewEdges<'a>(
+ mut pActiveList: Ref<'a, CEdge<'a>>,
+ iCurrentY: INT,
+ /*__deref_inout_xcount(array terminated by an edge with StartY != iCurrentY)*/
+ ppInactiveEdge: &'a mut [CInactiveEdge<'a>],
+ pYNextInactive: &mut INT, // will be INT_MAX when no more
+) -> &'a mut [CInactiveEdge<'a>] {
+
+ let mut inactive: &mut [CInactiveEdge] = ppInactiveEdge;
+
+ assert!((*inactive[0].Edge).StartY == iCurrentY);
+
+ while {
+ let newActive: Ref<CEdge> = inactive[0].Edge;
+
+ // The activeList edge list sentinel has X = INT_MAX, so this always
+ // terminates:
+
+ while ((*(*pActiveList).Next.get()).X < (*newActive).X) {
+ pActiveList = (*pActiveList).Next.get();
+ }
+
+ if SORT_EDGES_INCLUDING_SLOPE {
+ // The activeList edge list sentinel has Dx = INT_MAX, so this always
+ // terminates:
+
+ while (((*(*pActiveList).Next.get()).X == (*newActive).X) && ((*(*pActiveList).Next.get()).Dx < (*newActive).Dx)) {
+ pActiveList = (*pActiveList).Next.get();
+ }
+ }
+
+ (*newActive).Next.set((*pActiveList).Next.get());
+ (*pActiveList).Next.set(newActive);
+
+ inactive = &mut inactive[1..];
+ (*(inactive[0]).Edge).StartY == iCurrentY
+ } {}
+
+ *pYNextInactive = (*(inactive[0]).Edge).StartY;
+ return inactive;
+
+}
+
+/**************************************************************************\
+*
+* Function Description:
+*
+* Sort the edges so that they're in ascending 'x' order.
+*
+* We use a bubble-sort for this stage, because edges maintain good
+* locality and don't often switch ordering positions.
+*
+* Created:
+*
+* 03/25/2000 andrewgo
+*
+\**************************************************************************/
+
+fn SortActiveEdges(list: Ref<CEdge>) {
+
+ let mut swapOccurred: bool;
+ let mut tmp: Ref<CEdge>;
+
+ // We should never be called with an empty active edge list:
+
+ assert!((*(*list).Next.get()).X.get() != INT::MAX);
+
+ while {
+ swapOccurred = false;
+
+ let mut previous = list;
+ let mut current = (*list).Next.get();
+ let mut next = (*current).Next.get();
+ let mut nextX = (*next).X.get();
+
+ while {
+ if (nextX < (*current).X.get()) {
+ swapOccurred = true;
+
+ (*previous).Next.set(next);
+ (*current).Next.set((*next).Next.get());
+ (*next).Next.set(current);
+
+ SWAP!(tmp, next, current);
+ }
+
+ previous = current;
+ current = next;
+ next = (*next).Next.get();
+ nextX = (*next).X.get();
+ nextX != INT::MAX
+ } {}
+ swapOccurred
+ } {}
+
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/bezier.rs b/third_party/rust/wpf-gpu-raster/src/bezier.rs
new file mode 100644
index 0000000000..fe54628a40
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/bezier.rs
@@ -0,0 +1,990 @@
+// 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.
+
+//+-----------------------------------------------------------------------------
+//
+// class Bezier32
+//
+// Bezier cracker.
+//
+// A hybrid cubic Bezier curve flattener based on KirkO's error factor.
+// Generates line segments fast without using the stack. Used to flatten a
+// path.
+//
+// For an understanding of the methods used, see:
+//
+// Kirk Olynyk, "..."
+// Goossen and Olynyk, "System and Method of Hybrid Forward
+// Differencing to Render Bezier Splines"
+// Lien, Shantz and Vaughan Pratt, "Adaptive Forward Differencing for
+// Rendering Curves and Surfaces", Computer Graphics, July 1987
+// Chang and Shantz, "Rendering Trimmed NURBS with Adaptive Forward
+// Differencing", Computer Graphics, August 1988
+// Foley and Van Dam, "Fundamentals of Interactive Computer Graphics"
+//
+// Public Interface:
+// bInit(pptfx) - pptfx points to 4 control points of
+// Bezier. Current point is set to the first
+// point after the start-point.
+// Bezier32(pptfx) - Constructor with initialization.
+// vGetCurrent(pptfx) - Returns current polyline point.
+// bCurrentIsEndPoint() - TRUE if current point is end-point.
+// vNext() - Moves to next polyline point.
+//
+
+
+#![allow(unused_parens)]
+#![allow(non_upper_case_globals)]
+//+-----------------------------------------------------------------------------
+//
+
+//
+// $TAG ENGR
+
+// $Module: win_mil_graphics_geometry
+// $Keywords:
+//
+// $Description:
+// Class for flattening a bezier.
+//
+// $ENDTAG
+//
+//------------------------------------------------------------------------------
+
+// First conversion from original 28.4 to 18.14 format
+const HFD32_INITIAL_SHIFT: i32 = 10;
+
+// Second conversion to 15.17 format
+const HFD32_ADDITIONAL_SHIFT: i32 = 3;
+
+
+// BEZIER_FLATTEN_GDI_COMPATIBLE:
+//
+// Don't turn on this switch without testing carefully. It's more for
+// documentation's sake - to show the values that GDI used - for an error
+// tolerance of 2/3.
+
+// It turns out that 2/3 produces very noticable artifacts on antialiased lines,
+// so we want to use 1/4 instead.
+/*
+#ifdef BEZIER_FLATTEN_GDI_COMPATIBLE
+
+// Flatten to an error of 2/3. During initial phase, use 18.14 format.
+
+#define TEST_MAGNITUDE_INITIAL (6 * 0x00002aa0L)
+
+// Error of 2/3. During normal phase, use 15.17 format.
+
+#define TEST_MAGNITUDE_NORMAL (TEST_MAGNITUDE_INITIAL << 3)
+
+#else
+*/
+use crate::types::*;
+/*
+// Flatten to an error of 1/4. During initial phase, use 18.14 format.
+
+const TEST_MAGNITUDE_INITIAL: i32 = (6 * 0x00001000);
+
+// Error of 1/4. During normal phase, use 15.17 format.
+
+const TEST_MAGNITUDE_NORMAL: i32 = (TEST_MAGNITUDE_INITIAL << 3);
+*/
+
+// I have modified the constants for HFD32 as part of fixing accuracy errors
+// (Bug 816015). Something similar could be done for the 64 bit hfd, but it ain't
+// broke so I'd rather not fix it.
+
+// The shift to the steady state 15.17 format
+const HFD32_SHIFT: LONG = HFD32_INITIAL_SHIFT + HFD32_ADDITIONAL_SHIFT;
+
+// Added to output numbers before rounding back to original representation
+const HFD32_ROUND: LONG = 1 << (HFD32_SHIFT - 1);
+
+// The error is tested on max(|e2|, |e3|), which represent 6 times the actual error.
+// The flattening tolerance is hard coded to 1/4 in the original geometry space,
+// which translates to 4 in 28.4 format. So 6 times that is:
+
+const HFD32_TOLERANCE: LONGLONG = 24;
+
+// During the initial phase, while working in 18.14 format
+const HFD32_INITIAL_TEST_MAGNITUDE: LONGLONG = HFD32_TOLERANCE << HFD32_INITIAL_SHIFT;
+
+// During the steady state, while working in 15.17 format
+const HFD32_TEST_MAGNITUDE: LONGLONG = HFD32_INITIAL_TEST_MAGNITUDE << HFD32_ADDITIONAL_SHIFT;
+
+// We will stop halving the segment with basis e1, e2, e3, e4 when max(|e2|, |e3|)
+// is less than HFD32_TOLERANCE. The operation e2 = (e2 + e3) >> 3 in vHalveStepSize() may
+// eat up 3 bits of accuracy. HfdBasis32 starts off with a pad of HFD32_SHIFT zeros, so
+// we can stay exact up to HFD32_SHIFT/3 subdivisions. Since every subdivision is guaranteed
+// to shift max(|e2|, |e3|) at least by 2, we will subdivide no more than n times if the
+// initial max(|e2|, |e3|) is less than than HFD32_TOLERANCE << 2n. But if the initial
+// max(|e2|, |e3|) is greater than HFD32_TOLERANCE >> (HFD32_SHIFT / 3) then we may not be
+// able to flatten with the 32 bit hfd, so we need to resort to the 64 bit hfd.
+
+const HFD32_MAX_ERROR: INT = (HFD32_TOLERANCE as i32) << ((2 * HFD32_INITIAL_SHIFT) / 3);
+
+// The maximum size of coefficients that can be handled by HfdBasis32.
+const HFD32_MAX_SIZE: LONGLONG = 0xffffc000;
+
+// Michka 9/12/03: I found this number in the the body of the code witout any explanation.
+// My analysis suggests that we could get away with larger numbers, but if I'm wrong we
+// could be in big trouble, so let us stay conservative.
+//
+// In bInit() we subtract Min(Bezier coeffients) from the original coefficients, so after
+// that 0 <= coefficients <= Bound, and the test will be Bound < HFD32_MAX_SIZE. When
+// switching to the HFD basis in bInit():
+// * e0 is the first Bezier coeffient, so abs(e0) <= Bound.
+// * e1 is a difference of non-negative coefficients so abs(e1) <= Bound.
+// * e2 and e3 can be written as 12*(p - (q + r)/2) where p,q and r are coefficients.
+// 0 <=(q + r)/2 <= Bound, so abs(p - (q + r)/2) <= 2*Bound, hence
+// abs(e2), abs(e3) <= 12*Bound.
+//
+// During vLazyHalveStepSize we add e2 + e3, resulting in absolute value <= 24*Bound.
+// Initially HfdBasis32 shifts the numbers by HFD32_INITIAL_SHIFT, so we need to handle
+// 24*bounds*(2^HFD32_SHIFT), and that needs to be less than 2^31. So the bounds need to
+// be less than 2^(31-HFD32_INITIAL_SHIFT)/24).
+//
+// For speed, the algorithm uses & rather than < for comparison. To facilitate that we
+// replace 24 by 32=2^5, and then the binary representation of the number is of the form
+// 0...010...0 with HFD32_SHIFT+5 trailing zeros. By subtracting that from 2^32 = 0xffffffff+1
+// we get a number that is 1..110...0 with the same number of trailing zeros, and that can be
+// used with an & for comparison. So the number should be:
+//
+// 0xffffffffL - (1L << (31 - HFD32_INITIAL_SHIFT - 5)) + 1 = (1L << 16) + 1 = 0xffff0000
+//
+// For the current values of HFD32_INITIAL_SHIFT=10 and HFD32_ADDITIONAL_SHIFT=3, the steady
+// state doesn't pose additional requirements, as shown below.
+//
+// For some reason the current code uses 0xfffc0000 = (1L << 14) + 1.
+//
+// Here is why the steady state doesn't pose additional requirements:
+//
+// In vSteadyState we multiply e0 and e1 by 8, so the requirement is Bounds*2^13 < 2^31,
+// or Bounds < 2^18, less stringent than the above.
+//
+// In vLazyHalveStepSize we cut the error down by subdivision, making abs(e2) and abs(e3)
+// less than HFD32_TEST_MAGNITUDE = 24*2^13, well below 2^31.
+//
+// During all the steady-state operations - vTakeStep, vHalveStepSize and vDoubleStepSize,
+// e0 is on the curve and e1 is a difference of 2 points on the curve, so
+// abs(e0), abs(e1) < Bounds * 2^13, which requires Bound < 2^(31-13) = 2^18. e2 and e3
+// are errors, kept below 6*HFD32_TEST_MAGNITUDE = 216*2^13. Details:
+//
+// In vTakeStep e2 = 2e2 - e3 keeps abs(e2) < 3*HFD32_TEST_MAGNITUDE = 72*2^13,
+// well below 2^31
+//
+// In vHalveStepSize we add e2 + e3 when their absolute is < 3*HFD32_TEST_MAGNITUDE (because
+// this comes after a step), so that keeps the result below 6*HFD32_TEST_MAGNITUDE = 216*2^13.
+//
+// In vDoubleStepSize we know that abs(e2), abs(e3) < HFD32_TEST_MAGNITUDE/4, otherwise we
+// would not have doubled the step.
+
+#[derive(Default)]
+struct HfdBasis32
+{
+ e0: LONG,
+ e1: LONG,
+ e2: LONG,
+ e3: LONG,
+}
+
+impl HfdBasis32 {
+ fn lParentErrorDividedBy4(&self) -> LONG {
+ self.e3.abs().max((self.e2 + self.e2 - self.e3).abs())
+ }
+
+ fn lError(&self) -> LONG
+ {
+ self.e2.abs().max(self.e3.abs())
+ }
+
+ fn fxValue(&self) -> INT
+ {
+ return((self.e0 + HFD32_ROUND) >> HFD32_SHIFT);
+ }
+
+ fn bInit(&mut self, p1: INT, p2: INT, p3: INT, p4: INT) -> bool
+ {
+ // Change basis and convert from 28.4 to 18.14 format:
+
+ self.e0 = (p1 ) << HFD32_INITIAL_SHIFT;
+ self.e1 = (p4 - p1 ) << HFD32_INITIAL_SHIFT;
+
+ self.e2 = 6 * (p2 - p3 - p3 + p4);
+ self.e3 = 6 * (p1 - p2 - p2 + p3);
+
+ if (self.lError() >= HFD32_MAX_ERROR)
+ {
+ // Large error, will require too many subdivision for this 32 bit hfd
+ return false;
+ }
+
+ self.e2 <<= HFD32_INITIAL_SHIFT;
+ self.e3 <<= HFD32_INITIAL_SHIFT;
+
+ return true;
+ }
+
+ fn vLazyHalveStepSize(&mut self, cShift: LONG)
+ {
+ self.e2 = self.ExactShiftRight(self.e2 + self.e3, 1);
+ self.e1 = self.ExactShiftRight(self.e1 - self.ExactShiftRight(self.e2, cShift), 1);
+ }
+
+ fn vSteadyState(&mut self, cShift: LONG)
+ {
+ // We now convert from 18.14 fixed format to 15.17:
+
+ self.e0 <<= HFD32_ADDITIONAL_SHIFT;
+ self.e1 <<= HFD32_ADDITIONAL_SHIFT;
+
+ let mut lShift = cShift - HFD32_ADDITIONAL_SHIFT;
+
+ if (lShift < 0)
+ {
+ lShift = -lShift;
+ self.e2 <<= lShift;
+ self.e3 <<= lShift;
+ }
+ else
+ {
+ self.e2 >>= lShift;
+ self.e3 >>= lShift;
+ }
+ }
+
+ fn vHalveStepSize(&mut self)
+ {
+ self.e2 = self.ExactShiftRight(self.e2 + self.e3, 3);
+ self.e1 = self.ExactShiftRight(self.e1 - self.e2, 1);
+ self.e3 = self.ExactShiftRight(self.e3, 2);
+ }
+
+ fn vDoubleStepSize(&mut self)
+ {
+ self.e1 += self.e1 + self.e2;
+ self.e3 <<= 2;
+ self.e2 = (self.e2 << 3) - self.e3;
+ }
+
+ fn vTakeStep(&mut self)
+ {
+ self.e0 += self.e1;
+ let lTemp = self.e2;
+ self.e1 += lTemp;
+ self.e2 += lTemp - self.e3;
+ self.e3 = lTemp;
+ }
+
+ fn ExactShiftRight(&self, num: i32, shift: i32) -> i32
+ {
+ // Performs a shift to the right while asserting that we're not
+ // losing significant bits
+
+ assert!(num == (num >> shift) << shift);
+ return num >> shift;
+ }
+}
+
+fn vBoundBox(
+ aptfx: &[POINT; 4]) -> RECT
+{
+ let mut left = aptfx[0].x;
+ let mut right = aptfx[0].x;
+ let mut top = aptfx[0].y;
+ let mut bottom = aptfx[0].y;
+
+ for i in 1..4
+ {
+ left = left.min(aptfx[i].x);
+ top = top.min(aptfx[i].y);
+ right = right.max(aptfx[i].x);
+ bottom = bottom.max(aptfx[i].y);
+ }
+
+ // We make the bounds one pixel loose for the nominal width
+ // stroke case, which increases the bounds by half a pixel
+ // in every dimension:
+
+ RECT { left: left - 16, top: top - 16, right: right + 16, bottom: bottom + 16}
+}
+
+
+
+fn bIntersect(
+ a: &RECT,
+ b: &RECT) -> bool
+{
+ return((a.left < b.right) &&
+ (a.top < b.bottom) &&
+ (a.right > b.left) &&
+ (a.bottom > b.top));
+}
+
+#[derive(Default)]
+pub struct Bezier32
+{
+ cSteps: LONG,
+ x: HfdBasis32,
+ y: HfdBasis32,
+ rcfxBound: RECT
+}
+impl Bezier32 {
+
+fn bInit(&mut self,
+ aptfxBez: &[POINT; 4],
+ // Pointer to 4 control points
+ prcfxClip: Option<&RECT>) -> bool
+ // Bound box of visible region (optional)
+{
+ let mut aptfx;
+ let mut cShift = 0; // Keeps track of 'lazy' shifts
+
+ self.cSteps = 1; // Number of steps to do before reach end of curve
+
+ self.rcfxBound = vBoundBox(aptfxBez);
+
+ aptfx = aptfxBez.clone();
+
+ {
+ let mut fxOr;
+ let mut fxOffset;
+
+ // find out if the coordinates minus the bounding box
+ // exceed 10 bits
+ fxOffset = self.rcfxBound.left;
+ fxOr = {aptfx[0].x -= fxOffset; aptfx[0].x};
+ fxOr |= {aptfx[1].x -= fxOffset; aptfx[1].x};
+ fxOr |= {aptfx[2].x -= fxOffset; aptfx[2].x};
+ fxOr |= {aptfx[3].x -= fxOffset; aptfx[3].x};
+
+ fxOffset = self.rcfxBound.top;
+ fxOr |= {aptfx[0].y -= fxOffset; aptfx[0].y};
+ fxOr |= {aptfx[1].y -= fxOffset; aptfx[1].y};
+ fxOr |= {aptfx[2].y -= fxOffset; aptfx[2].y};
+ fxOr |= {aptfx[3].y -= fxOffset; aptfx[3].y};
+
+ // This 32 bit cracker can only handle points in a 10 bit space:
+
+ if ((fxOr as i64 & HFD32_MAX_SIZE) != 0) {
+ return false;
+ }
+ }
+
+ if (!self.x.bInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x))
+ {
+ return false;
+ }
+ if (!self.y.bInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y))
+ {
+ return false;
+ }
+
+
+ if (match prcfxClip { None => true, Some(clip) => bIntersect(&self.rcfxBound, clip)})
+ {
+
+ loop {
+ let lTestMagnitude = (HFD32_INITIAL_TEST_MAGNITUDE << cShift) as LONG;
+
+ if (self.x.lError() <= lTestMagnitude && self.y.lError() <= lTestMagnitude) {
+ break;
+ }
+
+ cShift += 2;
+ self.x.vLazyHalveStepSize(cShift);
+ self.y.vLazyHalveStepSize(cShift);
+ self.cSteps <<= 1;
+ }
+ }
+
+ self.x.vSteadyState(cShift);
+ self.y.vSteadyState(cShift);
+
+// Note that this handles the case where the initial error for
+// the Bezier is already less than HFD32_TEST_MAGNITUDE:
+
+ self.x.vTakeStep();
+ self.y.vTakeStep();
+ self.cSteps-=1;
+
+ return true;
+}
+
+
+fn cFlatten(&mut self,
+ mut pptfx: &mut [POINT],
+ pbMore: &mut bool) -> i32
+{
+ let mut cptfx = pptfx.len();
+ assert!(cptfx > 0);
+
+ let cptfxOriginal = cptfx;
+
+ while {
+ // Return current point:
+
+ pptfx[0].x = self.x.fxValue() + self.rcfxBound.left;
+ pptfx[0].y = self.y.fxValue() + self.rcfxBound.top;
+ pptfx = &mut pptfx[1..];
+
+ // If cSteps == 0, that was the end point in the curve!
+
+ if (self.cSteps == 0)
+ {
+ *pbMore = false;
+
+ // '+1' because we haven't decremented 'cptfx' yet:
+
+ return(cptfxOriginal - cptfx + 1) as i32;
+ }
+
+ // Okay, we have to step:
+
+ if (self.x.lError().max(self.y.lError()) > HFD32_TEST_MAGNITUDE as LONG)
+ {
+ self.x.vHalveStepSize();
+ self.y.vHalveStepSize();
+ self.cSteps <<= 1;
+ }
+
+ // We are here after vTakeStep. Before that the error max(|e2|,|e3|) was less
+ // than HFD32_TEST_MAGNITUDE. vTakeStep changed e2 to 2e2-e3. Since
+ // |2e2-e3| < max(|e2|,|e3|) << 2 and vHalveStepSize is guaranteed to reduce
+ // max(|e2|,|e3|) by >> 2, no more than one subdivision should be required to
+ // bring the new max(|e2|,|e3|) back to within HFD32_TEST_MAGNITUDE, so:
+ assert!(self.x.lError().max(self.y.lError()) <= HFD32_TEST_MAGNITUDE as LONG);
+
+ while (!(self.cSteps & 1 != 0) &&
+ self.x.lParentErrorDividedBy4() <= (HFD32_TEST_MAGNITUDE as LONG >> 2) &&
+ self.y.lParentErrorDividedBy4() <= (HFD32_TEST_MAGNITUDE as LONG >> 2))
+ {
+ self.x.vDoubleStepSize();
+ self.y.vDoubleStepSize();
+ self.cSteps >>= 1;
+ }
+
+ self.cSteps -=1 ;
+ self.x.vTakeStep();
+ self.y.vTakeStep();
+ cptfx -= 1;
+ cptfx != 0
+ } {}
+
+ *pbMore = true;
+ return cptfxOriginal as i32;
+}
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+// Bezier64
+//
+// All math is done using 64 bit fixed numbers in a 36.28 format.
+//
+// All drawing is done in a 31 bit space, then a 31 bit window offset
+// is applied. In the initial transform where we change to the HFD
+// basis, e2 and e3 require the most bits precision: e2 = 6(p2 - 2p3 + p4).
+// This requires an additional 4 bits precision -- hence we require 36 bits
+// for the integer part, and the remaining 28 bits is given to the fraction.
+//
+// In rendering a Bezier, every 'subdivide' requires an extra 3 bits of
+// fractional precision. In order to be reversible, we can allow no
+// error to creep in. Since a INT coordinate is 32 bits, and we
+// require an additional 4 bits as mentioned above, that leaves us
+// 28 bits fractional precision -- meaning we can do a maximum of
+// 9 subdivides. Now, the maximum absolute error of a Bezier curve in 27
+// bit integer space is 2^29 - 1. But 9 subdivides reduces the error by a
+// guaranteed factor of 2^18, meaning we can subdivide down only to an error
+// of 2^11 before we overflow, when in fact we want to reduce error to less
+// than 1.
+//
+// So what we do is HFD until we hit an error less than 2^11, reverse our
+// basis transform to get the four control points of this smaller curve
+// (rounding in the process to 32 bits), then invoke another copy of HFD
+// on the reduced Bezier curve. We again have enough precision, but since
+// its starting error is less than 2^11, we can reduce error to 2^-7 before
+// overflowing! We'll start a low HFD after every step of the high HFD.
+////////////////////////////////////////////////////////////////////////////
+#[derive(Default)]
+struct HfdBasis64
+{
+ e0: LONGLONG,
+ e1: LONGLONG,
+ e2: LONGLONG,
+ e3: LONGLONG,
+}
+
+impl HfdBasis64 {
+fn vParentError(&self) -> LONGLONG
+{
+ (self.e3 << 2).abs().max(((self.e2 << 3) - (self.e3 << 2)).abs())
+}
+
+fn vError(&self) -> LONGLONG
+{
+ self.e2.abs().max(self.e3.abs())
+}
+
+fn fxValue(&self) -> INT
+{
+// Convert from 36.28 and round:
+
+ let mut eq = self.e0;
+ eq += (1 << (BEZIER64_FRACTION - 1));
+ eq >>= BEZIER64_FRACTION;
+ return eq as LONG as INT;
+}
+
+fn vInit(&mut self, p1: INT, p2: INT, p3: INT, p4: INT)
+{
+ let mut eqTmp;
+ let eqP2 = p2 as LONGLONG;
+ let eqP3 = p3 as LONGLONG;
+
+// e0 = p1
+// e1 = p4 - p1
+// e2 = 6(p2 - 2p3 + p4)
+// e3 = 6(p1 - 2p2 + p3)
+
+// Change basis:
+
+ self.e0 = p1 as LONGLONG; // e0 = p1
+ self.e1 = p4 as LONGLONG;
+ self.e2 = eqP2; self.e2 -= eqP3; self.e2 -= eqP3; self.e2 += self.e1; // e2 = p2 - 2*p3 + p4
+ self.e3 = self.e0; self.e3 -= eqP2; self.e3 -= eqP2; self.e3 += eqP3; // e3 = p1 - 2*p2 + p3
+ self.e1 -= self.e0; // e1 = p4 - p1
+
+// Convert to 36.28 format and multiply e2 and e3 by six:
+
+ self.e0 <<= BEZIER64_FRACTION;
+ self.e1 <<= BEZIER64_FRACTION;
+ eqTmp = self.e2; self.e2 += eqTmp; self.e2 += eqTmp; self.e2 <<= (BEZIER64_FRACTION + 1);
+ eqTmp = self.e3; self.e3 += eqTmp; self.e3 += eqTmp; self.e3 <<= (BEZIER64_FRACTION + 1);
+}
+
+fn vUntransform<F: Fn(&mut POINT) -> &mut LONG>(&self,
+ afx: &mut [POINT; 4], field: F)
+{
+// Declare some temps to hold our operations, since we can't modify e0..e3.
+
+ let mut eqP0;
+ let mut eqP1;
+ let mut eqP2;
+ let mut eqP3;
+
+// p0 = e0
+// p1 = e0 + (6e1 - e2 - 2e3)/18
+// p2 = e0 + (12e1 - 2e2 - e3)/18
+// p3 = e0 + e1
+
+ eqP0 = self.e0;
+
+// NOTE PERF: Convert this to a multiply by 6: [andrewgo]
+
+ eqP2 = self.e1;
+ eqP2 += self.e1;
+ eqP2 += self.e1;
+ eqP1 = eqP2;
+ eqP1 += eqP2; // 6e1
+ eqP1 -= self.e2; // 6e1 - e2
+ eqP2 = eqP1;
+ eqP2 += eqP1; // 12e1 - 2e2
+ eqP2 -= self.e3; // 12e1 - 2e2 - e3
+ eqP1 -= self.e3;
+ eqP1 -= self.e3; // 6e1 - e2 - 2e3
+
+// NOTE: May just want to approximate these divides! [andrewgo]
+// Or can do a 64 bit divide by 32 bit to get 32 bits right here.
+
+ eqP1 /= 18;
+ eqP2 /= 18;
+ eqP1 += self.e0;
+ eqP2 += self.e0;
+
+ eqP3 = self.e0;
+ eqP3 += self.e1;
+
+// Convert from 36.28 format with rounding:
+
+ eqP0 += (1 << (BEZIER64_FRACTION - 1)); eqP0 >>= BEZIER64_FRACTION; *field(&mut afx[0]) = eqP0 as LONG;
+ eqP1 += (1 << (BEZIER64_FRACTION - 1)); eqP1 >>= BEZIER64_FRACTION; *field(&mut afx[1]) = eqP1 as LONG;
+ eqP2 += (1 << (BEZIER64_FRACTION - 1)); eqP2 >>= BEZIER64_FRACTION; *field(&mut afx[2]) = eqP2 as LONG;
+ eqP3 += (1 << (BEZIER64_FRACTION - 1)); eqP3 >>= BEZIER64_FRACTION; *field(&mut afx[3]) = eqP3 as LONG;
+}
+
+fn vHalveStepSize(&mut self)
+{
+// e2 = (e2 + e3) >> 3
+// e1 = (e1 - e2) >> 1
+// e3 >>= 2
+
+ self.e2 += self.e3; self.e2 >>= 3;
+ self.e1 -= self.e2; self.e1 >>= 1;
+ self.e3 >>= 2;
+}
+
+fn vDoubleStepSize(&mut self)
+{
+// e1 = 2e1 + e2
+// e3 = 4e3;
+// e2 = 8e2 - e3
+
+ self.e1 <<= 1; self.e1 += self.e2;
+ self.e3 <<= 2;
+ self.e2 <<= 3; self.e2 -= self.e3;
+}
+
+fn vTakeStep(&mut self)
+{
+ self.e0 += self.e1;
+ let eqTmp = self.e2;
+ self.e1 += self.e2;
+ self.e2 += eqTmp; self.e2 -= self.e3;
+ self.e3 = eqTmp;
+}
+}
+
+const BEZIER64_FRACTION: LONG = 28;
+
+// The following is our 2^11 target error encoded as a 36.28 number
+// (don't forget the additional 4 bits of fractional precision!) and
+// the 6 times error multiplier:
+
+const geqErrorHigh: LONGLONG = (6 * (1 << 15) >> (32 - BEZIER64_FRACTION)) << 32;
+
+/*#ifdef BEZIER_FLATTEN_GDI_COMPATIBLE
+
+// The following is the default 2/3 error encoded as a 36.28 number,
+// multiplied by 6, and leaving 4 bits for fraction:
+
+const LONGLONG geqErrorLow = (LONGLONG)(4) << 32;
+
+#else*/
+
+// The following is the default 1/4 error encoded as a 36.28 number,
+// multiplied by 6, and leaving 4 bits for fraction:
+
+use crate::types::POINT;
+
+const geqErrorLow: LONGLONG = (3) << 31;
+
+//#endif
+#[derive(Default)]
+pub struct Bezier64
+{
+ xLow: HfdBasis64,
+ yLow: HfdBasis64,
+ xHigh: HfdBasis64,
+ yHigh: HfdBasis64,
+
+ eqErrorLow: LONGLONG,
+ rcfxClip: Option<RECT>,
+
+ cStepsHigh: LONG,
+ cStepsLow: LONG
+}
+
+impl Bezier64 {
+
+fn vInit(&mut self,
+ aptfx: &[POINT; 4],
+ // Pointer to 4 control points
+ prcfxVis: Option<&RECT>,
+ // Pointer to bound box of visible area (may be NULL)
+ eqError: LONGLONG)
+ // Fractional maximum error (32.32 format)
+{
+ self.cStepsHigh = 1;
+ self.cStepsLow = 0;
+
+ self.xHigh.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x);
+ self.yHigh.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y);
+
+// Initialize error:
+
+ self.eqErrorLow = eqError;
+
+ self.rcfxClip = prcfxVis.cloned();
+
+ while (((self.xHigh.vError()) > geqErrorHigh) ||
+ ((self.yHigh.vError()) > geqErrorHigh))
+ {
+ self.cStepsHigh <<= 1;
+ self.xHigh.vHalveStepSize();
+ self.yHigh.vHalveStepSize();
+ }
+}
+
+fn cFlatten(
+ &mut self,
+ mut pptfx: &mut [POINT],
+ pbMore: &mut bool) -> INT
+{
+ let mut aptfx: [POINT; 4] = Default::default();
+ let mut cptfx = pptfx.len();
+ let mut rcfxBound: RECT;
+ let cptfxOriginal = cptfx;
+
+ assert!(cptfx > 0);
+
+ while {
+ if (self.cStepsLow == 0)
+ {
+ // Optimization that if the bound box of the control points doesn't
+ // intersect with the bound box of the visible area, render entire
+ // curve as a single line:
+
+ self.xHigh.vUntransform(&mut aptfx, |p| &mut p.x);
+ self.yHigh.vUntransform(&mut aptfx, |p| &mut p.y);
+
+ self.xLow.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x);
+ self.yLow.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y);
+ self.cStepsLow = 1;
+
+ if (match &self.rcfxClip { None => true, Some(clip) => {rcfxBound = vBoundBox(&aptfx); bIntersect(&rcfxBound, &clip)}})
+ {
+ while (((self.xLow.vError()) > self.eqErrorLow) ||
+ ((self.yLow.vError()) > self.eqErrorLow))
+ {
+ self.cStepsLow <<= 1;
+ self.xLow.vHalveStepSize();
+ self.yLow.vHalveStepSize();
+ }
+ }
+
+ // This 'if' handles the case where the initial error for the Bezier
+ // is already less than the target error:
+
+ if ({self.cStepsHigh -= 1; self.cStepsHigh} != 0)
+ {
+ self.xHigh.vTakeStep();
+ self.yHigh.vTakeStep();
+
+ if (((self.xHigh.vError()) > geqErrorHigh) ||
+ ((self.yHigh.vError()) > geqErrorHigh))
+ {
+ self.cStepsHigh <<= 1;
+ self.xHigh.vHalveStepSize();
+ self.yHigh.vHalveStepSize();
+ }
+
+ while (!(self.cStepsHigh & 1 != 0) &&
+ ((self.xHigh.vParentError()) <= geqErrorHigh) &&
+ ((self.yHigh.vParentError()) <= geqErrorHigh))
+ {
+ self.xHigh.vDoubleStepSize();
+ self.yHigh.vDoubleStepSize();
+ self.cStepsHigh >>= 1;
+ }
+ }
+ }
+
+ self.xLow.vTakeStep();
+ self.yLow.vTakeStep();
+
+ pptfx[0].x = self.xLow.fxValue();
+ pptfx[0].y = self.yLow.fxValue();
+ pptfx = &mut pptfx[1..];
+
+ self.cStepsLow-=1;
+ if (self.cStepsLow == 0 && self.cStepsHigh == 0)
+ {
+ *pbMore = false;
+
+ // '+1' because we haven't decremented 'cptfx' yet:
+
+ return(cptfxOriginal - cptfx + 1) as INT;
+ }
+
+ if ((self.xLow.vError() > self.eqErrorLow) ||
+ (self.yLow.vError() > self.eqErrorLow))
+ {
+ self.cStepsLow <<= 1;
+ self.xLow.vHalveStepSize();
+ self.yLow.vHalveStepSize();
+ }
+
+ while (!(self.cStepsLow & 1 != 0) &&
+ ((self.xLow.vParentError()) <= self.eqErrorLow) &&
+ ((self.yLow.vParentError()) <= self.eqErrorLow))
+ {
+ self.xLow.vDoubleStepSize();
+ self.yLow.vDoubleStepSize();
+ self.cStepsLow >>= 1;
+ }
+ cptfx -= 1;
+ cptfx != 0
+ } {};
+
+ *pbMore = true;
+ return(cptfxOriginal) as INT;
+}
+}
+
+//+-----------------------------------------------------------------------------
+//
+// class CMILBezier
+//
+// Bezier cracker. Flattens any Bezier in our 28.4 device space down to a
+// smallest 'error' of 2^-7 = 0.0078. Will use fast 32 bit cracker for small
+// curves and slower 64 bit cracker for big curves.
+//
+// Public Interface:
+// vInit(aptfx, prcfxClip, peqError)
+// - pptfx points to 4 control points of Bezier. The first point
+// retrieved by bNext() is the the first point in the approximation
+// after the start-point.
+//
+// - prcfxClip is an optional pointer to the bound box of the visible
+// region. This is used to optimize clipping of Bezier curves that
+// won't be seen. Note that this value should account for the pen's
+// width!
+//
+// - optional maximum error in 32.32 format, corresponding to Kirko's
+// error factor.
+//
+// bNext(pptfx)
+// - pptfx points to where next point in approximation will be
+// returned. Returns FALSE if the point is the end-point of the
+// curve.
+//
+pub (crate) enum CMILBezier
+{
+ Bezier64(Bezier64),
+ Bezier32(Bezier32)
+}
+
+impl CMILBezier {
+ // All coordinates must be in 28.4 format:
+ pub fn new(aptfxBez: &[POINT; 4], prcfxClip: Option<&RECT>) -> Self {
+ let mut bez32 = Bezier32::default();
+ let bBez32 = bez32.bInit(aptfxBez, prcfxClip);
+ if bBez32 {
+ CMILBezier::Bezier32(bez32)
+ } else {
+ let mut bez64 = Bezier64::default();
+ bez64.vInit(aptfxBez, prcfxClip, geqErrorLow);
+ CMILBezier::Bezier64(bez64)
+ }
+ }
+
+ // Returns the number of points filled in. This will never be zero.
+ //
+ // The last point returned may not be exactly the last control
+ // point. The workaround is for calling code to add an extra
+ // point if this is the case.
+ pub fn Flatten( &mut self,
+ pptfx: &mut [POINT],
+ pbMore: &mut bool) -> INT {
+ match self {
+ CMILBezier::Bezier32(bez) => bez.cFlatten(pptfx, pbMore),
+ CMILBezier::Bezier64(bez) => bez.cFlatten(pptfx, pbMore)
+ }
+ }
+}
+
+#[test]
+fn flatten() {
+ let curve: [POINT; 4] = [
+ POINT{x: 1715, y: 6506},
+ POINT{x: 1692, y: 6506},
+ POINT{x: 1227, y: 5148},
+ POINT{x: 647, y: 5211}];
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut result: [POINT; 32] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut result, &mut more);
+ assert_eq!(count, 21);
+ assert_eq!(more, false);
+}
+
+#[test]
+fn split_flatten32() {
+ // make sure that flattening a curve into two small buffers matches
+ // doing it into a large buffer
+ let curve: [POINT; 4] = [
+ POINT{x: 1795, y: 8445},
+ POINT{x: 1795, y: 8445},
+ POINT{x: 1908, y: 8683},
+ POINT{x: 2043, y: 8705}];
+
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut result: [POINT; 8] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut result[..5], &mut more);
+ assert_eq!(count, 5);
+ assert_eq!(more, true);
+ let count = bez.Flatten(&mut result[5..], &mut more);
+ assert_eq!(count, 3);
+ assert_eq!(more, false);
+
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut full_result: [POINT; 8] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut full_result, &mut more);
+ assert_eq!(count, 8);
+ assert_eq!(more, false);
+ assert!(result == full_result);
+}
+
+#[test]
+fn flatten32() {
+ let curve: [POINT; 4] = [
+ POINT{x: 100, y: 100},
+ POINT{x: 110, y: 100},
+ POINT{x: 110, y: 110},
+ POINT{x: 110, y: 100}];
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut result: [POINT; 32] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut result, &mut more);
+ assert_eq!(count, 3);
+ assert_eq!(more, false);
+}
+
+#[test]
+fn flatten32_double_step_size() {
+ let curve: [POINT; 4] = [
+ POINT{x: 1761, y: 8152},
+ POINT{x: 1761, y: 8152},
+ POINT{x: 1750, y: 8355},
+ POINT{x: 1795, y: 8445}];
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut result: [POINT; 32] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut result, &mut more);
+ assert_eq!(count, 7);
+ assert_eq!(more, false);
+}
+
+#[test]
+fn bezier64_init_high_num_steps() {
+ let curve: [POINT; 4] = [
+ POINT{x: 33, y: -1},
+ POINT{x: -1, y: -1},
+ POINT{x: -1, y: -16385},
+ POINT{x: -226, y: 10}];
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut result: [POINT; 32] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut result, &mut more);
+ assert_eq!(count, 32);
+ assert_eq!(more, true);
+}
+
+#[test]
+fn bezier64_high_error() {
+ let curve: [POINT; 4] = [
+ POINT{x: -1, y: -1},
+ POINT{x: -4097, y: -1},
+ POINT{x: 65471, y: -256},
+ POINT{x: -1, y: 0}];
+ let mut bez = CMILBezier::new(&curve, None);
+ let mut result: [POINT; 32] = Default::default();
+ let mut more: bool = false;
+ let count = bez.Flatten(&mut result, &mut more);
+ assert_eq!(count, 32);
+ assert_eq!(more, true);
+} \ No newline at end of file
diff --git a/third_party/rust/wpf-gpu-raster/src/c_bindings.rs b/third_party/rust/wpf-gpu-raster/src/c_bindings.rs
new file mode 100644
index 0000000000..1016287200
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/c_bindings.rs
@@ -0,0 +1,158 @@
+use crate::{PathBuilder, OutputPath, OutputVertex, FillMode, rasterize_to_tri_list};
+use crate::types::{BYTE, POINT};
+
+#[no_mangle]
+pub extern "C" fn wgr_new_builder() -> *mut PathBuilder {
+ let pb = PathBuilder::new();
+ Box::into_raw(Box::new(pb))
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_move_to(pb: &mut PathBuilder, x: f32, y: f32) {
+ pb.move_to(x, y);
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_line_to(pb: &mut PathBuilder, x: f32, y: f32) {
+ pb.line_to(x, y);
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_curve_to(pb: &mut PathBuilder, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) {
+ pb.curve_to(c1x, c1y, c2x, c2y, x, y);
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_quad_to(pb: &mut PathBuilder, cx: f32, cy: f32, x: f32, y: f32) {
+ pb.quad_to(cx, cy, x, y);
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_close(pb: &mut PathBuilder) {
+ pb.close();
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_set_fill_mode(pb: &mut PathBuilder, fill_mode: FillMode) {
+ pb.set_fill_mode(fill_mode)
+}
+
+#[repr(C)]
+pub struct Path {
+ fill_mode: FillMode,
+ points: *const POINT,
+ num_points: usize,
+ types: *const BYTE,
+ num_types: usize,
+}
+
+impl From<OutputPath> for Path {
+ fn from(output_path: OutputPath) -> Self {
+ let path = Self {
+ fill_mode: output_path.fill_mode,
+ points: output_path.points.as_ptr(),
+ num_points: output_path.points.len(),
+ types: output_path.types.as_ptr(),
+ num_types: output_path.types.len(),
+ };
+ std::mem::forget(output_path);
+ path
+ }
+}
+
+impl Into<OutputPath> for Path {
+ fn into(self) -> OutputPath {
+ OutputPath {
+ fill_mode: self.fill_mode,
+ points: unsafe {
+ if self.points == std::ptr::null() {
+ Default::default()
+ } else {
+ Box::from_raw(std::slice::from_raw_parts_mut(self.points as *mut POINT, self.num_points))
+ }
+ },
+ types: unsafe {
+ if self.types == std::ptr::null() {
+ Default::default()
+ } else {
+ Box::from_raw(std::slice::from_raw_parts_mut(self.types as *mut BYTE, self.num_types))
+ }
+ },
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_builder_get_path(pb: &mut PathBuilder) -> Path {
+ Path::from(pb.get_path().unwrap_or_default())
+}
+
+#[repr(C)]
+pub struct VertexBuffer {
+ data: *const OutputVertex,
+ len: usize
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_path_rasterize_to_tri_list(
+ path: &Path,
+ clip_x: i32,
+ clip_y: i32,
+ clip_width: i32,
+ clip_height: i32,
+ need_inside: bool,
+ need_outside: bool,
+ rasterization_truncates: bool,
+ output_ptr: *mut OutputVertex,
+ output_capacity: usize,
+) -> VertexBuffer {
+ let output_buffer = if output_ptr != std::ptr::null_mut() {
+ unsafe { Some(std::slice::from_raw_parts_mut(output_ptr, output_capacity)) }
+ } else {
+ None
+ };
+ let mut result = rasterize_to_tri_list(
+ path.fill_mode,
+ unsafe { std::slice::from_raw_parts(path.types, path.num_types) },
+ unsafe { std::slice::from_raw_parts(path.points, path.num_points) },
+ clip_x, clip_y, clip_width, clip_height,
+ need_inside, need_outside,
+ rasterization_truncates,
+ output_buffer
+ );
+ if let Some(output_buffer_size) = result.get_output_buffer_size() {
+ VertexBuffer {
+ data: std::ptr::null(),
+ len: output_buffer_size,
+ }
+ } else {
+ let slice = result.flush_output();
+ let vb = VertexBuffer {
+ data: slice.as_ptr(),
+ len: slice.len(),
+ };
+ std::mem::forget(slice);
+ vb
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_path_release(path: Path) {
+ let output_path: OutputPath = path.into();
+ drop(output_path);
+}
+
+#[no_mangle]
+pub extern "C" fn wgr_vertex_buffer_release(vb: VertexBuffer)
+{
+ if vb.data != std::ptr::null() {
+ unsafe {
+ drop(Box::from_raw(std::slice::from_raw_parts_mut(vb.data as *mut OutputVertex, vb.len)));
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wgr_builder_release(pb: *mut PathBuilder) {
+ drop(Box::from_raw(pb));
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/fix.rs b/third_party/rust/wpf-gpu-raster/src/fix.rs
new file mode 100644
index 0000000000..bf4741e260
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/fix.rs
@@ -0,0 +1,9 @@
+use crate::types::*;
+type FIX4 = INT; // 28.4 fixed point value
+
+// constants for working with 28.4 fixed point values
+macro_rules! FIX4_SHIFT { () => { 4 } }
+macro_rules! FIX4_PRECISION { () => { 4 } }
+macro_rules! FIX4_ONE { () => { (1 << FIX4_PRECISION!()) } }
+macro_rules! FIX4_HALF { () => { (1 << (FIX4_PRECISION!()-1)) } }
+macro_rules! FIX4_MASK { () => { (FIX4_ONE!() - 1) } } \ No newline at end of file
diff --git a/third_party/rust/wpf-gpu-raster/src/geometry_sink.rs b/third_party/rust/wpf-gpu-raster/src/geometry_sink.rs
new file mode 100644
index 0000000000..3282f10a15
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/geometry_sink.rs
@@ -0,0 +1,92 @@
+use crate::aacoverage::CCoverageInterval;
+use crate::nullable_ref::Ref;
+use crate::types::*;
+
+pub trait IGeometrySink
+{
+ //
+ // Aliased geometry output
+ //
+/*
+ virtual HRESULT AddVertex(
+ __in_ecount(1) const MilPoint2F &ptPosition,
+ // In: Vertex coordinates
+ __out_ecount(1) WORD *pidxOut
+ // Out: Index of vertex
+ ) PURE;
+
+ virtual HRESULT AddIndexedVertices(
+ UINT cVertices,
+ // In: number of vertices
+ __in_bcount(cVertices*uVertexStride) const void *pVertexBuffer,
+ // In: vertex buffer containing the vertices
+ UINT uVertexStride,
+ // In: size of each vertex
+ MilVertexFormat mvfFormat,
+ // In: format of each vertex
+ UINT cIndices,
+ // In: Number of indices
+ __in_ecount(cIndices) const UINT *puIndexBuffer
+ // In: index buffer
+ ) PURE;
+
+ virtual void SetTransformMapping(
+ __in_ecount(1) const MILMatrix3x2 &mat2DTransform
+ ) PURE;
+
+ virtual HRESULT AddTriangle(
+ DWORD idx1,
+ // In: Index of triangle's first vertex
+ DWORD idx2,
+ // In: Index of triangle's second vertex
+ DWORD idx3
+ // In: Index of triangle's third vertex
+ ) PURE;
+
+ //
+ // Trapezoidal AA geometry output
+ //
+*/
+ fn AddComplexScan(&mut self,
+ nPixelY: INT,
+ // In: y coordinate in pixel space
+ pIntervalSpanStart: Ref<CCoverageInterval>
+ // In: coverage segments
+ ) -> HRESULT;
+
+ fn AddTrapezoid(
+ &mut self,
+ rYMin: f32,
+ // In: y coordinate of top of trapezoid
+ rXLeftYMin: f32,
+ // In: x coordinate for top left
+ rXRightYMin: f32,
+ // In: x coordinate for top right
+ rYMax: f32,
+ // In: y coordinate of bottom of trapezoid
+ rXLeftYMax: f32,
+ // In: x coordinate for bottom left
+ rXRightYMax: f32,
+ // In: x coordinate for bottom right
+ rXDeltaLeft: f32,
+ // In: trapezoid expand radius
+ rXDeltaRight: f32
+ // In: trapezoid expand radius
+ ) -> HRESULT;
+
+ fn IsEmpty(&self) -> bool;
+ /*
+ virtual HRESULT AddParallelogram(
+ __in_ecount(4) const MilPoint2F *rgPosition
+ ) PURE;
+
+ //
+ // Query sink status
+ //
+
+ // Some geometry generators don't actually know if they have output
+ // any triangles, so they need to get this information from the geometry sink.
+
+ virtual BOOL IsEmpty() PURE;
+*/
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/helpers.rs b/third_party/rust/wpf-gpu-raster/src/helpers.rs
new file mode 100644
index 0000000000..27a594831e
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/helpers.rs
@@ -0,0 +1,55 @@
+pub fn Int32x32To64(a: i32, b: i32) -> i64 { a as i64 * b as i64 }
+
+macro_rules! IsTagEnabled {
+ ($e: expr) => {
+ false
+ }
+}
+
+macro_rules! TraceTag {
+ (($e: expr, $s: expr)) => {
+ dbg!($s)
+ }
+}
+
+macro_rules! IFC {
+ ($e: expr) => {
+ assert_eq!($e, S_OK);
+ }
+}
+
+macro_rules! IFR {
+ ($e: expr) => {
+ let hresult = $e;
+ if (hresult != S_OK) { return hresult }
+ }
+}
+
+macro_rules! __analysis_assume {
+ ($e: expr) => {
+ }
+}
+
+macro_rules! IFCOOM {
+ ($e: expr) => {
+ assert_ne!($e, NULL());
+ }
+}
+
+macro_rules! RRETURN1 {
+ ($e: expr, $s1: expr) => {
+ if $e == $s1 {
+ } else {
+ assert_eq!($e, S_OK);
+ }
+ return $e;
+ }
+}
+
+macro_rules! RRETURN {
+ ($e: expr) => {
+ assert_eq!($e, S_OK);
+ return $e;
+ }
+}
+
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:
+//
+// <HungarianType><CoordinateSystem>[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<MilPoint2F> *m_prgPoints;
+DynArray<BYTE> *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<CInactiveEdge>;
+ let mut edgeHead: CEdge = Default::default();
+ let mut edgeTail: CEdge = Default::default();
+ let pEdgeActiveList: Ref<CEdge>;
+ 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<CMatrix<CoordinateSpace::Shape,CoordinateSpace::Device>>,
+ 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<CCoverageInterval> = 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<CEdge>,
+ nSubpixelYCurrent: INT,
+ nSubpixelYNextInactive: INT
+ ) -> INT
+{
+
+ let mut nSubpixelYBottomTrapezoids;
+ let mut pEdgeLeft: Ref<CEdge>;
+ let mut pEdgeRight: Ref<CEdge>;
+
+ //
+ // 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<CEdge>,
+ 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<CEdge>;
+ let mut pEdgeCurrent: Ref<CEdge>;
+ 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);
+}
+
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/hwvertexbuffer.rs b/third_party/rust/wpf-gpu-raster/src/hwvertexbuffer.rs
new file mode 100644
index 0000000000..6b01b5fdb2
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/hwvertexbuffer.rs
@@ -0,0 +1,3075 @@
+// 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.
+
+
+//-----------------------------------------------------------------------------
+//
+
+//
+// Description:
+// Contains HW Vertex Buffer and Builder class implementations
+//
+//
+// Notes:
+//
+// +--------------------------------------+
+// | |
+// | Start Stratum |
+// 1 | |
+// | |
+// +--------------------------------------+
+// 2 |======================================|
+// +--------------------------------------+
+// | / \ / \ |
+// | / \ / \ |
+// | A / B \ C / D \ E |
+// 3 | / \ / \ |
+// | / \ / \ |
+// | / \ / \ |
+// | / \ / \ |
+// +--------------------------------------+
+// | \ / \ / |
+// | \ / \ / |
+// 4 | F \ G / H \ I / J |
+// | \ / \ / |
+// +--------------------------------------+
+// 5 |======================================|
+// +--------------------------------------+
+// 6 |======================================|
+// +--------------------------------------+
+// | |
+// | |
+// 7 | Stop Stratum |
+// | |
+// | |
+// +--------------------------------------+
+//
+//
+// Strata & complement mode.
+//
+// The anti-aliased HW rasterizer produces a series of "strata" where
+// each strata can be a complex span rendered using lines (#'s 2,5,6) or
+// a series of trapezoids (#'s 3 & 4.) In normal mode the trapezoid
+// regions B,D,G,I are filled in.
+//
+// Complement mode complicates things. Complex spans are relatively easy
+// because we get the whole line's worth of data at once. Trapezoids are
+// more complex because we get B,D,G and I separately. We handle this by
+// tracking the current stratum and finishing the last incomplete
+// trapezoid stratum when a new stratum begins. Regions E & J finish
+// trapezoid strata. We also need to add rectangles at the beginning and
+// end of the geometry (start and stop) to fill out the complement
+// region.
+//
+// This is implemented like so:
+//
+// 1. Strata are generated from top to bottom without gaps.
+// 2. Before drawing any lines or trapezoids call
+// PrepareStratum(a, b, fTrapezoid) where a & b are the extent of
+// the current stratum and fTrapezoid is true if you are drawing
+// a trapezoid. This will take care of creating the start
+// stratum and/or finishing a trapezoid stratum if necessary.
+// 3. When completely done call EndBuildingOutside() which will
+// close a pending trapezoid and/or produce the stop stratum.
+//
+//-----------------------------------------------------------------------------
+
+const FORCE_TRIANGLES: bool = true;
+
+//+----------------------------------------------------------------------------
+//
+// Constants to control when we stop waffling because the tiles are too
+// small to make a difference.
+//
+// Future Consideration: can produce an excessive number of triangles.
+// How we mitigate or handle this could be improved. Right now we stop
+// waffling if the waffle size is less than a quarter-pixel.
+// Two big improvements that could be made are:
+// - multipacking very small textures (but note that we cannot rely
+// on prefiltering to ensure that small screen space means small texture
+// source)
+// - clipping primitives to approximately the screen size
+//
+//-----------------------------------------------------------------------------
+//const c_rMinWaffleWidthPixels: f32 = 0.25;
+
+
+const FLOAT_ZERO: f32 = 0.;
+const FLOAT_ONE: f32 = 1.;
+
+//+----------------------------------------------------------------------------
+//
+// Class: CHwVertexBuffer and CHwTVertexBuffer<class TVertex>
+//
+// Synopsis: This class accumulates geometry data for a primitive
+//
+//-----------------------------------------------------------------------------
+
+use crate::{types::*, geometry_sink::IGeometrySink, aacoverage::c_nShiftSizeSquared, OutputVertex, nullable_ref::Ref};
+
+
+//+----------------------------------------------------------------------------
+//
+// Class: CHwVertexBuffer::Builder
+//
+// Synopsis: Base vertex builder class
+//
+// Responsibilities:
+// - Given ordered basic vertex information expand/convert/pass-thru
+// to vertex buffer (Basic vertex information is minimal vertex
+// information sent from the caller that may or may not have been
+// passed thru a tessellator.)
+// - Choosing vertex format from a minimal required vertex format
+//
+// Not responsible for:
+// - Allocating space in vertex buffer
+//
+// Inputs required:
+// - Key and data to translate input basic vertex info to full vertex data
+// - Vertex info from tessellation (or other Geometry Generator)
+// - Vertex buffer to send output to
+//
+
+/*pub struct CHwVertexBufferBuilder /* : public IGeometrySink */
+{
+ /*
+public:
+
+ static HRESULT Create(
+ MilVertexFormat vfIn,
+ MilVertexFormat vfOut,
+ MilVertexFormatAttribute vfaAntiAliasScaleLocation,
+ __in_ecount_opt(1) CHwPipeline *pPipeline,
+ __in_ecount_opt(1) CD3DDeviceLevel1 *pDevice,
+ __in_ecount(1) CBufferDispenser *pBufferDispenser,
+ __deref_out_ecount(1) CHwVertexBuffer::Builder **ppVertexBufferBuilder
+ );
+
+ virtual ~Builder()
+ {
+#if DBG
+ Assert(!m_fDbgDestroyed);
+ m_fDbgDestroyed = true;
+#endif DBG
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: SetConstantMapping
+ //
+ // Synopsis: Use this method to specify that the given color source for
+ // the given vertex destination is constant (won't differ per
+ // vertex)
+ //
+ //-------------------------------------------------------------------------
+
+ virtual HRESULT SetConstantMapping(
+ MilVertexFormatAttribute mvfaDestination,
+ __in_ecount(1) const CHwConstantColorSource *pConstCS
+ ) PURE;
+
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: FinalizeMappings
+ //
+ // Synopsis: Use this method to let builder know that all mappings have
+ // been sent
+ //
+ //-------------------------------------------------------------------------
+
+ virtual HRESULT FinalizeMappings(
+ ) PURE;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: SetOutsideBounds
+ //
+ // Synopsis: Enables rendering zero-alpha geometry outside of the input
+ // shape but within the given bounding rectangle, if fNeedInside
+ // isn't true then it doesn't render geometry with full alpha.
+ //
+ //-------------------------------------------------------------------------
+ virtual void SetOutsideBounds(
+ __in_ecount_opt(1) const CMILSurfaceRect *prcBounds,
+ bool fNeedInside
+ ) PURE;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: HasOutsideBounds
+ //
+ // Synopsis: Returns true if outside bounds have been set.
+ //
+ //-------------------------------------------------------------------------
+ virtual bool HasOutsideBounds() const PURE;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: BeginBuilding
+ //
+ // Synopsis: This method lets the builder know it should start from a
+ // clean slate
+ //
+ //-------------------------------------------------------------------------
+
+ virtual HRESULT BeginBuilding(
+ ) PURE;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: EndBuilding
+ //
+ // Synopsis: Use this method to let the builder know that all of the
+ // vertex data has been sent
+ //
+ //-------------------------------------------------------------------------
+
+ virtual HRESULT EndBuilding(
+ __deref_opt_out_ecount(1) CHwVertexBuffer **ppVertexBuffer
+ ) PURE;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: FlushReset
+ //
+ // Synopsis: Send pending state and geometry to the device and reset
+ // the vertex buffer.
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE HRESULT FlushReset()
+ {
+ return FlushInternal(NULL);
+ }
+
+ //
+ // Currently all CHwVertexBuffer::Builder are supposed to be allocated via
+ // a CBufferDispenser.
+ //
+
+ DECLARE_BUFFERDISPENSER_DELETE
+
+protected:
+
+ Builder()
+ {
+ m_mvfIn = MILVFAttrNone;
+
+#if DBG
+ m_mvfDbgOut = MILVFAttrNone;
+#endif
+
+ m_mvfaAntiAliasScaleLocation = MILVFAttrNone;
+
+ m_pPipelineNoRef = NULL;
+ m_pDeviceNoRef = NULL;
+
+#if DBG
+ m_fDbgDestroyed = false;
+#endif DBG
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: FlushInternal
+ //
+ // Synopsis: Send any pending state and geometry to the device.
+ // If the optional argument is NULL then reset the
+ // vertex buffer.
+ // If the optional argument is non-NULL AND we have
+ // not yet flushed the vertex buffer return the vertex
+ // buffer.
+ //
+ //-------------------------------------------------------------------------
+
+ virtual HRESULT FlushInternal(
+ __deref_opt_out_ecount_opt(1) CHwVertexBuffer **ppVertexBuffer
+ ) PURE;
+
+
+ CHwPipeline *m_pPipelineNoRef;
+ CD3DDeviceLevel1 *m_pDeviceNoRef;
+
+ MilVertexFormat m_mvfIn; // Vertex fields that are pre-generated
+
+#if DBG
+ MilVertexFormat m_mvfDbgOut; // Output format of the vertex
+#endif
+
+ MilVertexFormat m_mvfGenerated; // Vertex fields that are dynamically
+ // generated by this builder
+
+ MilVertexFormatAttribute m_mvfaAntiAliasScaleLocation; // Vertex field that
+ // contains PPAA
+ // falloff factor
+
+#if DBG
+private:
+
+ bool m_fDbgDestroyed; // Used to check single Release pattern
+
+#endif DBG
+*/
+}*/
+#[derive(Default)]
+pub struct CD3DVertexXYZDUV2 {
+ x: f32,
+ y: f32,
+ //Z: f32,
+ coverage: f32,
+ /*U0: f32, V0: f32,
+ U1: f32, V1: f32,*/
+}
+pub type CHwVertexBuffer<'z> = CHwTVertexBuffer<'z, OutputVertex>;
+#[derive(Default)]
+pub struct CHwTVertexBuffer<'z, TVertex>
+{
+ //m_rgIndices: DynArray<WORD>, // Dynamic array of indices
+
+
+ //m_pBuilder: Rc<CHwTVertexBufferBuilder<TVertex>>,
+
+ /*
+#if DBG
+public:
+
+ CHwTVertexBuffer()
+ {
+ m_fDbgNonLineSegmentTriangleStrip = false;
+ }
+#endif
+
+protected:
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: Reset
+ //
+ // Synopsis: Mark the beginning of a new list of vertices; the existing
+ // list is discarded
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE void Reset(
+ __in_ecount(1) Builder *pVBB
+ )
+ {
+#if DBG
+ m_fDbgNonLineSegmentTriangleStrip = false;
+#endif
+ m_rgIndices.SetCount(0);
+ m_rgVerticesTriList.SetCount(0);
+ m_rgVerticesTriStrip.SetCount(0);
+ m_rgVerticesLineList.SetCount(0);
+ m_rgVerticesNonIndexedTriList.SetCount(0);
+
+ m_pBuilder = pVBB;
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddNonIndexedTriListVertices
+ //
+ // Synopsis: Reserve space for consecutive vertices and return start
+ // index
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE HRESULT AddNonIndexedTriListVertices(
+ UINT uCount,
+ __deref_ecount(uCount) TVertex **ppVertices
+ );
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddTriListVertices
+ //
+ // Synopsis: Reserve space for consecutive vertices and return start
+ // index
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE HRESULT AddTriListVertices(
+ UINT uDelta,
+ __deref_ecount(uDelta) TVertex **ppVertices,
+ __out_ecount(1) WORD *pwIndexStart
+ );
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddTriStripVertices
+ //
+ // Synopsis: Reserve space for consecutive vertices and return start
+ // index
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE HRESULT AddTriStripVertices(
+ UINT uCount,
+ __deref_ecount(uCount) TVertex **ppVertices
+ );
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddLineListVertices
+ //
+ // Synopsis: Reserve space for consecutive vertices and return start
+ // index
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE HRESULT AddLineListVertices(
+ UINT uCount,
+ __deref_ecount(uCount) TVertex **ppVertices
+ );
+
+public:
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddLine implements ILineSink<PointXYA>
+ //
+ // Synopsis: Add a line given two points with x, y, & alpha.
+ //
+ //-------------------------------------------------------------------------
+ HRESULT AddLine(
+ __in_ecount(1) const PointXYA &v0,
+ __in_ecount(1) const PointXYA &v1
+ );
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddTriangle implements ITriangleSink<PointXYA>
+ //
+ // Synopsis: Add a triangle given three points with x, y, & alpha.
+ //
+ //-------------------------------------------------------------------------
+
+ HRESULT AddTriangle(
+ __in_ecount(1) const PointXYA &v0,
+ __in_ecount(1) const PointXYA &v1,
+ __in_ecount(1) const PointXYA &v2
+ );
+
+ // Re-introduce parent AddTriangle(WORD,WORD,WORD) into this scope.
+ using CHwVertexBuffer::AddTriangle;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: AddLineAsTriangleStrip
+ //
+ // Synopsis: Add a horizontal line using a trinagle strip
+ //
+ //-------------------------------------------------------------------------
+ HRESULT AddLineAsTriangleStrip(
+ __in_ecount(1) const TVertex *pBegin, // Begin
+ __in_ecount(1) const TVertex *pEnd // End
+ );
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: SendVertexFormat
+ //
+ // Synopsis: Send contained vertex format to device
+ //
+ //-------------------------------------------------------------------------
+
+ HRESULT SendVertexFormat(
+ __inout_ecount(1) CD3DDeviceLevel1 *pDevice
+ ) const;
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: DrawPrimitive
+ //
+ // Synopsis: Send the geometry data to the device and execute rendering
+ //
+ //-------------------------------------------------------------------------
+
+ HRESULT DrawPrimitive(
+ __inout_ecount(1) CD3DDeviceLevel1 *pDevice
+ ) const;
+
+protected:
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetNumTriListVertices
+ //
+ // Synopsis: Return current number of vertices
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE DWORD GetNumTriListVertices() const
+ {
+ return m_rgVerticesTriList.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetTriListVertices
+ //
+ // Synopsis: Return pointer to beginning of vertex list and their count
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE void GetTriListVertices(
+ __deref_out_ecount_full(*puNumVertices) TVertex **ppVertices,
+ __out_ecount(1) UINT * puNumVertices
+ )
+ {
+ *ppVertices = m_rgVerticesTriList.GetDataBuffer();
+ *puNumVertices = m_rgVerticesTriList.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetNumNonIndexedTriListVertices
+ //
+ // Synopsis: Return current number of vertices
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE DWORD GetNumNonIndexedTriListVertices() const
+ {
+ return m_rgVerticesNonIndexedTriList.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetNonIndexedTriListVertices
+ //
+ // Synopsis: Return pointer to beginning of vertex list and their count
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE void GetNonIndexedTriListVertices(
+ __deref_out_ecount_full(*puNumVertices) TVertex **ppVertices,
+ __out_ecount(1) UINT * puNumVertices
+ )
+ {
+ *ppVertices = m_rgVerticesNonIndexedTriList.GetDataBuffer();
+ *puNumVertices = m_rgVerticesNonIndexedTriList.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetNumTriStripVertices
+ //
+ // Synopsis: Return current number of vertices
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE DWORD GetNumTriStripVertices() const
+ {
+ return m_rgVerticesTriStrip.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetTriStripVertices
+ //
+ // Synopsis: Return pointer to beginning of vertex list and their count
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE void GetTriStripVertices(
+ __deref_out_ecount_full(*puNumVertices) TVertex **ppVertices,
+ __out_ecount(1) UINT *puNumVertices
+ )
+ {
+ *ppVertices = m_rgVerticesTriStrip.GetDataBuffer();
+ *puNumVertices = m_rgVerticesTriStrip.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetNumLineListVertices
+ //
+ // Synopsis: Return current number of vertices
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE DWORD GetNumLineListVertices() const
+ {
+ return m_rgVerticesLineList.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetLineListVertices
+ //
+ // Synopsis: Return pointer to beginning of vertex list and their count
+ //
+ //-------------------------------------------------------------------------
+
+ MIL_FORCEINLINE void GetLineListVertices(
+ __deref_out_ecount_full(*puNumVertices) TVertex **ppVertices,
+ __out_ecount(1) UINT * puNumVertices
+ )
+ {
+ *ppVertices = m_rgVerticesLineList.GetDataBuffer();
+ *puNumVertices = m_rgVerticesLineList.GetCount();
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: GetLineListVertices
+ //
+ // Synopsis: Return pointer to beginning of vertex list
+ //
+ //-------------------------------------------------------------------------
+
+
+
+*/
+
+ // Dynamic array of vertices for which all allocations are zeroed.
+ // XXX: the zero has been removed
+ //m_rgVerticesTriList: DynArray<TVertex>, // Indexed triangle list vertices
+ //m_rgVerticesNonIndexedTriList: DynArray<TVertex>, // Non-indexed triangle list vertices
+ m_rgVerticesTriList: DynArray<TVertex>, // Triangle strip vertices
+ //m_rgVerticesLineList: DynArray<TVertex>, // Linelist vertices
+
+ m_rgVerticesBuffer: Option<&'z mut [TVertex]>,
+ m_rgVerticesBufferOffset: usize,
+
+ #[cfg(debug_assertions)]
+ // In debug make a note if we add a triangle strip that doesn't have 6 vertices
+ // so that we can ensure that we only waffle 6-vertex tri strips.
+ m_fDbgNonLineSegmentTriangleStrip: bool,
+ subpixel_bias: f32,
+}
+
+impl<'z, TVertex: Default> CHwTVertexBuffer<'z, TVertex> {
+ pub fn new(rasterization_truncates: bool, output_buffer: Option<&'z mut [TVertex]>) -> Self {
+ CHwTVertexBuffer::<TVertex> {
+ subpixel_bias: if rasterization_truncates {
+ // 1/512 is 0.5 of a subpixel when using 8 bits of subpixel precision.
+ 1./512.
+ } else {
+ 0.
+ },
+ m_rgVerticesBuffer: output_buffer,
+ m_rgVerticesBufferOffset: 0,
+ ..Default::default()
+ }
+ }
+
+ pub fn flush_output(&mut self) -> Box<[TVertex]> {
+ std::mem::take(&mut self.m_rgVerticesTriList).into_boxed_slice()
+ }
+
+ pub fn get_output_buffer_size(&self) -> Option<usize> {
+ if self.m_rgVerticesBuffer.is_some() {
+ Some(self.m_rgVerticesBufferOffset)
+ } else {
+ None
+ }
+ }
+}
+
+//+----------------------------------------------------------------------------
+//
+// Class: CHwTVertexMappings<class TVertex>
+//
+// Synopsis: Helper class that knows how to populate a vertex from the
+// incoming basic per vertex data, like just X and Y
+//
+//-----------------------------------------------------------------------------
+#[derive(Default)]
+struct CHwTVertexMappings<TVertex>
+{/*
+public:
+
+ CHwTVertexMappings();
+
+ void SetPositionTransform(
+ __in_ecount(1) const MILMatrix3x2 &matPositionTransform
+ );
+
+ HRESULT SetConstantMapping(
+ MilVertexFormatAttribute mvfaDestination,
+ __in_ecount(1) const CHwConstantColorSource *pConstCS
+ );
+
+ void PointToUV(
+ __in_ecount(1) const MilPoint2F &ptIn,
+ __bound UINT uIndex,
+ __out_ecount(1) TVertex *pvOut
+ );
+
+ MIL_FORCEINLINE bool AreWaffling() const
+ {
+ return false;
+ }
+
+private:
+ static const size_t s_numOfVertexTextureCoords
+ = NUM_OF_VERTEX_TEXTURE_COORDS(TVertex);
+public:
+
+ MilVertexFormat m_mvfMapped;
+
+ MilColorF m_colorStatic;
+
+ MILMatrix3x2 m_matPos2DTransform;
+
+ MILMatrix3x2 m_rgmatPointToUV[s_numOfVertexTextureCoords];
+ CMilPointAndSizeF m_rgSubrect[s_numOfVertexTextureCoords];
+ WaffleModeFlags m_rgWaffleMode[s_numOfVertexTextureCoords];
+
+*/
+ m_vStatic: TVertex,
+ subpixel_bias: f32,
+}
+
+impl<TVertex> CHwTVertexBuffer<'_, TVertex> {
+ pub fn Reset(&mut self,
+ /*pVBB: &mut CHwTVertexBufferBuilder<TVertex>*/
+ )
+ {
+ #[cfg(debug_assertions)]
+ {
+ self.m_fDbgNonLineSegmentTriangleStrip = false;
+ }
+
+ //self.m_rgIndices.SetCount(0);
+ //self.m_rgVerticesTriList.SetCount(0);
+ self.m_rgVerticesTriList.SetCount(0);
+ self.m_rgVerticesBufferOffset = 0;
+ //self.m_rgVerticesLineList.SetCount(0);
+ //self.m_rgVerticesNonIndexedTriList.SetCount(0);
+
+ //self.m_pBuilder = pVBB;
+ }
+
+ fn IsEmpty(&self) -> bool
+ {
+ return true
+ // && (self.m_rgIndices.GetCount() == 0)
+ //&& (self.m_rgVerticesLineList.GetCount() == 0)
+ && (self.m_rgVerticesTriList.GetCount() == 0)
+ && self.m_rgVerticesBufferOffset == 0
+ //&& (self.m_rgVerticesNonIndexedTriList.GetCount() == 0);
+ }
+
+}
+
+//+----------------------------------------------------------------------------
+//
+// Class: CHwTVertexBuffer<class TVertex>::Builder
+//
+// Synopsis: Implements CHwVertexBuffer::Builder for a particular vertex
+// format
+//
+//-----------------------------------------------------------------------------
+
+pub struct CHwTVertexBufferBuilder<'y, 'z, TVertex>
+{
+ m_mvfIn: MilVertexFormat, // Vertex fields that are pre-generated
+
+ #[cfg(debug_assertions)]
+ m_mvfDbgOut: MilVertexFormat, // Output format of the vertex
+
+ m_mvfGenerated: MilVertexFormat, // Vertex fields that are dyn
+
+ m_mvfaAntiAliasScaleLocation: MilVertexFormatAttribute, // Vertex field that
+ // contains PPAA
+ // falloff factor
+
+ /*
+public:
+
+ static MilVertexFormat GetOutVertexFormat();
+
+ static HRESULT Create(
+ __in_ecount(1) CHwTVertexBuffer<TVertex> *pVertexBuffer,
+ MilVertexFormat mvfIn,
+ MilVertexFormat mvfOut,
+ MilVertexFormatAttribute mvfaAntiAliasScaleLocation,
+ __inout_ecount(1) CBufferDispenser *pBufferDispenser,
+ __deref_out_ecount(1) typename CHwTVertexBuffer<TVertex>::Builder **ppVertexBufferBuilder
+ );
+
+ HRESULT SetConstantMapping(
+ MilVertexFormatAttribute mvfaDestination,
+ __in_ecount(1) const CHwConstantColorSource *pConstCS
+ );
+
+ void SetTransformMapping(
+ __in_ecount(1) const MILMatrix3x2 &mat2DTransform
+ );
+
+ HRESULT FinalizeMappings(
+ );
+
+ void SetOutsideBounds(
+ __in_ecount_opt(1) const CMILSurfaceRect *prcBounds,
+ bool fNeedInside
+ );
+
+ bool HasOutsideBounds() const
+ {
+ return NeedOutsideGeometry();
+ }
+
+ HRESULT BeginBuilding(
+ );
+
+ HRESULT AddVertex(
+ __in_ecount(1) const MilPoint2F &ptPosition,
+ // In: Vertex coordinates
+ __out_ecount(1) WORD *pIndex
+ // Out: The index of the new vertex
+ );
+
+ HRESULT AddIndexedVertices(
+ UINT cVertices, // In: number of vertices
+ __in_bcount(cVertices*uVertexStride) const void *pVertexBuffer, // In: vertex buffer containing the vertices
+ UINT uVertexStride, // In: size of each vertex
+ MilVertexFormat mvfFormat, // In: format of each vertex
+ UINT cIndices, // In: Number of indices
+ __in_ecount(cIndices) const UINT *puIndexBuffer // In: index buffer
+ );
+
+ HRESULT AddTriangle(
+ DWORD i1, // In: Index of triangle's first vertex
+ DWORD i2, // In: Index of triangle's second vertex
+ DWORD i3 // In: Index of triangle's third vertex
+ );
+
+ HRESULT AddComplexScan(
+ INT nPixelY,
+ // In: y coordinate in pixel space
+ __in_ecount(1) const CCoverageInterval *pIntervalSpanStart
+ // In: coverage segments
+ );
+
+ HRESULT AddParallelogram(
+ __in_ecount(4) const MilPoint2F *rgPosition
+ );
+
+ HRESULT AddTrapezoid(
+ float rPixelYTop, // In: y coordinate of top of trapezoid
+ float rPixelXTopLeft, // In: x coordinate for top left
+ float rPixelXTopRight, // In: x coordinate for top right
+ float rPixelYBottom, // In: y coordinate of bottom of trapezoid
+ float rPixelXBottomLeft, // In: x coordinate for bottom left
+ float rPixelXBottomRight, // In: x coordinate for bottom right
+ float rPixelXLeftDelta, // In: trapezoid expand radius for left edge
+ float rPixelXRightDelta // In: trapezoid expand radius for right edge
+ );
+
+ BOOL IsEmpty();
+
+ HRESULT EndBuilding(
+ __deref_opt_out_ecount(1) CHwVertexBuffer **ppVertexBuffer
+ );
+
+ HRESULT FlushInternal(
+ __deref_opt_out_ecount_opt(1) CHwVertexBuffer **ppVertexBuffer
+ );
+
+private:
+
+ // Helpers that do AddTrapezoid. Same parameters
+ HRESULT AddTrapezoidStandard( float, float, float, float, float, float, float, float );
+ HRESULT AddTrapezoidWaffle( float, float, float, float, float, float, float, float );
+
+
+
+ HRESULT PrepareStratumSlow(
+ float rStratumTop,
+ float rStratumBottom,
+ bool fTrapezoid,
+ float rTrapezoidLeft,
+ float rTrapezoidRight
+ );
+
+ // Wrap up building of outside geometry.
+ HRESULT EndBuildingOutside();
+
+ DECLARE_BUFFERDISPENSER_NEW(CHwTVertexBuffer<TVertex>::Builder,
+ Mt(CHwTVertexBuffer_Builder));
+
+ Builder(
+ __in_ecount(1) CHwTVertexBuffer<TVertex> *pVertexBuffer
+ );
+
+ HRESULT SetupConverter(
+ MilVertexFormat mvfIn,
+ MilVertexFormat mvfOut,
+ MilVertexFormatAttribute mvfaAntiAliasScaleLocation
+ );
+
+ HRESULT RenderPrecomputedIndexedTriangles(
+ __range(1, SHORT_MAX) UINT cVertices,
+ __in_ecount(cVertices) const TVertex *rgoVertices,
+ __range(1, UINT_MAX) UINT cIndices,
+ __in_ecount(cIndices) const UINT *rguIndices
+ );
+
+
+ // Expands all vertices in the buffer.
+ void ExpandVertices();
+
+ // Has never been successfully used to declare a method or derived type...
+/* typedef void (CHwTVertexBuffer<TVertex>::Builder::FN_ExpandVertices)(
+ UINT uCount,
+ TVertex *pVertex
+ );*/
+
+ // error C2143: syntax error : missing ';' before '*'
+// typedef FN_ExpandVertices *PFN_ExpandVertices;
+
+ typedef void (CHwTVertexBuffer<TVertex>::Builder::* PFN_ExpandVertices)(
+ __range(1,UINT_MAX) UINT uCount,
+ __inout_ecount_full(uCount) TVertex *rgVertices
+ );
+
+ //
+ // Table of vertex expansion routines for common expansion cases:
+ // - There are entries for Z, Diffuse, and one set texture coordinates for
+ // a total of eight combinations.
+ // - Additionally there is a second set of entries for anti-aliasing
+ // falloff applied thru diffuse.
+ //
+
+ static const PFN_ExpandVertices sc_pfnExpandVerticesTable[8*2];
+
+ MIL_FORCEINLINE
+ void TransferAndOrExpandVerticesInline(
+ __range(1,UINT_MAX) UINT uCount,
+ __in_ecount(uCount) TVertex const * rgInputVertices,
+ __out_ecount(uCount) TVertex *rgOutputVertices,
+ MilVertexFormat mvfOut,
+ MilVertexFormatAttribute mvfaScaleByFalloff,
+ bool fInputOutputAreSameBuffer,
+ bool fTransformPosition
+ );
+
+ // FN_ExpandVertices ExpandVerticesFast
+ template <MilVertexFormat mvfOut, MilVertexFormatAttribute mvfaScaleByFalloff>
+ void ExpandVerticesFast(
+ __range(1,UINT_MAX) UINT uCount,
+ __inout_ecount_full(uCount) TVertex *rgVertices
+ )
+ {
+ TransferAndOrExpandVerticesInline(
+ uCount,
+ rgVertices,
+ rgVertices,
+ mvfOut,
+ mvfaScaleByFalloff,
+ true, // => fInputOutputAreSameBuffer
+ false // => fTransformPosition
+ );
+ }
+
+ // error C2146: syntax error : missing ';' before identifier 'ExpandVerticesGeneral'
+ // error C2501: 'CHwTVertexBufferBuilder<TVertex>::FN_ExpandVertices' : missing storage-class or type specifiers
+// FN_ExpandVertices ExpandVerticesGeneral
+// typename FN_ExpandVertices ExpandVerticesGeneral
+ // error C4346: 'CHwTVertexBufferBuilder<TVertex>::FN_ExpandVertices' : dependent name is not a type
+// CHwTVertexBufferBuilder<TVertex>::FN_ExpandVertices ExpandVerticesGeneral
+ // Can't define methos here (unless not parameters are used).
+// typename CHwTVertexBufferBuilder<TVertex>::FN_ExpandVertices ExpandVerticesGeneral
+ // FN_ExpandVertices ExpandVerticesGeneral
+ void ExpandVerticesGeneral(
+ __range(1,UINT_MAX) UINT uCount,
+ __inout_ecount_full(uCount) TVertex *rgVertices
+ )
+ {
+ TransferAndOrExpandVerticesInline(
+ uCount,
+ rgVertices,
+ rgVertices,
+ m_mvfGenerated,
+ m_mvfaAntiAliasScaleLocation,
+ true, // => fInputOutputAreSameBuffer
+ false // => fTransformPosition
+ );
+ }
+
+ void TransferAndExpandVerticesGeneral(
+ __range(1,UINT_MAX) UINT uCount,
+ __in_ecount(uCount) TVertex const *rgInputVertices,
+ __out_ecount_full(uCount) TVertex *rgOutputVertices,
+ bool fTransformPosition
+ )
+ {
+ TransferAndOrExpandVerticesInline(
+ uCount,
+ rgInputVertices,
+ rgOutputVertices,
+ m_mvfGenerated,
+ m_mvfaAntiAliasScaleLocation,
+ false, // => fInputOutputAreSameBuffer
+ fTransformPosition // => fTransformPosition
+ );
+ }
+
+ // FN_ExpandVertices ExpandVerticesInvalid
+ void ExpandVerticesInvalid(
+ __range(1,UINT_MAX) UINT uCount,
+ __inout_ecount_full(uCount) TVertex *rgVertices
+ )
+ {
+ RIP("Invalid ExpandVertices routine.");
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: NeedCoverageGeometry
+ //
+ // Synopsis: True if we should create geometry for a particular
+ // coverage value.
+ //
+ //-------------------------------------------------------------------------
+ bool NeedCoverageGeometry(INT nCoverage) const;
+
+
+
+
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: ReinterpretFloatAsDWORD
+ //
+ // Synopsis: Quicky helper to convert a float to a DWORD bitwise.
+ //
+ //-------------------------------------------------------------------------
+ static MIL_FORCEINLINE DWORD ReinterpretFloatAsDWORD(float c)
+ {
+ return reinterpret_cast<DWORD &>(c);
+ }
+
+private:
+ MIL_FORCEINLINE bool AreWaffling() const
+ {
+ return m_map.AreWaffling();
+ }
+
+ void ViewportToPackedCoordinates(
+ __range(1,UINT_MAX / uGroupSize) UINT uGroupCount,
+ __inout_ecount(uGroupCount * uGroupSize) TVertex *pVertex,
+ __range(2,6) UINT uGroupSize,
+ /*__range(0,NUM_OF_VERTEX_TEXTURE_COORDS(TVertex)-1)*/ __bound UINT uIndex
+ );
+
+ void ViewportToPackedCoordinates(
+ __range(1,UINT_MAX / uGroupSize) UINT uGroupCount,
+ __inout_ecount(uGroupCount * uGroupSize) TVertex *pVertex,
+ __range(2,6) UINT uGroupSize
+ );
+
+ template<class TWaffler>
+ __out_ecount(1) typename TWaffler::ISink *
+ BuildWafflePipeline(
+ __out_xcount(NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2) TWaffler *wafflers,
+ __out_ecount(1) bool &fWafflersUsed
+ ) const;
+
+
+ template<class TWaffler>
+ typename TWaffler::ISink *
+ BuildWafflePipeline(
+ __out_xcount(NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2) TWaffler *wafflers
+ ) const
+ {
+ bool fNotUsed;
+ return BuildWafflePipeline(wafflers, fNotUsed);
+ }*/
+
+ m_pVB: &'y mut CHwTVertexBuffer<'z, TVertex>,
+
+ //m_pfnExpandVertices: PFN_ExpandVertices, // Method for expanding vertices
+
+ //m_rgoPrecomputedTriListVertices: *const TVertex,
+ //m_cPrecomputedTriListVertices: UINT,
+
+ //m_rguPrecomputedTriListIndices: *const UINT,
+ //m_cPrecomputedTriListIndices: UINT,
+
+ //m_map: CHwTVertexMappings<TVertex>,
+
+ // This is true if we had to flush the pipeline as we were getting
+ // geometry rather than just filling up a single vertex buffer.
+ m_fHasFlushed: bool,
+
+ // The next two members control the generation of the zero-alpha geometry
+ // outside the input geometry.
+ m_fNeedOutsideGeometry: bool,
+ m_fNeedInsideGeometry: bool,
+ m_rcOutsideBounds: CMILSurfaceRect, // Bounds for creation of outside geometry
+
+ /*
+ // Helpful m_rcOutsideBounds casts.
+ float OutsideLeft() const { return static_cast<float>(m_rcOutsideBounds.left); }
+ float OutsideRight() const { return static_cast<float>(m_rcOutsideBounds.right); }
+ float OutsideTop() const { return static_cast<float>(m_rcOutsideBounds.top); }
+ float OutsideBottom() const { return static_cast<float>(m_rcOutsideBounds.bottom); }
+ */
+ // This interval (if we are doing outside) shows the location
+ // of the current stratum. It is initialized to [FLT_MAX, -FLT_MAX].
+ //
+ // If the current stratum is a complex span then
+ // m_rCurStratumBottom is set to the bottom of the stratum and
+ // m_rCurStratumTop is set to FLT_MAX.
+ //
+ // If the current stratum is a trapezoidal one, then
+ // m_rCurStratumBottom is its bottom and m_rCurStratumTop is its
+ // top.
+ m_rCurStratumTop: f32,
+ m_rCurStratumBottom: f32,
+
+ // If the current stratum is a trapezoidal one, following var stores
+ // right boundary of the last trapezoid handled by PrepareStratum.
+ // We need it to cloze the stratus properly.
+ m_rLastTrapezoidRight: f32,
+
+ // These are needed to implement outside geometry using triangle lists
+ m_rLastTrapezoidTopRight: f32,
+ m_rLastTrapezoidBottomRight: f32,
+}
+
+/*
+//+----------------------------------------------------------------------------
+//
+// Member: CHwVertexBuffer::AddTriangle
+//
+// Synopsis: Add a triangle using the three indices given to the list
+//
+impl CHwVertexBuffer {
+
+fn AddTriangle(
+ i1: WORD, // In: Index of triangle's first vertex
+ i2: WORD, // In: Index of triangle's second vertex
+ i3: WORD // In: Index of triangle's third vertex
+ ) -> HRESULT
+{
+ let hr: HRESULT = S_OK;
+
+ // Asserting indices < max vertex requires a debug only pure virtual method
+ // which is too much of a functionality change between retail and debug.
+ //
+ //
+ // Assert(i1 < GetNumTriListVertices());
+ // Assert(i2 < GetNumTriListVertices());
+ // Assert(i3 < GetNumTriListVertices());
+
+ WORD *pIndices;
+
+ IFC(m_rgIndices.AddMultiple(3, &pIndices));
+
+ pIndices[0] = i1;
+ pIndices[1] = i2;
+ pIndices[2] = i3;
+
+Cleanup:
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::AddTriangle
+//
+// Synopsis: Add a triangle using given three points to the list
+//
+//-----------------------------------------------------------------------------
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::AddTriangle(
+ __in_ecount(1) const PointXYA &v0,
+ __in_ecount(1) const PointXYA &v1,
+ __in_ecount(1) const PointXYA &v2)
+{
+ let hr: HRESULT = S_OK;
+
+ TVertex *pVertices;
+ hr = AddNonIndexedTriListVertices(3,&pVertices);
+
+ if (hr == E_OUTOFMEMORY)
+ {
+ DebugBreak ();
+ }
+ IFC(hr);
+
+ pVertices[0].ptPt.X = v0.x;
+ pVertices[0].ptPt.Y = v0.y;
+ pVertices[0].Diffuse = reinterpret_cast<const DWORD &>(v0.a);
+ pVertices[1].ptPt.X = v1.x;
+ pVertices[1].ptPt.Y = v1.y;
+ pVertices[1].Diffuse = reinterpret_cast<const DWORD &>(v1.a);
+ pVertices[2].ptPt.X = v2.x;
+ pVertices[2].ptPt.Y = v2.y;
+ pVertices[2].Diffuse = reinterpret_cast<const DWORD &>(v2.a);
+
+Cleanup:
+ RRETURN(hr);
+}
+*/
+
+impl CHwVertexBuffer<'_> {
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::AddLine
+//
+// Synopsis: Add a nominal width line using given two points to the list
+//
+//-----------------------------------------------------------------------------
+fn AddLine(&mut self,
+ v0: &PointXYA,
+ v1: &PointXYA
+ ) -> HRESULT
+{
+ type TVertex = CD3DVertexXYZDUV2;
+ let hr = S_OK;
+
+ let pVertices: &mut [TVertex];
+ let mut rgScratchVertices: [TVertex; 2] = Default::default();
+
+ assert!(!(v0.y != v1.y));
+
+ let fUseTriangles = /*(v0.y < m_pBuilder->GetViewportTop() + 1) ||*/ FORCE_TRIANGLES;
+
+ //if (fUseTriangles)
+ //{
+ pVertices = &mut rgScratchVertices;
+ //}
+ //else
+ //{
+ //IFC!(AddLineListVertices(2, &pVertices));
+ //}
+
+ pVertices[0].x = v0.x;
+ pVertices[0].y = v0.y;
+ pVertices[0].coverage = v0.a;
+ pVertices[1].x = v1.x;
+ pVertices[1].y = v1.y;
+ pVertices[1].coverage = v1.a;
+
+ if (fUseTriangles)
+ {
+ IFC!(self.AddLineAsTriangleList(&pVertices[0],&pVertices[1]));
+ }
+
+ RRETURN!(hr);
+}
+}
+/*
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::AddTriListVertices
+//
+// Synopsis: Reserve space for consecutive vertices and return start index
+//
+
+template <class TVertex>
+MIL_FORCEINLINE
+HRESULT
+CHwTVertexBuffer<TVertex>::AddTriListVertices(
+ UINT uDelta,
+ __deref_ecount(uDelta) TVertex **ppVertices,
+ __out_ecount(1) WORD *pwIndexStart
+ )
+{
+ HRESULT hr = S_OK;
+
+ Assert(ppVertices);
+
+ UINT uCount = static_cast<UINT>(m_rgVerticesTriList.GetCount());
+ if (uCount > SHRT_MAX)
+ {
+ IFC(WGXERR_INVALIDPARAMETER);
+ }
+ UINT newCount;
+ newCount = uDelta + uCount;
+
+ if (newCount > SHRT_MAX)
+ {
+ IFC(m_pBuilder->FlushReset());
+ uCount = 0;
+ newCount = uDelta;
+ }
+
+ if (newCount > m_rgVerticesTriList.GetCapacity())
+ {
+ IFC(m_rgVerticesTriList.ReserveSpace(uDelta));
+ }
+
+ m_rgVerticesTriList.SetCount(newCount);
+ *pwIndexStart = static_cast<WORD>(uCount);
+ *ppVertices = &m_rgVerticesTriList[uCount];
+
+ Cleanup:
+ RRETURN(hr);
+}
+*/
+
+impl<TVertex: Clone + Default> CHwTVertexBuffer<'_, TVertex> {
+
+fn AddTriVertices(&mut self, v0: TVertex, v1: TVertex, v2: TVertex) {
+ if let Some(output_buffer) = &mut self.m_rgVerticesBuffer {
+ let offset = self.m_rgVerticesBufferOffset;
+ if offset + 3 <= output_buffer.len() {
+ output_buffer[offset] = v0;
+ output_buffer[offset + 1] = v1;
+ output_buffer[offset + 2] = v2;
+ }
+ self.m_rgVerticesBufferOffset = offset + 3;
+ } else {
+ self.m_rgVerticesTriList.reserve(3);
+ self.m_rgVerticesTriList.push(v0);
+ self.m_rgVerticesTriList.push(v1);
+ self.m_rgVerticesTriList.push(v2);
+ }
+}
+
+fn AddTrapezoidVertices(&mut self, v0: TVertex, v1: TVertex, v2: TVertex, v3: TVertex) {
+ if let Some(output_buffer) = &mut self.m_rgVerticesBuffer {
+ let offset = self.m_rgVerticesBufferOffset;
+ if offset + 6 <= output_buffer.len() {
+ output_buffer[offset] = v0;
+ output_buffer[offset + 1] = v1.clone();
+ output_buffer[offset + 2] = v2.clone();
+
+ output_buffer[offset + 3] = v1;
+ output_buffer[offset + 4] = v2;
+ output_buffer[offset + 5] = v3;
+ }
+ self.m_rgVerticesBufferOffset = offset + 6;
+ } else {
+ self.m_rgVerticesTriList.reserve(6);
+
+ self.m_rgVerticesTriList.push(v0);
+ self.m_rgVerticesTriList.push(v1.clone());
+ self.m_rgVerticesTriList.push(v2.clone());
+
+ self.m_rgVerticesTriList.push(v1);
+ self.m_rgVerticesTriList.push(v2);
+ self.m_rgVerticesTriList.push(v3);
+ }
+}
+
+fn AddedNonLineSegment(&mut self) {
+ #[cfg(debug_assertions)]
+ {
+ self.m_fDbgNonLineSegmentTriangleStrip = true;
+ }
+}
+
+}
+
+/*
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::AddNonIndexedTriListVertices
+//
+// Synopsis: Reserve space for triangle list vertices.
+//
+
+template <class TVertex>
+MIL_FORCEINLINE
+HRESULT
+CHwTVertexBuffer<TVertex>::AddNonIndexedTriListVertices(
+ UINT uCount,
+ __deref_ecount(uCount) TVertex **ppVertices
+ )
+{
+ HRESULT hr = S_OK;
+
+ UINT Count = static_cast<UINT>(m_rgVerticesNonIndexedTriList.GetCount());
+ UINT newCount = Count + uCount;
+
+ if (newCount > m_rgVerticesNonIndexedTriList.GetCapacity())
+ {
+ IFC(m_rgVerticesNonIndexedTriList.ReserveSpace(uCount));
+ }
+
+ m_rgVerticesNonIndexedTriList.SetCount(newCount);
+ *ppVertices = &m_rgVerticesNonIndexedTriList[Count];
+
+Cleanup:
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::AddLineListVertices
+//
+// Synopsis: Reserve space for consecutive vertices
+//
+
+template <class TVertex>
+MIL_FORCEINLINE
+HRESULT
+CHwTVertexBuffer<TVertex>::AddLineListVertices(
+ UINT uCount,
+ __deref_ecount(uCount) TVertex **ppVertices
+ )
+{
+ HRESULT hr = S_OK;
+
+ Assert(ppVertices);
+
+ UINT Count = static_cast<UINT>(m_rgVerticesLineList.GetCount());
+ UINT newCount = Count + uCount;
+
+ if (newCount > m_rgVerticesLineList.GetCapacity())
+ {
+ IFC(m_rgVerticesLineList.ReserveSpace(uCount));
+ }
+
+ m_rgVerticesLineList.SetCount(newCount);
+ *ppVertices = &m_rgVerticesLineList[Count];
+
+Cleanup:
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Class: CHwVertexBuffer::Builder
+//
+//-----------------------------------------------------------------------------
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::GetOutVertexFormat
+//
+// Synopsis: Return MIL vertex format covered by specific builders
+//
+//-----------------------------------------------------------------------------
+
+template <>
+MilVertexFormat
+CHwTVertexBuffer<CD3DVertexXYZDUV2>::Builder::GetOutVertexFormat()
+{
+ return (MILVFAttrXYZ | MILVFAttrDiffuse | MILVFAttrUV2);
+}
+
+template <>
+MilVertexFormat
+CHwTVertexBuffer<CD3DVertexXYZDUV8>::Builder::GetOutVertexFormat()
+{
+ return (MILVFAttrXYZ | MILVFAttrDiffuse | MILVFAttrUV8);
+}
+
+template <>
+MilVertexFormat
+CHwTVertexBuffer<CD3DVertexXYZDUV6>::Builder::GetOutVertexFormat()
+{
+ return (MILVFAttrXYZ | MILVFAttrDiffuse | MILVFAttrUV6);
+}
+
+template <>
+MilVertexFormat
+CHwTVertexBuffer<CD3DVertexXYZNDSUV4>::Builder::GetOutVertexFormat()
+{
+ return (MILVFAttrXYZ |
+ MILVFAttrNormal |
+ MILVFAttrDiffuse |
+ MILVFAttrSpecular |
+ MILVFAttrUV4);
+}
+
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwVertexBuffer::Builder::Create
+//
+// Synopsis: Choose the appropriate final vertex format and instantiate the
+// matching vertex builder
+//
+*/
+pub type CHwVertexBufferBuilder<'y, 'z> = CHwTVertexBufferBuilder<'y, 'z, OutputVertex>;
+impl<'y, 'z> CHwVertexBufferBuilder<'y, 'z> {
+pub fn Create(
+ vfIn: MilVertexFormat,
+ vfOut: MilVertexFormat,
+ mvfaAntiAliasScaleLocation: MilVertexFormatAttribute,
+ pVertexBuffer: &'y mut CHwVertexBuffer<'z>,
+ /*pBufferDispenser: &CBufferDispenser*/
+ ) -> CHwVertexBufferBuilder<'y, 'z>
+{
+ CHwVertexBufferBuilder::CreateTemplate(pVertexBuffer, vfIn, vfOut, mvfaAntiAliasScaleLocation)
+ //let hr: HRESULT = S_OK;
+
+ //assert!(ppVertexBufferBuilder);
+
+ //*ppVertexBufferBuilder = None;
+/*
+ if (!(vfOut & ~CHwTVertexBuffer<CD3DVertexXYZDUV2>::Builder::GetOutVertexFormat()))
+ {
+ CHwTVertexBuffer<CD3DVertexXYZDUV2> *pVB = pDevice->GetVB_XYZDUV2();
+ CHwTVertexBuffer<CD3DVertexXYZDUV2>::Builder *pVBB = NULL;
+
+ IFC(CHwTVertexBuffer<CD3DVertexXYZDUV2>::Builder::Create(
+ pVB,
+ vfIn,
+ vfOut,
+ mvfaAntiAliasScaleLocation,
+ pBufferDispenser,
+ &pVBB
+ ));
+
+ *ppVertexBufferBuilder = pVBB;
+ }
+ else if (!(vfOut & ~CHwTVertexBuffer<CD3DVertexXYZDUV8>::Builder::GetOutVertexFormat()))
+ {
+ CHwTVertexBuffer<CD3DVertexXYZDUV8> *pVB = pDevice->GetVB_XYZRHWDUV8();
+ CHwTVertexBuffer<CD3DVertexXYZDUV8>::Builder *pVBB = NULL;
+
+ IFC(CHwTVertexBuffer<CD3DVertexXYZDUV8>::Builder::Create(
+ pVB,
+ vfIn,
+ vfOut,
+ mvfaAntiAliasScaleLocation,
+ pBufferDispenser,
+ &pVBB
+ ));
+
+ *ppVertexBufferBuilder = pVBB;
+ }
+ else
+ {
+ // NOTE-2004/03/22-chrisra Adding another vertexbuffer type requires updating enum
+ //
+ // If we add another buffer builder type kMaxVertexBuilderSize enum in hwvertexbuffer.h file
+ // needs to be updated to reflect possible changes to the maximum size of buffer builders.
+ //
+ IFC(E_NOTIMPL);
+ }
+
+ // Store the pipeline, if any, which this VBB can use to spill the vertex buffer to if it
+ // overflows.
+ (**ppVertexBufferBuilder).m_pPipelineNoRef = pPipeline;
+ (**ppVertexBufferBuilder).m_pDeviceNoRef = pDevice;
+
+
+Cleanup:
+ RRETURN(hr);*/
+ //hr
+}
+ /*fn AreWafffling(&self) -> bool {
+ false
+ }*/
+
+ // Helpful m_rcOutsideBounds casts.
+ fn OutsideLeft(&self) -> f32 { return self.m_rcOutsideBounds.left as f32; }
+ fn OutsideRight(&self) -> f32 { return self.m_rcOutsideBounds.right as f32; }
+ fn OutsideTop(&self) -> f32 { return self.m_rcOutsideBounds.top as f32; }
+ fn OutsideBottom(&self) -> f32 { return self.m_rcOutsideBounds.bottom as f32; }
+}
+
+//+----------------------------------------------------------------------------
+//
+// Class: THwTVertexMappings<class TVertex>
+//
+//-----------------------------------------------------------------------------
+
+//+----------------------------------------------------------------------------
+//
+// Member: THwTVertexMappings<TVertex>::THwTVertexMappings
+//
+// Synopsis: ctor
+//
+//-----------------------------------------------------------------------------
+/*
+template <class TVertex>
+CHwTVertexMappings<TVertex>::CHwTVertexMappings()
+ :
+ m_mvfMapped(MILVFAttrNone)
+{
+ for (int i = 0; i < ARRAY_SIZE(m_rgWaffleMode); ++i)
+ {
+ m_rgWaffleMode[i] = WaffleModeNone;
+ }
+
+ m_matPos2DTransform.SetIdentity();
+}
+
+
+//+----------------------------------------------------------------------------
+//
+// Member: THwTVertexMappings<TVertex>::SetPositionTransform
+//
+// Synopsis: Sets the position transform that needs to be applied.
+//
+//-----------------------------------------------------------------------------
+template <class TVertex>
+void
+CHwTVertexMappings<TVertex>::SetPositionTransform(
+ __in_ecount(1) const MILMatrix3x2 &matPositionTransform
+ )
+{
+ m_matPos2DTransform = matPositionTransform;
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexMappings<TVertex>::SetConstantMapping
+//
+// Synopsis: Remember the static color for the given vertex field
+//
+
+template <class TVertex>
+HRESULT
+CHwTVertexMappings<TVertex>::SetConstantMapping(
+ MilVertexFormatAttribute mvfaLocation,
+ __in_ecount(1) const CHwConstantColorSource *pConstCS
+ )
+{
+ HRESULT hr = S_OK;
+
+ Assert(!(m_mvfMapped & mvfaLocation));
+ pConstCS->GetColor(m_colorStatic);
+ m_mvfMapped |= mvfaLocation; // Remember this field has been mapped
+
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Function: GetMILVFAttributeOfTextureCoord
+//
+// Synopsis: Compute MilVertexFormatAttribute for a texture coordinate index
+//
+
+MIL_FORCEINLINE
+MilVertexFormat
+GetMILVFAttributeOfTextureCoord(
+ DWORD dwCoordIndex
+ )
+{
+ return MILVFAttrUV1 << dwCoordIndex;
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexMappings<TVertex>::PointToUV
+//
+// Synopsis: Helper function to populate the texture coordinates at the given
+// index using the given point
+//
+
+template <class TVertex>
+MIL_FORCEINLINE void
+CHwTVertexMappings<TVertex>::PointToUV(
+ __in_ecount(1) const MilPoint2F &ptIn,
+ __bound UINT uIndex,
+ __out_ecount(1) TVertex *pvOut
+ )
+{
+ m_rgmatPointToUV[uIndex].TransformPoint(
+ &pvOut->ptTx[uIndex],
+ ptIn.X,
+ ptIn.Y
+ );
+}
+
+
+
+
+
+//+----------------------------------------------------------------------------
+//
+// Class: CHwTVertexBuffer<TVertex>::Builder
+//
+//-----------------------------------------------------------------------------
+
+
+*/
+
+impl<'y, 'z, TVertex: Default> CHwTVertexBufferBuilder<'y, 'z, TVertex> {
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::Create
+//
+// Synopsis: Instantiate a specific type of vertex builder
+//
+
+fn CreateTemplate(
+ pVertexBuffer: &'y mut CHwTVertexBuffer<'z, TVertex>,
+ mvfIn: MilVertexFormat,
+ mvfOut: MilVertexFormat,
+ mvfaAntiAliasScaleLocation: MilVertexFormatAttribute,
+ /*pBufferDispenser: __inout_ecount(1) CBufferDispenser *,*/
+ ) -> Self
+{
+
+
+
+ let mut pVertexBufferBuilder = CHwTVertexBufferBuilder::<TVertex>::new(pVertexBuffer);
+
+ IFC!(pVertexBufferBuilder.SetupConverter(
+ mvfIn,
+ mvfOut,
+ mvfaAntiAliasScaleLocation
+ ));
+
+ return pVertexBufferBuilder;
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::Builder
+//
+// Synopsis: ctor
+//
+//-----------------------------------------------------------------------------
+
+fn new(pVertexBuffer: &'y mut CHwTVertexBuffer<'z, TVertex>) -> Self
+{
+ Self {
+ m_pVB: pVertexBuffer,
+
+
+ //m_rgoPrecomputedTriListVertices: NULL(),
+ //m_cPrecomputedTriListVertices: 0,
+
+ //m_rguPrecomputedTriListIndices: NULL(),
+ //m_cPrecomputedTriListIndices: 0,
+
+ // These two track the Y extent of the shape this builder is producing.
+ m_rCurStratumTop: f32::MAX,
+ m_rCurStratumBottom: -f32::MAX,
+ m_fNeedOutsideGeometry: false,
+ m_fNeedInsideGeometry: true,
+
+ m_rLastTrapezoidRight: -f32::MAX,
+ m_rLastTrapezoidTopRight: -f32::MAX,
+ m_rLastTrapezoidBottomRight: -f32::MAX,
+
+ m_fHasFlushed: false,
+ //m_map: Default::default(),
+ m_rcOutsideBounds: Default::default(),
+ #[cfg(debug_assertions)]
+ m_mvfDbgOut: MilVertexFormatAttribute::MILVFAttrNone as MilVertexFormat,
+ m_mvfIn: MilVertexFormatAttribute::MILVFAttrNone as MilVertexFormat,
+ m_mvfGenerated: MilVertexFormatAttribute::MILVFAttrNone as MilVertexFormat,
+ m_mvfaAntiAliasScaleLocation: MilVertexFormatAttribute::MILVFAttrNone,
+ }
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::SetupConverter
+//
+// Synopsis: Choose the appropriate conversion method
+//
+
+fn SetupConverter(&mut self,
+ mvfIn: MilVertexFormat,
+ mvfOut: MilVertexFormat,
+ mvfaAntiAliasScaleLocation: MilVertexFormatAttribute,
+ ) -> HRESULT
+{
+ let hr = S_OK;
+
+ self.m_mvfIn = mvfIn;
+
+ #[cfg(debug_assertions)]
+ {
+ self.m_mvfDbgOut = mvfOut;
+ }
+
+ self.m_mvfGenerated = mvfOut & !self.m_mvfIn;
+ self.m_mvfaAntiAliasScaleLocation = mvfaAntiAliasScaleLocation;
+
+ assert!((self.m_mvfGenerated & MilVertexFormatAttribute::MILVFAttrXY as MilVertexFormat) == 0);
+
+ RRETURN!(hr);
+}
+}
+/*
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::SetTransformMapping
+//
+// Synopsis: Delegate mapping sets to CHwTVertexMappings
+//
+//-----------------------------------------------------------------------------
+
+template <class TVertex>
+void
+CHwTVertexBuffer<TVertex>::Builder::SetTransformMapping(
+ __in_ecount(1) const MILMatrix3x2 &mat2DPositionTransform
+ )
+{
+ m_map.SetPositionTransform(mat2DPositionTransform);
+}
+
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::SetConstantMapping(
+ MilVertexFormatAttribute mvfaLocation,
+ __in_ecount(1) const CHwConstantColorSource *pConstCS
+ )
+{
+ HRESULT hr = S_OK;
+
+ IFC(m_map.SetConstantMapping(mvfaLocation, pConstCS));
+
+Cleanup:
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::FinalizeMappings
+//
+// Synopsis: Complete setup of vertex mappings
+//
+
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::FinalizeMappings(
+ )
+{
+ HRESULT hr = S_OK;
+
+ //
+ // Set default Z if required.
+ //
+
+ if (m_mvfGenerated & MILVFAttrZ)
+ {
+ if (!(m_map.m_mvfMapped & MILVFAttrZ))
+ {
+ m_map.m_vStatic.Z = 0.5f;
+ }
+ }
+
+ //
+ // If AA falloff is not going to scale the diffuse color and it is
+ // generated then see if the color is constant such that we can do any
+ // complex conversions just once here instead of in every iteration of the
+ // expansion loop. If AA falloff is going to scale the diffuse color then
+ // we can still optimize for the falloff = 1.0 case by precomputing that
+ // color now and checking for 1.0 during generation. Such a precomputation
+ // has shown significant to performance.
+ //
+
+ if (m_mvfGenerated & MILVFAttrDiffuse)
+ {
+ if (m_map.m_mvfMapped & MILVFAttrDiffuse)
+ {
+
+ // Assumes diffuse color is constant
+ m_map.m_vStatic.Diffuse =
+ Convert_MilColorF_scRGB_To_Premultiplied_MilColorB_sRGB(&m_map.m_colorStatic);
+ }
+ else
+ {
+ // Set default Diffuse value: White
+ m_map.m_vStatic.Diffuse = MIL_COLOR(0xFF,0xFF,0xFF,0xFF);
+ }
+ }
+
+ RRETURN(hr);
+}*/
+impl<TVertex> CHwTVertexBufferBuilder<'_, '_, TVertex> {
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::SetOutsideBounds
+//
+//
+// Synopsis: Enables rendering geometry for areas outside the shape but
+// within the bounds. These areas will be created with
+// zero alpha.
+//
+
+pub fn SetOutsideBounds(&mut self,
+ prcOutsideBounds: Option<&CMILSurfaceRect>,
+ fNeedInside: bool,
+ )
+{
+ // Waffling and outside bounds is not currently implemented. It's
+ // not difficult to do but currently there is no need.
+ //assert!(!(self.AreWaffling() && self.prcOutsideBounds));
+
+ if let Some(prcOutsideBounds) = prcOutsideBounds
+ {
+ self.m_rcOutsideBounds = prcOutsideBounds.clone();
+ self.m_fNeedOutsideGeometry = true;
+ self.m_fNeedInsideGeometry = fNeedInside;
+ }
+ else
+ {
+ self.m_fNeedOutsideGeometry = false;
+ self.m_fNeedInsideGeometry = true;
+ }
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::BeginBuilding
+//
+// Synopsis: Prepare for a new primitive by resetting the vertex buffer
+//
+pub fn BeginBuilding(&mut self,
+ ) -> HRESULT
+{
+
+ let hr: HRESULT = S_OK;
+
+ self.m_fHasFlushed = false;
+ self.m_pVB.Reset(/*self*/);
+
+ RRETURN!(hr);
+}
+}
+impl IGeometrySink for CHwVertexBufferBuilder<'_, '_> {
+
+ fn AddTrapezoid(&mut self,
+ rPixelYTop: f32, // In: y coordinate of top of trapezoid
+ rPixelXTopLeft: f32, // In: x coordinate for top left
+ rPixelXTopRight: f32, // In: x coordinate for top right
+ rPixelYBottom: f32, // In: y coordinate of bottom of trapezoid
+ rPixelXBottomLeft: f32, // In: x coordinate for bottom left
+ rPixelXBottomRight: f32, // In: x coordinate for bottom right
+ rPixelXLeftDelta: f32, // In: trapezoid expand radius for left edge
+ rPixelXRightDelta: f32 // In: trapezoid expand radius for right edge
+ ) -> HRESULT
+ {
+ let hr = S_OK;
+
+ if (/*self.AreWaffling()*/ false)
+ {
+ /*IFC(AddTrapezoidWaffle(
+ rPixelYTop,
+ rPixelXTopLeft,
+ rPixelXTopRight,
+ rPixelYBottom,
+ rPixelXBottomLeft,
+ rPixelXBottomRight,
+ rPixelXLeftDelta,
+ rPixelXRightDelta));*/
+ }
+ else
+ {
+ IFC!(self.AddTrapezoidStandard(
+ rPixelYTop,
+ rPixelXTopLeft,
+ rPixelXTopRight,
+ rPixelYBottom,
+ rPixelXBottomLeft,
+ rPixelXBottomRight,
+ rPixelXLeftDelta,
+ rPixelXRightDelta));
+ }
+
+ //Cleanup:
+ RRETURN!(hr);
+ }
+
+
+ fn IsEmpty(&self) -> bool {
+ self.m_pVB.IsEmpty()
+ }
+
+/*
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddVertex
+//
+// Synopsis: Add a vertex to the vertex buffer
+//
+// Remember just the given vertex information now and convert later
+// in a single, more optimal pass.
+//
+
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::AddVertex(
+ __in_ecount(1) const MilPoint2F &ptPosition,
+ // Vertex coordinates
+ __out_ecount(1) WORD *pIndex
+ // The index of the new vertex
+ )
+{
+ HRESULT hr = S_OK;
+
+ Assert(!NeedOutsideGeometry());
+ Assert(m_mvfIn == MILVFAttrXY);
+
+ TVertex *pVertex;
+
+ IFC(m_pVB->AddTriListVertices(1, &pVertex, pIndex));
+
+ pVertex->ptPt = ptPosition;
+
+ // store coverage as a DWORD instead of float
+
+ pVertex->Diffuse = FLOAT_ONE;
+
+Cleanup:
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddIndexedVertices, IGeometrySink
+//
+// Synopsis: Add a fully computed, indexed vertex to the vertex buffer
+//
+
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::AddIndexedVertices(
+ UINT cVertices,
+ // In: number of vertices
+ __in_bcount(cVertices*uVertexStride) const void *pVertexBufferNoRef,
+ // In: vertex buffer containing the vertices
+ UINT uVertexStride,
+ // In: size of each vertex
+ MilVertexFormat mvfFormat,
+ // In: format of each vertex
+ UINT cIndices,
+ // In: Number of indices
+ __in_ecount(cIndices) const UINT *puIndexBuffer
+ // In: index buffer
+ )
+{
+ Assert(m_mvfIn & (MILVFAttrXYZ | MILVFAttrDiffuse | MILVFAttrUV2));
+ Assert(mvfFormat == (MILVFAttrXYZ | MILVFAttrDiffuse | MILVFAttrUV2));
+
+ Assert(uVertexStride == sizeof(TVertex));
+
+ m_rgoPrecomputedTriListVertices = reinterpret_cast<const TVertex *>(pVertexBufferNoRef);
+ m_cPrecomputedTriListVertices = cVertices;
+
+ m_rguPrecomputedTriListIndices = puIndexBuffer;
+ m_cPrecomputedTriListIndices = cIndices;
+
+ return S_OK;
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddTriangle
+//
+// Synopsis: Add a triangle to the vertex buffer
+//
+
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::AddTriangle(
+ DWORD i1, // In: Index of triangle's first vertex
+ DWORD i2, // In: Index of triangle's second vertex
+ DWORD i3 // In: Index of triangle's third vertex
+ )
+{
+ HRESULT hr = S_OK;
+
+ Assert(!NeedOutsideGeometry());
+
+ if (AreWaffling())
+ {
+ TVertex *pVertex;
+ UINT uNumVertices;
+ m_pVB->GetTriListVertices(&pVertex, &uNumVertices);
+
+ Assert(i1 < uNumVertices);
+ Assert(i2 < uNumVertices);
+ Assert(i3 < uNumVertices);
+
+ PointXYA rgPoints[3];
+ rgPoints[0].x = pVertex[i1].ptPt.X;
+ rgPoints[0].y = pVertex[i1].ptPt.Y;
+ rgPoints[0].a = 1;
+ rgPoints[1].x = pVertex[i2].ptPt.X;
+ rgPoints[1].y = pVertex[i2].ptPt.Y;
+ rgPoints[1].a = 1;
+ rgPoints[2].x = pVertex[i3].ptPt.X;
+ rgPoints[2].y = pVertex[i3].ptPt.Y;
+ rgPoints[2].a = 1;
+
+ TriangleWaffler<PointXYA> wafflers[NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2];
+ TriangleWaffler<PointXYA>::ISink *pWaffleSinkNoRef = BuildWafflePipeline(wafflers);
+ IFC(pWaffleSinkNoRef->AddTriangle(rgPoints[0], rgPoints[1], rgPoints[2]));
+ }
+ else
+ {
+ IFC(m_pVB->AddTriangle(
+ static_cast<WORD>(i1),
+ static_cast<WORD>(i2),
+ static_cast<WORD>(i3)
+ ));
+ }
+
+Cleanup:
+ RRETURN(hr);
+}
+*/
+
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddComplexScan
+//
+// Synopsis: Add a coverage span to the vertex buffer
+//
+//-----------------------------------------------------------------------------
+ fn AddComplexScan(&mut self,
+ nPixelY: INT,
+ // In: y coordinate in pixel space
+ mut pIntervalSpanStart: Ref<crate::aacoverage::CCoverageInterval>
+ // In: coverage segments
+ ) -> HRESULT {
+
+ let hr: HRESULT = S_OK;
+ //let pVertex: *mut CD3DVertexXYZDUV2 = NULL();
+
+ IFC!(self.PrepareStratum((nPixelY) as f32,
+ (nPixelY+1) as f32,
+ false, /* Not a trapezoid. */
+ 0., 0.,
+ 0., 0., 0., 0.));
+
+ let rPixelY: f32;
+ rPixelY = (nPixelY) as f32 + 0.5;
+
+ //LineWaffler<PointXYA> wafflers[NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2];
+
+ // Use sink for waffling & the first line fix up (aka the complicated cases.)
+ //ILineSink<PointXYA> *pLineSink = NULL;
+ let mut pLineSink = None;
+
+ /*if (self.AreWaffling())
+ {
+ bool fWafflersUsed;
+ pLineSink = BuildWafflePipeline(wafflers, OUT fWafflersUsed);
+ if (!fWafflersUsed)
+ {
+ pLineSink = NULL;
+ }
+ }*/
+
+ // Use triangles instead of lines, for lines too close to the top of the viewport
+ // because lines are clipped (before rasterization) against a viewport that only
+ // includes half of the top pixel row. Waffling will take care of this separately.
+ if (/*pLineSink.is_none() && rPixelY < self.GetViewportTop() + 1 ||*/ FORCE_TRIANGLES)
+ {
+ pLineSink = Some(&mut self.m_pVB);
+ }
+
+ //
+ // Output all segments if creating outside geometry, otherwise only output segments
+ // with non-zero coverage.
+ //
+
+ if (pLineSink.is_none())
+ {
+ /*
+ UINT nSegmentCount = 0;
+
+ for (const CCoverageInterval *pIntervalSpanTemp = pIntervalSpanStart;
+ pIntervalSpanTemp->m_nPixelX != INT_MAX;
+ pIntervalSpanTemp = pIntervalSpanTemp->m_pNext
+ )
+ {
+ if (NeedCoverageGeometry(pIntervalSpanTemp->m_nCoverage))
+ {
+ ++nSegmentCount;
+ }
+ }
+
+ //
+ // Add vertices
+ //
+ if (nSegmentCount)
+ {
+ IFC(m_pVB->AddLineListVertices(nSegmentCount*2, &pVertex));
+ }*/
+ }
+
+ //
+ // Having allocated space (if not using sink), now let's actually output the vertices.
+ //
+
+ while ((*pIntervalSpanStart).m_nPixelX.get() != INT::MAX)
+ {
+ assert!(!(*pIntervalSpanStart).m_pNext.get().is_null());
+
+ //
+ // Output line list segments
+ //
+ // Note that line segments light pixels by going through the the
+ // "diamond" interior of a pixel. While we could accomplish this
+ // by going from left edge to right edge of pixel, D3D10 uses the
+ // convention that the LASTPIXEL is never lit. We respect that now
+ // by setting D3DRS_LASTPIXEL to FALSE and use line segments that
+ // start in center of first pixel and end in center of one pixel
+ // beyond last.
+ //
+ // Since our top left corner is integer, we add 0.5 to get to the
+ // pixel center.
+ //
+ if (self.NeedCoverageGeometry((*pIntervalSpanStart).m_nCoverage.get()))
+ {
+ let rCoverage: f32 = ((*pIntervalSpanStart).m_nCoverage.get() as f32)/(c_nShiftSizeSquared as f32);
+
+ let mut iBegin: LONG = (*pIntervalSpanStart).m_nPixelX.get();
+ let mut iEnd: LONG = (*(*pIntervalSpanStart).m_pNext.get()).m_nPixelX.get();
+ if (self.NeedOutsideGeometry())
+ {
+ // Intersect the interval with the outside bounds to create
+ // start and stop lines. The scan begins (ends) with an
+ // interval starting (ending) at -inf (+inf).
+
+ // The given geometry is not guaranteed to be within m_rcOutsideBounds but
+ // the additional inner min and max (in that order) produce empty spans
+ // for intervals not intersecting m_rcOutsideBounds.
+ //
+ // We could cull here but that should really be done by the geometry
+ // generator.
+
+ iBegin = iBegin.max(iEnd.min(self.m_rcOutsideBounds.left));
+ iEnd = iEnd.min(iBegin.max(self.m_rcOutsideBounds.right));
+ }
+ let rPixelXBegin: f32= (iBegin as f32) + 0.5;
+ let rPixelXEnd: f32 = (iEnd as f32) + 0.5;
+
+ //
+ // Output line (linelist or tristrip) for a pixel
+ //
+
+ //if let Some(pLineSink) = pLineSink
+ {
+ let mut v0: PointXYA = Default::default(); let mut v1: PointXYA = Default::default();
+ v0.x = rPixelXBegin;
+ v0.y = rPixelY;
+ v0.a = rCoverage;
+
+ v1.x = rPixelXEnd;
+ v1.y = rPixelY;
+ v1.a = rCoverage;
+
+ IFC!(self.m_pVB.AddLine(&v0,&v1));
+ }
+ //else
+ {
+ /*
+ let dwDiffuse = ReinterpretFloatAsDWORD(rCoverage);
+
+ pVertex[0].ptPt.X = rPixelXBegin;
+ pVertex[0].ptPt.Y = rPixelY;
+ pVertex[0].Diffuse = dwDiffuse;
+
+ pVertex[1].ptPt.X = rPixelXEnd;
+ pVertex[1].ptPt.Y = rPixelY;
+ pVertex[1].Diffuse = dwDiffuse;
+
+ // Advance output vertex pointer
+ pVertex += 2;*/
+ }
+ }
+
+ //
+ // Advance coverage buffer
+ //
+
+ pIntervalSpanStart = (*pIntervalSpanStart).m_pNext.get();
+ }
+
+
+//Cleanup:
+ RRETURN!(hr);
+
+}
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddLineAsTriangleList
+//
+// Synopsis: Adds a horizontal line as a triangle list to work around
+// issue in D3D9 where horizontal lines with y = 0 may not render.
+//
+// Line clipping in D3D9
+// This behavior will change in D3D10 and this work-around will no
+// longer be needed. (Pixel center conventions will also change.)
+//
+//-----------------------------------------------------------------------------
+impl CHwVertexBuffer<'_> {
+ fn AddLineAsTriangleList(&mut self,
+ pBegin: &CD3DVertexXYZDUV2, // Begin
+ pEnd: &CD3DVertexXYZDUV2 // End
+ ) -> HRESULT
+{
+ let hr = S_OK;
+
+ // Collect pertinent data from vertices.
+ debug_assert!(pBegin.y == pEnd.y);
+ debug_assert!(pBegin.coverage == pEnd.coverage);
+
+ // Offset begin and end X left by 0.5 because the line starts on the first
+ // pixel center and ends on the center of the pixel after the line segment.
+ let x0 = pBegin.x - 0.5;
+ let x1 = pEnd.x - 0.5;
+ let y = pBegin.y;
+ let dwDiffuse = pBegin.coverage;
+
+ //
+ // Add the vertices
+ //
+
+ // OpenGL doesn't specify how vertex positions are converted to fixed point prior to rasterization. On macOS, with AMD GPUs,
+ // the GPU appears to truncate to fixed point instead of rounding. This behaviour is controlled by PA_SU_VTX_CNTL
+ // register. To handle this we'll add a 1./512. subpixel bias to the center vertex to cause the coordinates to round instead
+ // of truncate.
+ //
+ // D3D11 requires the fixed point integer result to be within 0.6ULP which implicitly disallows the truncate behaviour above.
+ // This means that D2D doesn't need to deal with this problem.
+ let subpixel_bias = self.subpixel_bias;
+
+
+ // Use a single triangle to cover the entire line
+ self.AddTriVertices(
+ OutputVertex{ x: x0, y: y - 0.5, coverage: dwDiffuse },
+ OutputVertex{ x: x0, y: y + 0.5, coverage: dwDiffuse },
+ OutputVertex{ x: x1, y: y + subpixel_bias, coverage: dwDiffuse },
+ );
+
+ self.AddedNonLineSegment();
+
+ //Cleanup:
+ RRETURN!(hr);
+}
+}
+
+/*
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddParallelogram
+//
+// Synopsis: This function adds the coordinates of a parallelogram to the vertex strip buffer.
+//
+// Parameter: rgPosition contains four coordinates of the parallelogram. Coordinates should have
+// a winding order
+//
+//-----------------------------------------------------------------------------
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::AddParallelogram(
+ __in_ecount(4) const MilPoint2F *rgPosition
+ )
+{
+ HRESULT hr = S_OK;
+
+ if (AreWaffling())
+ {
+ PointXYA rgPoints[4];
+ for (int i = 0; i < 4; ++i)
+ {
+ rgPoints[i].x = rgPosition[i].X;
+ rgPoints[i].y = rgPosition[i].Y;
+ rgPoints[i].a = 1;
+ }
+ TriangleWaffler<PointXYA> wafflers[NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2];
+ TriangleWaffler<PointXYA>::ISink *pWaffleSinkNoRef = BuildWafflePipeline(wafflers);
+ IFC(pWaffleSinkNoRef->AddTriangle(rgPoints[0], rgPoints[1], rgPoints[3]));
+ IFC(pWaffleSinkNoRef->AddTriangle(rgPoints[3], rgPoints[1], rgPoints[2]));
+ }
+ else
+ {
+ TVertex *pVertex;
+
+ //
+ // Add the vertices
+ //
+
+ IFC(m_pVB->AddTriStripVertices(6, &pVertex));
+
+ //
+ // Duplicate the first vertex. This creates 2 degenerate triangles: one connecting
+ // the previous rect to this one and another between vertices 0 and 1.
+ //
+
+ pVertex[0].ptPt = rgPosition[0];
+ pVertex[0].Diffuse = FLOAT_ONE;
+
+ pVertex[1].ptPt = rgPosition[0];
+ pVertex[1].Diffuse = FLOAT_ONE;
+
+ pVertex[2].ptPt = rgPosition[1];
+ pVertex[2].Diffuse = FLOAT_ONE;
+
+ pVertex[3].ptPt = rgPosition[3];
+ pVertex[3].Diffuse = FLOAT_ONE;
+
+ pVertex[4].ptPt = rgPosition[2];
+ pVertex[4].Diffuse = FLOAT_ONE;
+
+ //
+ // Duplicate the last vertex. This creates 2 degenerate triangles: one
+ // between vertices 4 and 5 and one connecting this Rect to the
+ // next one.
+ //
+
+ pVertex[5].ptPt = rgPosition[2];
+ pVertex[5].Diffuse = FLOAT_ONE;
+ }
+
+ Cleanup:
+ RRETURN(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::BuildWafflePipeline<TWaffler>
+//
+// Synopsis: Builds a pipeline of wafflers into the provided array of wafflers.
+// And returns a pointer (not to be deleted) to the input sink
+// of the waffle pipeline.
+// the final result is sinked int m_pVB.
+//
+//-----------------------------------------------------------------------------
+
+template<class TVertex>
+template<class TWaffler>
+__out_ecount(1) typename TWaffler::ISink *
+CHwTVertexBuffer<TVertex>::Builder::BuildWafflePipeline(
+ __out_xcount(NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2) TWaffler *wafflers,
+ __out_ecount(1) bool &fWafflersUsed
+ ) const
+{
+ UINT count = 0;
+
+ for (int i = 0; i < NUM_OF_VERTEX_TEXTURE_COORDS(TVertex); ++i)
+ {
+ if (m_map.m_rgWaffleMode[i] != 0)
+ {
+ const MILMatrix3x2 &pMatWaffle = m_map.m_rgmatPointToUV[i];
+
+ // Each column ([a,b,c] transpose) of this matrix specifies a waffler that
+ // partitions the plane into regions between the lines:
+ // ax + by + c = k
+ // for every integer k.
+ //
+ // If this partition width is substantially less than a pixel we have
+ // serious problems with waffling generating too many triangles for
+ // doubtful visual effect so we don't perform a waffling with width less
+ // than c_rMinWaffleWidthPixels. So we need to know the width of the partition
+ // regions:
+ //
+ // Changing c just translates the partition so let's assume c = 0.
+ // The line ax + by = 0 goes through the origin and the line ax + by
+ // = 1 is adjacent to it in the partition. The distance between
+ // these lines is also the distance from ax + by = 1 to the origin.
+ // Using Lagrange multipliers we can determine that this distance
+ // is
+ // 1/sqrt(a*a+b*b).
+ // We want to avoid waffling if this is less than c_rMinWaffleWidthPixels
+ // or equivalently:
+ // 1/sqrt(a*a+b*b) < c_rMinWaffleWidthPixels
+ // sqrt(a*a+b*b) > 1/c_rMinWaffleWidthPixels
+ // a*a+b*b > 1/(c_rMinWaffleWidthPixels*c_rMinWaffleWidthPixels)
+ //
+
+ const float c_rMaxWaffleMagnitude = 1/(c_rMinWaffleWidthPixels*c_rMinWaffleWidthPixels);
+
+ float mag0 = pMatWaffle.m_00*pMatWaffle.m_00+pMatWaffle.m_10*pMatWaffle.m_10;
+ if (mag0 < c_rMaxWaffleMagnitude)
+ {
+ wafflers[count].Set(pMatWaffle.m_00, pMatWaffle.m_10, pMatWaffle.m_20, wafflers+count+1);
+ ++count;
+ }
+
+ float mag1 = pMatWaffle.m_01*pMatWaffle.m_01+pMatWaffle.m_11*pMatWaffle.m_11;
+ if (mag1 < c_rMaxWaffleMagnitude)
+ {
+ wafflers[count].Set(pMatWaffle.m_01, pMatWaffle.m_11, pMatWaffle.m_21, wafflers+count+1);
+ ++count;
+ }
+ }
+ }
+
+ if (count)
+ {
+ fWafflersUsed = true;
+ // As the last step in the chain we send the triangles to our vertex buffer.
+ wafflers[count-1].SetSink(m_pVB);
+ return &wafflers[0];
+ }
+ else
+ {
+ fWafflersUsed = false;
+ // If we built no wafflers then sink straight into the vertex buffer.
+ return m_pVB;
+ }
+}
+
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::IsEmpty
+//
+// Synopsis: Does our VB have any triangles/lines?
+//
+//-----------------------------------------------------------------------------
+template <class TVertex>
+BOOL
+CHwTVertexBuffer<TVertex>::Builder::IsEmpty()
+{
+ return m_pVB->IsEmpty();
+}
+*/
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddTrapezoid
+//
+// Synopsis: Add a trapezoid to the vertex buffer
+//
+//
+// left edge right edge
+// ___+_________________+___ <<< top edge
+// / + / \ + \
+// / + / \ + \
+// / + / \ + \
+// /__+__/___________________\__+__\ <<< bottom edge
+// + ^^ +
+// delta
+//
+impl CHwVertexBufferBuilder<'_, '_> {
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddTrapezoidStandard
+//
+// Synopsis: See AddTrapezoid. This doesn't do waffling & uses tri strips.
+//
+
+fn AddTrapezoidStandard(&mut self,
+ rPixelYTop: f32, // In: y coordinate of top of trapezoid
+ rPixelXTopLeft: f32, // In: x coordinate for top left
+ rPixelXTopRight: f32, // In: x coordinate for top right
+ rPixelYBottom: f32, // In: y coordinate of bottom of trapezoid
+ rPixelXBottomLeft: f32, // In: x coordinate for bottom left
+ rPixelXBottomRight: f32, // In: x coordinate for bottom right
+ rPixelXLeftDelta: f32, // In: trapezoid expand radius for left edge
+ rPixelXRightDelta: f32 // In: trapezoid expand radius for right edge
+ ) -> HRESULT
+{
+ type TVertex = CD3DVertexXYZDUV2;
+ let hr = S_OK;
+ //TVertex *pVertex;
+
+ IFC!(self.PrepareStratum(
+ rPixelYTop,
+ rPixelYBottom,
+ true, /* Trapezoid */
+ rPixelXTopLeft.min(rPixelXBottomLeft),
+ rPixelXTopRight.max(rPixelXBottomRight),
+ rPixelXTopLeft - rPixelXLeftDelta, rPixelXBottomLeft - rPixelXLeftDelta,
+ rPixelXTopRight + rPixelXRightDelta, rPixelXBottomRight + rPixelXRightDelta
+ ));
+
+ //
+ // Add the vertices
+ //
+
+ let fNeedOutsideGeometry: bool; let fNeedInsideGeometry: bool;
+ fNeedOutsideGeometry = self.NeedOutsideGeometry();
+ fNeedInsideGeometry = self.NeedInsideGeometry();
+
+ //
+ // Fill in the vertices
+ //
+
+ self.m_pVB.AddTrapezoidVertices(
+ OutputVertex{
+ x: rPixelXTopLeft - rPixelXLeftDelta,
+ y: rPixelYTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rPixelXBottomLeft - rPixelXLeftDelta,
+ y: rPixelYBottom,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rPixelXTopLeft + rPixelXLeftDelta,
+ y: rPixelYTop,
+ coverage: FLOAT_ONE,
+ },
+ OutputVertex{
+ x: rPixelXBottomLeft + rPixelXLeftDelta,
+ y: rPixelYBottom,
+ coverage: FLOAT_ONE,
+ }
+ );
+
+
+ if (fNeedInsideGeometry)
+ {
+ self.m_pVB.AddTrapezoidVertices(
+ OutputVertex{
+ x: rPixelXTopLeft + rPixelXLeftDelta,
+ y: rPixelYTop,
+ coverage: FLOAT_ONE,
+ },
+ OutputVertex{
+ x: rPixelXBottomLeft + rPixelXLeftDelta,
+ y: rPixelYBottom,
+ coverage: FLOAT_ONE,
+ },
+ OutputVertex{
+ x: rPixelXTopRight - rPixelXRightDelta,
+ y: rPixelYTop,
+ coverage: FLOAT_ONE,
+ },
+ OutputVertex{
+ x: rPixelXBottomRight - rPixelXRightDelta,
+ y: rPixelYBottom,
+ coverage: FLOAT_ONE,
+ }
+ );
+ }
+
+ self.m_pVB.AddTrapezoidVertices(
+ OutputVertex{
+ x: rPixelXTopRight - rPixelXRightDelta,
+ y: rPixelYTop,
+ coverage: FLOAT_ONE,
+ },
+ OutputVertex{
+ x: rPixelXBottomRight - rPixelXRightDelta,
+ y: rPixelYBottom,
+ coverage: FLOAT_ONE,
+ },
+ OutputVertex{
+ x: rPixelXTopRight + rPixelXRightDelta,
+ y: rPixelYTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rPixelXBottomRight + rPixelXRightDelta,
+ y: rPixelYBottom,
+ coverage: FLOAT_ZERO,
+ }
+ );
+
+ if (!fNeedOutsideGeometry)
+ {
+ //
+ // Duplicate the last vertex. This creates 2 degenerate triangles: one
+ // between vertices 8 and 9 and one connecting this trapezoid to the
+ // next one.
+ //
+
+ //pVertex.push(OutputVertex{
+ // x: rPixelXBottomRight + rPixelXRightDelta,
+ // y: rPixelYBottom,
+ // coverage: FLOAT_ZERO,
+ //});
+ }
+
+ self.m_pVB.AddedNonLineSegment();
+
+//Cleanup:
+ RRETURN!(hr);
+}
+}
+/*
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::AddTrapezoidWaffle
+//
+// Synopsis: See AddTrapezoid. This adds a waffled trapezoid.
+//
+//-----------------------------------------------------------------------------
+template <class TVertex>
+HRESULT
+CHwTVertexBuffer<TVertex>::Builder::AddTrapezoidWaffle(
+ float rPixelYTop, // In: y coordinate of top of trapezoid
+ float rPixelXTopLeft, // In: x coordinate for top left
+ float rPixelXTopRight, // In: x coordinate for top right
+ float rPixelYBottom, // In: y coordinate of bottom of trapezoid
+ float rPixelXBottomLeft, // In: x coordinate for bottom left
+ float rPixelXBottomRight, // In: x coordinate for bottom right
+ float rPixelXLeftDelta, // In: trapezoid expand radius for left edge
+ float rPixelXRightDelta // In: trapezoid expand radius for right edge
+ )
+{
+ HRESULT hr = S_OK;
+
+ // We have 2 (u & v) wafflers per texture coordinate that need waffling.
+ TriangleWaffler<PointXYA> wafflers[NUM_OF_VERTEX_TEXTURE_COORDS(TVertex) * 2];
+ bool fWafflersUsed = false;
+
+ TriangleWaffler<PointXYA>::ISink *pWaffleSinkNoRef = BuildWafflePipeline(wafflers, OUT fWafflersUsed);
+
+ PointXYA vertices[8];
+
+ //
+ // Fill in the strip vertices
+ //
+
+ // Nonstandard coverage mapping and waffling are not supported at the same time.
+ Assert(!NeedOutsideGeometry());
+
+ vertices[0].x = rPixelXTopLeft - rPixelXLeftDelta;
+ vertices[0].y = rPixelYTop;
+ vertices[0].a = 0;
+
+ vertices[1].x = rPixelXBottomLeft - rPixelXLeftDelta;
+ vertices[1].y = rPixelYBottom;
+ vertices[1].a = 0;
+
+ vertices[2].x = rPixelXTopLeft + rPixelXLeftDelta;
+ vertices[2].y = rPixelYTop;
+ vertices[2].a = 1;
+
+ vertices[3].x = rPixelXBottomLeft + rPixelXLeftDelta;
+ vertices[3].y = rPixelYBottom;
+ vertices[3].a = 1;
+
+ vertices[4].x = rPixelXTopRight - rPixelXRightDelta;
+ vertices[4].y = rPixelYTop;
+ vertices[4].a = 1;
+
+ vertices[5].x = rPixelXBottomRight - rPixelXRightDelta;
+ vertices[5].y = rPixelYBottom;
+ vertices[5].a = 1;
+
+ vertices[6].x = rPixelXTopRight + rPixelXRightDelta;
+ vertices[6].y = rPixelYTop;
+ vertices[6].a = 0;
+
+ vertices[7].x = rPixelXBottomRight + rPixelXRightDelta;
+ vertices[7].y = rPixelYBottom;
+ vertices[7].a = 0;
+
+ // Send the triangles in the strip through the waffle pipeline.
+ for (int i = 0; i < 6; ++i)
+ {
+ IFC(pWaffleSinkNoRef->AddTriangle(vertices[i+1], vertices[i], vertices[i+2]));
+ }
+
+Cleanup:
+ RRETURN(hr);
+}
+*/
+impl CHwVertexBufferBuilder<'_, '_> {
+
+ //+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::NeedCoverageGeometry
+//
+// Synopsis: Returns true if the coverage value needs to be rendered
+// based on NeedInsideGeometry() and NeedOutsideGeometry()
+//
+// Two cases where we don't need to generate geometry:
+// 1. NeedInsideGeometry is false, and coverage is c_nShiftSizeSquared.
+// 2. NeedOutsideGeometry is false and coverage is 0
+//
+//-----------------------------------------------------------------------------
+fn NeedCoverageGeometry(&self,
+ nCoverage: INT
+ ) -> bool
+{
+ return (self.NeedInsideGeometry() || nCoverage != c_nShiftSizeSquared)
+ && (self.NeedOutsideGeometry() || nCoverage != 0);
+}
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: NeedOutsideGeometry
+ //
+ // Synopsis: True if we should create geometry with zero alpha for
+ // areas outside the input geometry but within a given
+ // bounding box.
+ //
+ //-------------------------------------------------------------------------
+ fn NeedOutsideGeometry(&self) -> bool
+ {
+ return self.m_fNeedOutsideGeometry;
+ }
+
+ //+------------------------------------------------------------------------
+ //
+ // Member: NeedInsideGeometry
+ //
+ // Synopsis: True if we should create geometry for areas completely
+ // withing the input geometry (i.e. alpha 1.) Should only
+ // be false if NeedOutsideGeometry is true.
+ //
+ //-------------------------------------------------------------------------
+ fn NeedInsideGeometry(&self) -> bool
+ {
+ assert!(self.m_fNeedOutsideGeometry || self.m_fNeedInsideGeometry);
+ return self.m_fNeedInsideGeometry;
+ }
+
+
+
+ // Helpers that handle extra shapes in trapezoid mode.
+ fn PrepareStratum(&mut self,
+ rStratumTop: f32,
+ rStratumBottom: f32,
+ fTrapezoid: bool,
+ rTrapezoidLeft: f32,
+ rTrapezoidRight: f32,
+ rTrapezoidTopLeft: f32, // = 0
+ rTrapezoidBottomLeft: f32, // = 0
+ rTrapezoidTopRight: f32, // = 0
+ rTrapezoidBottomRight: f32, // = 0
+
+ ) -> HRESULT
+ {
+ return if self.NeedOutsideGeometry() {
+ self.PrepareStratumSlow(
+ rStratumTop,
+ rStratumBottom,
+ fTrapezoid,
+ rTrapezoidLeft,
+ rTrapezoidRight,
+ rTrapezoidTopLeft,
+ rTrapezoidBottomLeft,
+ rTrapezoidTopRight,
+ rTrapezoidBottomRight
+ )
+ } else { S_OK };
+ }
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::PrepareStratumSlow
+//
+// Synopsis: Call before producing a new stratum (complex span or trapezoid.)
+// Handles several tasks:
+// 1. Producing between top of complement geometry & the 1st
+// stratum or when a gap between strata occurs (because
+// the geometry is not closed and has horizontal gaps.)
+// Passing in FLT_MAX for rStratumTop and rStratumBottom
+// Fills the gap between the last stratum and the bottom
+// of the outside.
+// 2. Begins and/or ends the triangle strip corresponding to
+// a trapezoid row.
+// 3. Updates status vars m_rCurStratumTop & m_rCurStratumBottom
+//
+// Note: Call PrepareStratum which inlines the check for NeedOutsideGeometry()
+// If NeedOutsideGeometry is false PrepareStratum() does nothing.
+// This (slow) version asserts NeedOutsideGeometry()
+//
+//-----------------------------------------------------------------------------
+fn PrepareStratumSlow(&mut self,
+ rStratumTop: f32,
+ rStratumBottom: f32,
+ fTrapezoid: bool,
+ rTrapezoidLeft: f32,
+ rTrapezoidRight: f32,
+ rTrapezoidTopLeft: f32,
+ rTrapezoidBottomLeft: f32,
+ rTrapezoidTopRight: f32,
+ rTrapezoidBottomRight: f32,
+ ) -> HRESULT
+{
+ type TVertex = OutputVertex;
+ let hr: HRESULT = S_OK;
+
+ assert!(!(rStratumTop > rStratumBottom));
+ assert!(self.NeedOutsideGeometry());
+
+ // There's only once case where a stratum can go "backwards"
+ // and that's when we're done building & calling from
+ // EndBuildingOutside
+
+ let fEndBuildingOutside: f32 = (rStratumBottom == self.OutsideBottom() &&
+ rStratumTop == self.OutsideBottom()) as i32 as f32;
+
+ if (fEndBuildingOutside == 1.)
+ {
+ assert!(!fTrapezoid);
+ }
+ else
+ {
+ assert!(!(rStratumBottom < self.m_rCurStratumBottom));
+ }
+
+ if ( fEndBuildingOutside == 1.
+ || rStratumBottom != self.m_rCurStratumBottom)
+ {
+
+ // New stratum starting now. Two things to do
+ // 1. Close out current trapezoid stratum if necessary.
+ // 2. Begin new trapezoid stratum if necessary.
+
+ if (self.m_rCurStratumTop != f32::MAX)
+ {
+ // we do not clip trapezoids so RIGHT boundary
+ // of the stratus can be outside of m_rcOutsideBounds.
+
+ let rOutsideRight: f32 = self.OutsideRight().max(self.m_rLastTrapezoidRight);
+
+ // End current trapezoid stratum.
+
+ self.m_pVB.AddTrapezoidVertices(
+ OutputVertex{
+ x: self.m_rLastTrapezoidTopRight,
+ y: self.m_rCurStratumTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: self.m_rLastTrapezoidBottomRight,
+ y: self.m_rCurStratumBottom,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rOutsideRight,
+ y: self.m_rCurStratumTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rOutsideRight,
+ y: self.m_rCurStratumBottom,
+ coverage: FLOAT_ZERO,
+ }
+ );
+ }
+ // Compute the gap between where the last stratum ended and where
+ // this one begins.
+ let flGap: f32 = rStratumTop - self.m_rCurStratumBottom;
+
+ if (flGap > 0.)
+ {
+ // The "special" case of a gap at the beginning is caught here
+ // using the sentinel initial value of m_rCurStratumBottom.
+
+ let flRectTop: f32 = if self.m_rCurStratumBottom == -f32::MAX {
+ self.OutsideTop() } else {
+ self.m_rCurStratumBottom };
+ let flRectBot: f32 = (rStratumTop as f32);
+
+ // Produce rectangular for any horizontal intervals in the
+ // outside bounds that have no generated geometry.
+ assert!(self.m_rCurStratumBottom != -f32::MAX || self.m_rCurStratumTop == f32::MAX);
+
+ let outside_left = self.OutsideLeft();
+ let outside_right = self.OutsideRight();
+
+ // Duplicate first vertex.
+ self.m_pVB.AddTrapezoidVertices(
+ OutputVertex{
+ x: outside_left,
+ y: flRectTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: outside_left,
+ y: flRectBot,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: outside_right,
+ y: flRectTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: outside_right,
+ y: flRectBot,
+ coverage: FLOAT_ZERO,
+ }
+ );
+ }
+
+ if (fTrapezoid)
+ {
+
+ // we do not clip trapezoids so left boundary
+ // of the stratus can be outside of m_rcOutsideBounds.
+
+ let rOutsideLeft: f32 = self.OutsideLeft().min(rTrapezoidLeft);
+
+ // Begin new trapezoid stratum.
+
+ self.m_pVB.AddTrapezoidVertices(
+ OutputVertex{
+ x: rOutsideLeft,
+ y: rStratumTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rOutsideLeft,
+ y: rStratumBottom,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rTrapezoidTopLeft,
+ y: rStratumTop,
+ coverage: FLOAT_ZERO,
+ },
+ OutputVertex{
+ x: rTrapezoidBottomLeft,
+ y: rStratumBottom,
+ coverage: FLOAT_ZERO,
+ }
+ );
+ }
+ }
+
+ if (fTrapezoid)
+ {
+ self.m_rLastTrapezoidTopRight = rTrapezoidTopRight;
+ self.m_rLastTrapezoidBottomRight = rTrapezoidBottomRight;
+ self.m_rLastTrapezoidRight = rTrapezoidRight;
+ }
+
+ self.m_rCurStratumTop = if fTrapezoid { rStratumTop } else { f32::MAX };
+ self.m_rCurStratumBottom = rStratumBottom;
+
+ RRETURN!(hr);
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::EndBuildingOutside
+//
+// Synopsis: Finish creating outside geometry.
+// 1. If no geometry was created then just fill bounds.
+// 2. Otherwise:
+// A. End last trapezoid row
+// B. Produce stop stratum
+//
+//-----------------------------------------------------------------------------
+fn EndBuildingOutside(&mut self) -> HRESULT
+{
+ return self.PrepareStratum(
+ self.OutsideBottom(),
+ self.OutsideBottom(),
+ false, /* Not a trapezoid. */
+ 0., 0.,
+ 0., 0.,
+ 0., 0.,
+ );
+}
+
+//+----------------------------------------------------------------------------
+//
+// Member: CHwTVertexBuffer<TVertex>::Builder::EndBuilding
+//
+// Synopsis: Expand all vertices to the full required format and return
+// vertex buffer.
+//
+//-----------------------------------------------------------------------------
+pub fn EndBuilding(&mut self) -> HRESULT
+{
+ let hr = S_OK;
+
+ IFC!(self.EndBuildingOutside());
+
+//Cleanup:
+ RRETURN!(hr);
+}
+
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/lib.rs b/third_party/rust/wpf-gpu-raster/src/lib.rs
new file mode 100644
index 0000000000..4f5a4495aa
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/lib.rs
@@ -0,0 +1,691 @@
+/*!
+Converts a 2D path into a set of vertices of a triangle strip mesh that represents the antialiased fill of that path.
+
+```rust
+ use wpf_gpu_raster::PathBuilder;
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(40., 40.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+```
+
+*/
+#![allow(unused_parens)]
+#![allow(overflowing_literals)]
+#![allow(non_snake_case)]
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![allow(dead_code)]
+#![allow(unused_macros)]
+
+#[macro_use]
+mod fix;
+#[macro_use]
+mod helpers;
+#[macro_use]
+mod real;
+mod bezier;
+#[macro_use]
+mod aarasterizer;
+mod hwrasterizer;
+mod aacoverage;
+mod hwvertexbuffer;
+
+mod types;
+mod geometry_sink;
+mod matrix;
+
+mod nullable_ref;
+
+#[cfg(feature = "c_bindings")]
+pub mod c_bindings;
+
+#[cfg(test)]
+mod tri_rasterize;
+
+use aarasterizer::CheckValidRange28_4;
+use hwrasterizer::CHwRasterizer;
+use hwvertexbuffer::{CHwVertexBuffer, CHwVertexBufferBuilder};
+use real::CFloatFPU;
+use types::{MilFillMode, PathPointTypeStart, MilPoint2F, MilPointAndSizeL, PathPointTypeLine, MilVertexFormat, MilVertexFormatAttribute, DynArray, BYTE, PathPointTypeBezier, PathPointTypeCloseSubpath, CMILSurfaceRect, POINT};
+
+#[repr(C)]
+#[derive(Clone, Debug, Default)]
+pub struct OutputVertex {
+ pub x: f32,
+ pub y: f32,
+ pub coverage: f32
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub enum FillMode {
+ EvenOdd = 0,
+ Winding = 1,
+}
+
+impl Default for FillMode {
+ fn default() -> Self {
+ FillMode::EvenOdd
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct OutputPath {
+ fill_mode: FillMode,
+ points: Box<[POINT]>,
+ types: Box<[BYTE]>,
+}
+
+impl std::hash::Hash for OutputVertex {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.x.to_bits().hash(state);
+ self.y.to_bits().hash(state);
+ self.coverage.to_bits().hash(state);
+ }
+}
+
+pub struct PathBuilder {
+ points: DynArray<POINT>,
+ types: DynArray<BYTE>,
+ initial_point: Option<MilPoint2F>,
+ current_point: Option<MilPoint2F>,
+ in_shape: bool,
+ fill_mode: FillMode,
+ outside_bounds: Option<CMILSurfaceRect>,
+ need_inside: bool,
+ valid_range: bool,
+ rasterization_truncates: bool,
+}
+
+impl PathBuilder {
+ pub fn new() -> Self {
+ Self {
+ points: Vec::new(),
+ types: Vec::new(),
+ initial_point: None,
+ current_point: None,
+ in_shape: false,
+ fill_mode: FillMode::EvenOdd,
+ outside_bounds: None,
+ need_inside: true,
+ valid_range: true,
+ rasterization_truncates: false,
+ }
+ }
+ fn add_point(&mut self, x: f32, y: f32) {
+ self.current_point = Some(MilPoint2F{X: x, Y: y});
+ // Transform from pixel corner at 0.0 to pixel center at 0.0. Scale into 28.4 range.
+ // Validate that the point before rounding is within expected bounds for the rasterizer.
+ let (x, y) = ((x - 0.5) * 16.0, (y - 0.5) * 16.0);
+ self.valid_range = self.valid_range && CheckValidRange28_4(x, y);
+ self.points.push(POINT {
+ x: CFloatFPU::Round(x),
+ y: CFloatFPU::Round(y),
+ });
+ }
+ pub fn line_to(&mut self, x: f32, y: f32) {
+ if let Some(initial_point) = self.initial_point {
+ if !self.in_shape {
+ self.types.push(PathPointTypeStart);
+ self.add_point(initial_point.X, initial_point.Y);
+ self.in_shape = true;
+ }
+ self.types.push(PathPointTypeLine);
+ self.add_point(x, y);
+ } else {
+ self.initial_point = Some(MilPoint2F{X: x, Y: y})
+ }
+ }
+ pub fn move_to(&mut self, x: f32, y: f32) {
+ self.in_shape = false;
+ self.initial_point = Some(MilPoint2F{X: x, Y: y});
+ self.current_point = self.initial_point;
+ }
+ pub fn curve_to(&mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) {
+ let initial_point = match self.initial_point {
+ Some(initial_point) => initial_point,
+ None => MilPoint2F{X:c1x, Y:c1y}
+ };
+ if !self.in_shape {
+ self.types.push(PathPointTypeStart);
+ self.add_point(initial_point.X, initial_point.Y);
+ self.initial_point = Some(initial_point);
+ self.in_shape = true;
+ }
+ self.types.push(PathPointTypeBezier);
+ self.add_point(c1x, c1y);
+ self.add_point(c2x, c2y);
+ self.add_point(x, y);
+ }
+ pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
+ // For now we just implement quad_to on top of curve_to.
+ // Long term we probably want to support quad curves
+ // directly.
+ let c0 = match self.current_point {
+ Some(current_point) => current_point,
+ None => MilPoint2F{X:cx, Y:cy}
+ };
+
+ let c1x = c0.X + (2./3.) * (cx - c0.X);
+ let c1y = c0.Y + (2./3.) * (cy - c0.Y);
+
+ let c2x = x + (2./3.) * (cx - x);
+ let c2y = y + (2./3.) * (cy - y);
+
+ self.curve_to(c1x, c1y, c2x, c2y, x, y);
+ }
+ pub fn close(&mut self) {
+ if self.in_shape {
+ // Only close the path if we are inside a shape. Otherwise, the point
+ // should be safe to elide.
+ if let Some(last) = self.types.last_mut() {
+ *last |= PathPointTypeCloseSubpath;
+ }
+ self.in_shape = false;
+ }
+ // Close must install a new initial point that is the same as the
+ // initial point of the just-closed sub-path. Thus, just leave the
+ // initial point unchanged.
+ self.current_point = self.initial_point;
+ }
+ pub fn set_fill_mode(&mut self, fill_mode: FillMode) {
+ self.fill_mode = fill_mode;
+ }
+ /// Enables rendering geometry for areas outside the shape but
+ /// within the bounds. These areas will be created with
+ /// zero alpha.
+ ///
+ /// This is useful for creating geometry for other blend modes.
+ /// For example:
+ /// - `IN(dest, geometry)` can be done with `outside_bounds` and `need_inside = false`
+ /// - `IN(dest, geometry, alpha)` can be done with `outside_bounds` and `need_inside = true`
+ ///
+ /// Note: trapezoidal areas won't be clipped to outside_bounds
+ pub fn set_outside_bounds(&mut self, outside_bounds: Option<(i32, i32, i32, i32)>, need_inside: bool) {
+ self.outside_bounds = outside_bounds.map(|r| CMILSurfaceRect { left: r.0, top: r.1, right: r.2, bottom: r.3 });
+ self.need_inside = need_inside;
+ }
+
+ /// Set this to true if post vertex shader coordinates are converted to fixed point
+ /// via truncation. This has been observed with OpenGL on AMD GPUs on macOS.
+ pub fn set_rasterization_truncates(&mut self, rasterization_truncates: bool) {
+ self.rasterization_truncates = rasterization_truncates;
+ }
+
+ /// Note: trapezoidal areas won't necessarily be clipped to the clip rect
+ pub fn rasterize_to_tri_list(&self, clip_x: i32, clip_y: i32, clip_width: i32, clip_height: i32) -> Box<[OutputVertex]> {
+ if !self.valid_range {
+ // If any of the points are outside of valid 28.4 range, then just return an empty triangle list.
+ return Box::new([]);
+ }
+ let (x, y, width, height, need_outside) = if let Some(CMILSurfaceRect { left, top, right, bottom }) = self.outside_bounds {
+ let x0 = clip_x.max(left);
+ let y0 = clip_y.max(top);
+ let x1 = (clip_x + clip_width).min(right);
+ let y1 = (clip_y + clip_height).min(bottom);
+ (x0, y0, x1 - x0, y1 - y0, true)
+ } else {
+ (clip_x, clip_y, clip_width, clip_height, false)
+ };
+ rasterize_to_tri_list(self.fill_mode, &self.types, &self.points, x, y, width, height, self.need_inside, need_outside, self.rasterization_truncates, None)
+ .flush_output()
+ }
+
+ pub fn get_path(&mut self) -> Option<OutputPath> {
+ if self.valid_range && !self.points.is_empty() && !self.types.is_empty() {
+ Some(OutputPath {
+ fill_mode: self.fill_mode,
+ points: std::mem::take(&mut self.points).into_boxed_slice(),
+ types: std::mem::take(&mut self.types).into_boxed_slice(),
+ })
+ } else {
+ None
+ }
+ }
+}
+
+// Converts a path that is specified as an array of edge types, each associated with a fixed number
+// of points that are serialized to the points array. Edge types are specified via PathPointType
+// masks, whereas points must be supplied in 28.4 signed fixed-point format. By default, users can
+// fill the inside of the path excluding the outside. It may alternatively be desirable to fill the
+// outside the path out to the clip boundary, optionally keeping the inside. PathBuilder may be
+// used instead as a simpler interface to this function that handles building the path arrays.
+pub fn rasterize_to_tri_list<'a>(
+ fill_mode: FillMode,
+ types: &[BYTE],
+ points: &[POINT],
+ clip_x: i32,
+ clip_y: i32,
+ clip_width: i32,
+ clip_height: i32,
+ need_inside: bool,
+ need_outside: bool,
+ rasterization_truncates: bool,
+ output_buffer: Option<&'a mut [OutputVertex]>,
+) -> CHwVertexBuffer<'a> {
+ let clipRect = MilPointAndSizeL {
+ X: clip_x,
+ Y: clip_y,
+ Width: clip_width,
+ Height: clip_height,
+ };
+
+ let mil_fill_mode = match fill_mode {
+ FillMode::EvenOdd => MilFillMode::Alternate,
+ FillMode::Winding => MilFillMode::Winding,
+ };
+
+ let m_mvfIn: MilVertexFormat = MilVertexFormatAttribute::MILVFAttrXY as MilVertexFormat;
+ let m_mvfGenerated: MilVertexFormat = MilVertexFormatAttribute::MILVFAttrNone as MilVertexFormat;
+ //let mvfaAALocation = MILVFAttrNone;
+ const HWPIPELINE_ANTIALIAS_LOCATION: MilVertexFormatAttribute = MilVertexFormatAttribute::MILVFAttrDiffuse;
+ let mvfaAALocation = HWPIPELINE_ANTIALIAS_LOCATION;
+
+ let outside_bounds = if need_outside {
+ Some(CMILSurfaceRect {
+ left: clip_x,
+ top: clip_y,
+ right: clip_x + clip_width,
+ bottom: clip_y + clip_height,
+ })
+ } else {
+ None
+ };
+
+ let mut vertexBuffer = CHwVertexBuffer::new(rasterization_truncates, output_buffer);
+ {
+ let mut vertexBuilder = CHwVertexBufferBuilder::Create(
+ m_mvfIn, m_mvfIn | m_mvfGenerated, mvfaAALocation, &mut vertexBuffer);
+ vertexBuilder.SetOutsideBounds(outside_bounds.as_ref(), need_inside);
+ vertexBuilder.BeginBuilding();
+ {
+ let mut rasterizer = CHwRasterizer::new(
+ &mut vertexBuilder, mil_fill_mode, None, clipRect);
+ rasterizer.SendGeometry(points, types);
+ }
+ vertexBuilder.EndBuilding();
+ }
+
+ vertexBuffer
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{hash::{Hash, Hasher}, collections::hash_map::DefaultHasher};
+ use crate::{*, tri_rasterize::rasterize_to_mask};
+ fn calculate_hash<T: Hash>(t: &T) -> u64 {
+ let mut s = DefaultHasher::new();
+ t.hash(&mut s);
+ s.finish()
+ }
+ #[test]
+ fn basic() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(10., 30.);
+ p.line_to(30., 30.);
+ p.line_to(30., 10.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 18);
+ //assert_eq!(dbg!(calculate_hash(&result)), 0x5851570566450135);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfbb7c3932059e240);
+ }
+
+ #[test]
+ fn simple() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(40., 40.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ //assert_eq!(dbg!(calculate_hash(&result)), 0x81a9af7769f88e68);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6d1595533d40ef92);
+ }
+
+ #[test]
+ fn rust() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(40., 40.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ //assert_eq!(dbg!(calculate_hash(&result)), 0x81a9af7769f88e68);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6d1595533d40ef92);
+ }
+
+ #[test]
+ fn fill_mode() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(40., 40.);
+ p.line_to(10., 40.);
+ p.close();
+ p.move_to(15., 15.);
+ p.line_to(35., 15.);
+ p.line_to(35., 35.);
+ p.line_to(15., 35.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ //assert_eq!(dbg!(calculate_hash(&result)), 0xb34344234f2f75a8);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xc7bf999c56ccfc34);
+
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(40., 40.);
+ p.line_to(10., 40.);
+ p.close();
+ p.move_to(15., 15.);
+ p.line_to(35., 15.);
+ p.line_to(35., 35.);
+ p.line_to(15., 35.);
+ p.close();
+ p.set_fill_mode(FillMode::Winding);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ //assert_eq!(dbg!(calculate_hash(&result)), 0xee4ecd8a738fc42c);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfafad659db9a2efd);
+
+ }
+
+ #[test]
+ fn range() {
+ // test for a start point out of range
+ let mut p = PathBuilder::new();
+ p.curve_to(8.872974e16, 0., 0., 0., 0., 0.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 0);
+
+ // test for a subsequent point out of range
+ let mut p = PathBuilder::new();
+ p.curve_to(0., 0., 8.872974e16, 0., 0., 0.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 0);
+ }
+
+ #[test]
+ fn multiple_starts() {
+ let mut p = PathBuilder::new();
+ p.line_to(10., 10.);
+ p.move_to(0., 0.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 0);
+ }
+
+ #[test]
+ fn path_closing() {
+ let mut p = PathBuilder::new();
+ p.curve_to(0., 0., 0., 0., 0., 32.0);
+ p.close();
+ p.curve_to(0., 0., 0., 0., 0., 32.0);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 0);
+ }
+
+ #[test]
+ fn curve() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.curve_to(40., 10., 40., 10., 40., 40.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xa92aae8dba7b8cd4);
+ assert_eq!(dbg!(calculate_hash(&result)), 0x8dbc4d23f9bba38d);
+ }
+
+ #[test]
+ fn partial_coverage_last_line() {
+ let mut p = PathBuilder::new();
+
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(40., 39.6);
+ p.line_to(10., 39.6);
+
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 21);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0xfa200c3bae144952);
+ assert_eq!(dbg!(calculate_hash(&result)), 0xf90cb6afaadfb559);
+ }
+
+ #[test]
+ fn delta_upper_bound() {
+ let mut p = PathBuilder::new();
+ p.move_to(-122.3 + 200.,84.285);
+ p.curve_to(-122.3 + 200., 84.285, -122.2 + 200.,86.179, -123.03 + 200., 86.16);
+ p.curve_to(-123.85 + 200., 86.141, -140.3 + 200., 38.066, -160.83 + 200., 40.309);
+ p.curve_to(-160.83 + 200., 40.309, -143.05 + 200., 32.956, -122.3 + 200., 84.285);
+ p.close();
+
+ let result = p.rasterize_to_tri_list(0, 0, 400, 400);
+ assert_eq!(result.len(), 429);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x5e82d98fdb47a796);
+ assert_eq!(dbg!(calculate_hash(&result)), 0x52d52992e249587a);
+ }
+
+
+ #[test]
+ fn self_intersect() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(10., 40.);
+ p.line_to(40., 40.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x49ecc769e1d4ec01);
+ assert_eq!(dbg!(calculate_hash(&result)), 0xf10babef5c619d19);
+ }
+
+ #[test]
+ fn grid() {
+ let mut p = PathBuilder::new();
+
+ for i in 0..200 {
+ let offset = i as f32 * 1.3;
+ p.move_to(0. + offset, -8.);
+ p.line_to(0.5 + offset, -8.);
+ p.line_to(0.5 + offset, 40.);
+ p.line_to(0. + offset, 40.);
+ p.close();
+ }
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 12000);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x5a7df39d9e9292f0);
+ }
+
+ #[test]
+ fn outside() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(10., 40.);
+ p.line_to(40., 40.);
+ p.close();
+ p.set_outside_bounds(Some((0, 0, 50, 50)), false);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x59403ddbb7e1d09a);
+ assert_eq!(dbg!(calculate_hash(&result)), 0x805fd385e47e6f2);
+
+ // ensure that adjusting the outside bounds changes the results
+ p.set_outside_bounds(Some((5, 5, 50, 50)), false);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x59403ddbb7e1d09a);
+ assert_eq!(dbg!(calculate_hash(&result)), 0xcec2ed688999c966);
+ }
+
+ #[test]
+ fn outside_inside() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(40., 10.);
+ p.line_to(10., 40.);
+ p.line_to(40., 40.);
+ p.close();
+ p.set_outside_bounds(Some((0, 0, 50, 50)), true);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x49ecc769e1d4ec01);
+ assert_eq!(dbg!(calculate_hash(&result)), 0xaf76b42a5244d1ec);
+ }
+
+ #[test]
+ fn outside_clipped() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.);
+ p.line_to(10., 40.);
+ p.line_to(90., 40.);
+ p.line_to(40., 10.);
+ p.close();
+ p.set_outside_bounds(Some((0, 0, 50, 50)), false);
+ let result = p.rasterize_to_tri_list(0, 0, 50, 50);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x3d2a08f5d0bac999);
+ assert_eq!(dbg!(calculate_hash(&result)), 0xbd42b934ab52be39);
+ }
+
+ #[test]
+ fn clip_edge() {
+ let mut p = PathBuilder::new();
+ // tests the bigNumerator < 0 case of aarasterizer::ClipEdge
+ p.curve_to(-24., -10., -300., 119., 0.0, 0.0);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ // The edge merging only happens between points inside the enumerate buffer. This means
+ // that the vertex output can depend on the size of the enumerate buffer because there
+ // the number of edges and positions of vertices will change depending on edge merging.
+ if ENUMERATE_BUFFER_NUMBER!() == 32 {
+ assert_eq!(result.len(), 111);
+ } else {
+ assert_eq!(result.len(), 171);
+ }
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x50b887b09a4c16e);
+ }
+
+ #[test]
+ fn enum_buffer_num() {
+ let mut p = PathBuilder::new();
+ p.curve_to(0.0, 0.0, 0.0, 12.0, 0.0, 44.919434);
+ p.line_to(64.0, 36.0 );
+ p.line_to(0.0, 80.0,);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 300);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x659cc742f16b42f2);
+ }
+
+ #[test]
+ fn fill_alternating_empty_interior_pairs() {
+ let mut p = PathBuilder::new();
+ p.line_to( 0., 2. );
+ p.curve_to(0.0, 0.0,1., 6., 0.0, 0.0);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 9);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x726606a662fe46a0);
+ }
+
+ #[test]
+ fn fill_winding_empty_interior_pairs() {
+ let mut p = PathBuilder::new();
+ p.curve_to(45., 61., 0.09, 0., 0., 0.);
+ p.curve_to(45., 61., 0.09, 0., 0., 0.);
+ p.curve_to(0., 0., 0., 38., 0.09, 15.);
+ p.set_fill_mode(FillMode::Winding);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 462);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x651ea4ade5543087);
+ }
+
+ #[test]
+ fn empty_fill() {
+ let mut p = PathBuilder::new();
+ p.move_to(0., 0.);
+ p.line_to(10., 100.);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 0);
+ }
+
+ #[test]
+ fn rasterize_line() {
+ let mut p = PathBuilder::new();
+ p.move_to(1., 1.);
+ p.line_to(2., 1.);
+ p.line_to(2., 2.);
+ p.line_to(1., 2.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ let mask = rasterize_to_mask(&result, 3, 3);
+ assert_eq!(&mask[..], &[0, 0, 0,
+ 0, 255, 0,
+ 0, 0, 0][..]);
+ }
+
+ #[test]
+ fn triangle() {
+ let mut p = PathBuilder::new();
+ p.move_to(1., 10.);
+ p.line_to(100., 13.);
+ p.line_to(1., 16.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x4757b0c5a19b02f0);
+ }
+
+ #[test]
+ fn single_pixel() {
+ let mut p = PathBuilder::new();
+ p.move_to(1.5, 1.5);
+ p.line_to(2., 1.5);
+ p.line_to(2., 2.);
+ p.line_to(1.5, 2.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(result.len(), 3);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 4, 4)), 0x9f481fe5588e341c);
+ }
+
+ #[test]
+ fn traps_outside_bounds() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.0);
+ p.line_to(30., 10.);
+ p.line_to(50., 20.);
+ p.line_to(30., 30.);
+ p.line_to(10., 30.);
+ p.close();
+ // The generated trapezoids are not necessarily clipped to the outside bounds rect
+ // and in this case the outside bounds geometry ends up drawing on top of the
+ // edge geometry which could be considered a bug.
+ p.set_outside_bounds(Some((0, 0, 50, 30)), true);
+ let result = p.rasterize_to_tri_list(0, 0, 100, 100);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 100, 100)), 0x6514e3d79d641f09);
+
+ }
+
+ #[test]
+ fn quad_to() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 10.0);
+ p.quad_to(30., 10., 30., 30.);
+ p.quad_to(10., 30., 30., 30.);
+ p.quad_to(60., 30., 60., 10.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 70, 40);
+ assert_eq!(result.len(), 279);
+ assert_eq!(calculate_hash(&rasterize_to_mask(&result, 70, 40)), 0xbd2eec3cfe9bd30b);
+ }
+
+ #[test]
+ fn close_after_move_to() {
+ let mut p = PathBuilder::new();
+ p.move_to(10., 0.);
+ p.close();
+ p.move_to(0., 0.);
+ p.line_to(0., 10.);
+ p.line_to(10., 10.);
+ p.move_to(10., 0.);
+ p.close();
+ let result = p.rasterize_to_tri_list(0, 0, 20, 20);
+ assert_eq!(result.len(), 27);
+ assert_eq!(dbg!(calculate_hash(&result)), 0xecfdf5bdfa25a1dd);
+ }
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/matrix.rs b/third_party/rust/wpf-gpu-raster/src/matrix.rs
new file mode 100644
index 0000000000..ed873410f8
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/matrix.rs
@@ -0,0 +1,37 @@
+use std::marker::PhantomData;
+
+use crate::types::CoordinateSpace;
+
+pub type CMILMatrix = CMatrix<CoordinateSpace::Shape,CoordinateSpace::Device>;
+#[derive(Default, Clone)]
+pub struct CMatrix<InCoordSpace, OutCoordSpace> {
+ _11: f32, _12: f32, _13: f32, _14: f32,
+ _21: f32, _22: f32, _23: f32 , _24: f32,
+ _31: f32, _32: f32, _33: f32, _34: f32,
+ _41: f32, _42: f32, _43: f32, _44: f32,
+ in_coord: PhantomData<InCoordSpace>,
+ out_coord: PhantomData<OutCoordSpace>
+}
+
+impl<InCoordSpace: Default, OutCoordSpace: Default> CMatrix<InCoordSpace, OutCoordSpace> {
+ pub fn Identity() -> Self { let mut ret: Self = Default::default();
+ ret._11 = 1.;
+ ret._22 = 1.;
+ ret._33 = 1.;
+ ret._44 = 1.;
+ ret
+ }
+ pub fn GetM11(&self) -> f32 { self._11 }
+ pub fn GetM12(&self) -> f32 { self._12 }
+ pub fn GetM21(&self) -> f32 { self._21 }
+ pub fn GetM22(&self) -> f32 { self._22 }
+ pub fn GetDx(&self) -> f32 { self._41 }
+ pub fn GetDy(&self) -> f32 { self._42 }
+
+ pub fn SetM11(&mut self, r: f32) { self._11 = r}
+ pub fn SetM12(&mut self, r: f32) { self._12 = r}
+ pub fn SetM21(&mut self, r: f32) { self._21 = r}
+ pub fn SetM22(&mut self, r: f32) { self._22 = r}
+ pub fn SetDx(&mut self, dx: f32) { self._41 = dx }
+ pub fn SetDy(&mut self, dy: f32) { self._42 = dy }
+} \ No newline at end of file
diff --git a/third_party/rust/wpf-gpu-raster/src/notes b/third_party/rust/wpf-gpu-raster/src/notes
new file mode 100644
index 0000000000..2737f1903e
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/notes
@@ -0,0 +1,12 @@
+crossing goto
+./MultiSpaceRectF.inl:70:5: error: call to implicitly-deleted default constructor of 'union (anonymous union at ./MultiSpaceRectF.inl:138:5)'
+
+
+Rust conversion
+---------------
+CEdge is a singly linked list
+
+Future
+------
+When flatening curves if we try to flatten at integer values
+we can avoid the ComplexSpan code path.
diff --git a/third_party/rust/wpf-gpu-raster/src/nullable_ref.rs b/third_party/rust/wpf-gpu-raster/src/nullable_ref.rs
new file mode 100644
index 0000000000..1e8389e5b7
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/nullable_ref.rs
@@ -0,0 +1,53 @@
+use std::{marker::PhantomData, ops::Deref};
+
+pub struct Ref<'a, T> {
+ ptr: *const T,
+ _phantom: PhantomData<&'a T>
+}
+
+impl<'a, T> Copy for Ref<'a, T> { }
+
+impl<'a, T> Clone for Ref<'a, T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<'a, T> Ref<'a, T> {
+ pub fn new(p: &'a T) -> Self {
+ Ref { ptr: p as *const T, _phantom: PhantomData}
+ }
+ pub unsafe fn null() -> Self {
+ Ref { ptr: std::ptr::null(), _phantom: PhantomData}
+ }
+ pub fn is_null(&self) -> bool {
+ self.ptr.is_null()
+ }
+ pub fn get_ref(self) -> &'a T {
+ unsafe { &*self.ptr }
+ }
+}
+
+impl<'a, T> PartialEq for Ref<'a, T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ptr == other.ptr && self._phantom == other._phantom
+ }
+}
+
+impl<'a, T> PartialOrd for Ref<'a, T> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ match self.ptr.partial_cmp(&other.ptr) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ self._phantom.partial_cmp(&other._phantom)
+ }
+}
+
+impl<'a, T> Deref for Ref<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ unsafe { &*self.ptr }
+ }
+} \ No newline at end of file
diff --git a/third_party/rust/wpf-gpu-raster/src/real.rs b/third_party/rust/wpf-gpu-raster/src/real.rs
new file mode 100644
index 0000000000..a9144ec149
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/real.rs
@@ -0,0 +1,163 @@
+pub mod CFloatFPU {
+ // Maximum allowed argument for SmallRound
+ // const sc_uSmallMax: u32 = 0xFFFFF;
+
+ // Binary representation of static_cast<float>(sc_uSmallMax)
+ const sc_uBinaryFloatSmallMax: u32 = 0x497ffff0;
+
+ fn LargeRound(x: f32) -> i32 {
+ //XXX: the SSE2 version is probably slower than a naive SSE4 implementation that can use roundss
+ #[cfg(target_feature = "sse2")]
+ unsafe {
+ #[cfg(target_arch = "x86")]
+ use std::arch::x86::{__m128, _mm_set_ss, _mm_cvtss_si32, _mm_cvtsi32_ss, _mm_sub_ss, _mm_cmple_ss, _mm_store_ss, _mm_setzero_ps};
+ #[cfg(target_arch = "x86_64")]
+ use std::arch::x86_64::{__m128, _mm_set_ss, _mm_cvtss_si32, _mm_cvtsi32_ss, _mm_sub_ss, _mm_cmple_ss, _mm_store_ss, _mm_setzero_ps};
+
+ let given: __m128 = _mm_set_ss(x); // load given value
+ let result = _mm_cvtss_si32(given);
+ let rounded: __m128 = _mm_setzero_ps(); // convert it to integer (rounding mode doesn't matter)
+ let rounded = _mm_cvtsi32_ss(rounded, result); // convert back to float
+ let diff = _mm_sub_ss(rounded, given); // diff = (rounded - given)
+ let negHalf = _mm_set_ss(-0.5); // load -0.5f
+ let mask = _mm_cmple_ss(diff, negHalf); // get all-ones if (rounded - given) < -0.5f
+ let mut correction: i32 = 0;
+ _mm_store_ss((&mut correction) as *mut _ as *mut _, mask); // get comparison result as integer
+ return result - correction; // correct the result of rounding
+ }
+ #[cfg(not(target_feature = "sse2"))]
+ return (x + 0.5).floor() as i32;
+ }
+
+
+ //+------------------------------------------------------------------------
+//
+// Function: CFloatFPU::SmallRound
+//
+// Synopsis: Convert given floating point value to nearest integer.
+// Half-integers are rounded up.
+//
+// Important: this routine is fast but restricted:
+// given x should be within (-(0x100000-.5) < x < (0x100000-.5))
+//
+// Details: Implementation has abnormal looking that use to confuse
+// many people. However, it indeed works, being tested
+// thoroughly on x86 and ia64 platforms for literally
+// each possible argument values in the given range.
+//
+// More details:
+// Implementation is based on the knowledge of floating point
+// value representation. This 32-bits value consists of three parts:
+// v & 0x80000000 = sign
+// v & 0x7F800000 = exponent
+// v & 0x007FFFFF - mantissa
+//
+// Let N to be a floating point number within -0x400000 <= N <= 0x3FFFFF.
+// The sum (S = 0xC00000 + N) thus will satisfy Ox800000 <= S <= 0xFFFFFF.
+// All the numbers within this range (sometimes referred to as "binade")
+// have same position of most significant bit, i.e. 0x800000.
+// Therefore they are normalized equal way, thus
+// providing the weights on mantissa's bits to be the same
+// as integer numbers have. In other words, to get
+// integer value of floating point S, when Ox800000 <= S <= 0xFFFFFF,
+// we can just throw away the exponent and sign, and add assumed
+// most significant bit (that is always 1 and therefore is not stored
+// in floating point value):
+// (int)S = (<float S as int> & 0x7FFFFF | 0x800000);
+// To get given N in as integer, we need to subtract back
+// the value 0xC00000 that was added in order to obtain
+// proper normalization:
+// N = (<float S as int> & 0x7FFFFF | 0x800000) - 0xC00000.
+// or
+// N = (<float S as int> & 0x7FFFFF ) - 0x400000.
+//
+// Hopefully, the text above explains how
+// following routine works:
+// int SmallRound1(float x)
+// {
+// union
+// {
+// __int32 i;
+// float f;
+// } u;
+//
+// u.f = x + float(0x00C00000);
+// return ((u.i - (int)0x00400000) << 9) >> 9;
+// }
+// Unfortunatelly it is imperfect, due to the way how FPU
+// use to round intermediate calculation results.
+// By default, rounding mode is set to "nearest".
+// This means that when it calculates N+float(0x00C00000),
+// the 80-bit precise result will not fit in 32-bit float,
+// so some least significant bits will be thrown away.
+// Rounding to nearest means that S consisting of intS + fraction,
+// where 0 <= fraction < 1, will be converted to intS
+// when fraction < 0.5 and to intS+1 if fraction > 0.5.
+// What would happen with fraction exactly equal to 0.5?
+// Smart thing: S will go to intS if intS is even and
+// to intS+1 if intS is odd. In other words, half-integers
+// are rounded to nearest even number.
+// This FPU feature apparently is useful to minimize
+// average rounding error when somebody is, say,
+// digitally simulating electrons' behavior in plasma.
+// However for graphics this is not desired.
+//
+// We want to move half-integers up, therefore
+// define SmallRound(x) as {return SmallRound1(x*2+.5) >> 1;}.
+// This may require more comments.
+// Let given x = i+f, where i is integer and f is fraction, 0 <= f < 1.
+// Let's wee what is y = x*2+.5:
+// y = i*2 + (f*2 + .5) = i*2 + g, where g = f*2 + .5;
+// If "f" is in the range 0 <= f < .5 (so correct rounding result should be "i"),
+// then range for "g" is .5 <= g < 1.5. The very first value, .5 will force
+// SmallRound1 result to be "i*2", due to round-to-even rule; the remaining
+// will lead to "i*2+1". Consequent shift will throw away extra "1" and give
+// us desired "i".
+// When "f" in in the range .5 <= f < 1, then 1.5 <= g < 2.5.
+// All these values will round to 2, so SmallRound1 will return (2*i+2),
+// and the final shift will give desired 1+1.
+//
+// To get final routine looking we need to transform the combines
+// expression for u.f:
+// (x*2) + .5 + float(0x00C00000) ==
+// (x + (.25 + double(0x00600000)) )*2
+// Note that the ratio "2" means nothing for following operations,
+// since it affects only exponent bits that are ignored anyway.
+// So we can save some processor cycles avoiding this multiplication.
+//
+// And, the very final beautification:
+// to avoid subtracting 0x00400000 let's ignore this bit.
+// This mean that we effectively decrease available range by 1 bit,
+// but we're chasing for performance and found it acceptable.
+// So
+// return ((u.i - (int)0x00400000) << 9) >> 9;
+// is converted to
+// return ((u.i ) << 10) >> 10;
+// Eventually, will found that final shift by 10 bits may be combined
+// with shift by 1 in the definition {return SmallRound1(x*2+.5) >> 1;},
+// we'll just shift by 11 bits. That's it.
+//
+//-------------------------------------------------------------------------
+fn SmallRound(x: f32) -> i32
+{
+ //AssertPrecisionAndRoundingMode();
+ debug_assert!(-(0x100000 as f64 -0.5) < x as f64 && (x as f64) < (0x100000 as f64 -0.5));
+
+
+ let fi = (x as f64 + (0x00600000 as f64 + 0.25)) as f32;
+ let result = ((fi.to_bits() as i32) << 10) >> 11;
+
+ debug_assert!(x < (result as f32) + 0.5 && x >= (result as f32) - 0.5);
+ return result;
+}
+
+pub fn Round(x: f32) -> i32
+{
+ // cut off sign
+ let xAbs: u32 = x.to_bits() & 0x7FFFFFFF;
+
+ return if xAbs <= sc_uBinaryFloatSmallMax {SmallRound(x)} else {LargeRound(x)};
+}
+}
+
+macro_rules! TOREAL { ($e: expr) => { $e as REAL } }
diff --git a/third_party/rust/wpf-gpu-raster/src/tri_rasterize.rs b/third_party/rust/wpf-gpu-raster/src/tri_rasterize.rs
new file mode 100644
index 0000000000..28fbf6a73f
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/tri_rasterize.rs
@@ -0,0 +1,190 @@
+/* The rasterization code here is based off of piglit/tests/general/triangle-rasterization.cpp:
+
+ /**************************************************************************
+ *
+ * Copyright 2012 VMware, Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ **************************************************************************/
+
+*/
+
+use std::ops::Index;
+use crate::OutputVertex;
+#[derive(Debug)]
+struct Vertex {
+ x: f32,
+ y: f32,
+ coverage: f32
+}
+#[derive(Debug)]
+struct Triangle {
+ v: [Vertex; 3],
+}
+
+impl Index<usize> for Triangle {
+ type Output = Vertex;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.v[index]
+ }
+}
+
+// D3D11 mandates 8 bit subpixel precision:
+// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#CoordinateSnapping
+const FIXED_SHIFT: i32 = 8;
+const FIXED_ONE: f32 = (1 << FIXED_SHIFT) as f32;
+
+/* Proper rounding of float to integer */
+fn iround(mut v: f32) -> i64 {
+ if v > 0.0 {
+ v += 0.5;
+ }
+ if v < 0.0 {
+ v -= 0.5;
+ }
+ return v as i64
+}
+
+/* Based on http://devmaster.net/forums/topic/1145-advanced-rasterization */
+fn rast_triangle(buffer: &mut [u8], width: usize, height: usize, tri: &Triangle) {
+ let center_offset = -0.5;
+
+ let mut coverage1 = tri[0].coverage;
+ let mut coverage2 = tri[1].coverage;
+ let mut coverage3 = tri[2].coverage;
+
+ /* fixed point coordinates */
+ let mut x1 = iround(FIXED_ONE * (tri[0].x + center_offset));
+ let x2 = iround(FIXED_ONE * (tri[1].x + center_offset));
+ let mut x3 = iround(FIXED_ONE * (tri[2].x + center_offset));
+
+ let mut y1 = iround(FIXED_ONE * (tri[0].y + center_offset));
+ let y2 = iround(FIXED_ONE * (tri[1].y + center_offset));
+ let mut y3 = iround(FIXED_ONE * (tri[2].y + center_offset));
+
+
+ /* Force correct vertex order */
+ let cross = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2);
+ if cross > 0 {
+ std::mem::swap(&mut x1, &mut x3);
+ std::mem::swap(&mut y1, &mut y3);
+ // I don't understand why coverage 2 and 3 are swapped instead of 1 and 3
+ std::mem::swap(&mut coverage2, &mut coverage3);
+ } else {
+ std::mem::swap(&mut coverage1, &mut coverage3);
+ }
+
+ /* Deltas */
+ let dx12 = x1 - x2;
+ let dx23 = x2 - x3;
+ let dx31 = x3 - x1;
+
+ let dy12 = y1 - y2;
+ let dy23 = y2 - y3;
+ let dy31 = y3 - y1;
+
+ /* Fixed-point deltas */
+ let fdx12 = dx12 << FIXED_SHIFT;
+ let fdx23 = dx23 << FIXED_SHIFT;
+ let fdx31 = dx31 << FIXED_SHIFT;
+
+ let fdy12 = dy12 << FIXED_SHIFT;
+ let fdy23 = dy23 << FIXED_SHIFT;
+ let fdy31 = dy31 << FIXED_SHIFT;
+
+ /* Bounding rectangle */
+ let mut minx = x1.min(x2).min(x3) >> FIXED_SHIFT;
+ let mut maxx = x1.max(x2).max(x3) >> FIXED_SHIFT;
+
+ let mut miny = y1.min(y2).min(y3) >> FIXED_SHIFT;
+ let mut maxy = y1.max(y2).max(y3) >> FIXED_SHIFT;
+
+ minx = minx.max(0);
+ maxx = maxx.min(width as i64 - 1);
+
+ miny = miny.max(0);
+ maxy = maxy.min(height as i64 - 1);
+
+ /* Half-edge constants */
+ let mut c1 = dy12 * x1 - dx12 * y1;
+ let mut c2 = dy23 * x2 - dx23 * y2;
+ let mut c3 = dy31 * x3 - dx31 * y3;
+
+ /* Correct for top-left filling convention */
+ if dy12 < 0 || (dy12 == 0 && dx12 < 0) { c1 += 1 }
+ if dy23 < 0 || (dy23 == 0 && dx23 < 0) { c2 += 1 }
+ if dy31 < 0 || (dy31 == 0 && dx31 < 0) { c3 += 1 }
+
+ let mut cy1 = c1 + dx12 * (miny << FIXED_SHIFT) - dy12 * (minx << FIXED_SHIFT);
+ let mut cy2 = c2 + dx23 * (miny << FIXED_SHIFT) - dy23 * (minx << FIXED_SHIFT);
+ let mut cy3 = c3 + dx31 * (miny << FIXED_SHIFT) - dy31 * (minx << FIXED_SHIFT);
+ //dbg!(minx, maxx, tri, cross);
+ /* Perform rasterization */
+ let mut buffer = &mut buffer[miny as usize * width..];
+ for _y in miny..=maxy {
+ let mut cx1 = cy1;
+ let mut cx2 = cy2;
+ let mut cx3 = cy3;
+
+ for x in minx..=maxx {
+ if cx1 > 0 && cx2 > 0 && cx3 > 0 {
+ // cross is equal to 2*area of the triangle.
+ // we can normalize cx by 2*area to get barycentric coords.
+ let area = cross.abs() as f32;
+ let bary = (cx1 as f32 / area, cx2 as f32 / area, cx3 as f32 / area);
+ let coverages = coverage1 * bary.0 + coverage2 * bary.1 + coverage3 * bary.2;
+ let color = (coverages * 255. + 0.5) as u8;
+
+ buffer[x as usize] = color;
+ }
+
+ cx1 -= fdy12;
+ cx2 -= fdy23;
+ cx3 -= fdy31;
+ }
+
+ cy1 += fdx12;
+ cy2 += fdx23;
+ cy3 += fdx31;
+
+ buffer = &mut buffer[width..];
+ }
+}
+
+pub fn rasterize_to_mask(vertices: &[OutputVertex], width: u32, height: u32) -> Box<[u8]> {
+ let mut mask = vec![0; (width * height) as usize];
+ for n in (0..vertices.len()).step_by(3) {
+ let tri =
+ [&vertices[n], &vertices[n+1], &vertices[n+2]];
+
+ let tri = Triangle { v: [
+ Vertex { x: tri[0].x, y: tri[0].y, coverage: tri[0].coverage},
+ Vertex { x: tri[1].x, y: tri[1].y, coverage: tri[1].coverage},
+ Vertex { x: tri[2].x, y: tri[2].y, coverage: tri[2].coverage}
+ ]
+ };
+ rast_triangle(&mut mask, width as usize, height as usize, &tri);
+ }
+ mask.into_boxed_slice()
+}
diff --git a/third_party/rust/wpf-gpu-raster/src/types.rs b/third_party/rust/wpf-gpu-raster/src/types.rs
new file mode 100644
index 0000000000..696976f185
--- /dev/null
+++ b/third_party/rust/wpf-gpu-raster/src/types.rs
@@ -0,0 +1,181 @@
+pub(crate) type LONG = i32;
+pub(crate) type INT = i32;
+pub(crate) type UINT = u32;
+pub(crate) type ULONG = u32;
+pub(crate) type DWORD = ULONG;
+pub(crate) type WORD = u16;
+pub(crate) type LONGLONG = i64;
+pub(crate) type ULONGLONG = u64;
+pub(crate) type BYTE = u8;
+pub(crate) type FLOAT = f32;
+pub(crate) type REAL = FLOAT;
+pub(crate) type HRESULT = LONG;
+
+pub(crate) const S_OK: HRESULT = 0;
+pub(crate) const INTSAFE_E_ARITHMETIC_OVERFLOW: HRESULT = 0x80070216;
+pub(crate) const WGXERR_VALUEOVERFLOW: HRESULT = INTSAFE_E_ARITHMETIC_OVERFLOW;
+pub(crate) const WINCODEC_ERR_VALUEOVERFLOW: HRESULT = INTSAFE_E_ARITHMETIC_OVERFLOW;
+const fn MAKE_HRESULT(sev: LONG,fac: LONG,code: LONG) -> HRESULT {
+ ( (((sev)<<31) | ((fac)<<16) | ((code))) )
+}
+
+const FACILITY_WGX: LONG = 0x898;
+
+
+const fn MAKE_WGXHR( sev: LONG, code: LONG) -> HRESULT {
+ MAKE_HRESULT( sev, FACILITY_WGX, (code) )
+}
+
+const fn MAKE_WGXHR_ERR( code: LONG ) -> HRESULT
+{
+ MAKE_WGXHR( 1, code )
+}
+
+pub const WGXHR_CLIPPEDTOEMPTY: HRESULT = MAKE_WGXHR(0, 1);
+pub const WGXHR_EMPTYFILL: HRESULT = MAKE_WGXHR(0, 2);
+pub const WGXHR_INTERNALTEMPORARYSUCCESS: HRESULT = MAKE_WGXHR(0, 3);
+pub const WGXHR_RESETSHAREDHANDLEMANAGER: HRESULT = MAKE_WGXHR(0, 4);
+
+pub const WGXERR_BADNUMBER: HRESULT = MAKE_WGXHR_ERR(0x00A); // 4438
+
+pub fn FAILED(hr: HRESULT) -> bool {
+ hr != S_OK
+}
+pub trait NullPtr {
+ fn make() -> Self;
+}
+
+impl<T> NullPtr for *mut T {
+ fn make() -> Self {
+ std::ptr::null_mut()
+ }
+}
+
+impl<T> NullPtr for *const T {
+ fn make() -> Self {
+ std::ptr::null()
+ }
+}
+
+pub fn NULL<T: NullPtr>() -> T {
+ T::make()
+}
+#[derive(Default, Clone)]
+pub struct RECT {
+ pub left: LONG,
+ pub top: LONG,
+ pub right: LONG,
+ pub bottom: LONG,
+}
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+pub struct POINT {
+ pub x: LONG,
+ pub y: LONG
+}
+#[derive(Clone, Copy)]
+pub struct MilPoint2F
+{
+ pub X: FLOAT,
+ pub Y: FLOAT,
+}
+
+#[derive(Default, Clone)]
+pub struct MilPointAndSizeL
+{
+ pub X: INT,
+ pub Y: INT,
+ pub Width: INT,
+ pub Height: INT,
+}
+
+pub type CMILSurfaceRect = RECT;
+
+#[derive(PartialEq)]
+pub enum MilAntiAliasMode {
+ None = 0,
+ EightByEight = 1,
+}
+#[derive(PartialEq, Clone, Copy)]
+pub enum MilFillMode {
+ Alternate = 0,
+ Winding = 1,
+}
+
+pub const PathPointTypeStart: u8 = 0; // move, 1 point
+pub const PathPointTypeLine: u8 = 1; // line, 1 point
+pub const PathPointTypeBezier: u8 = 3; // default Bezier (= cubic Bezier), 3 points
+pub const PathPointTypePathTypeMask: u8 = 0x07; // type mask (lowest 3 bits).
+pub const PathPointTypeCloseSubpath: u8 = 0x80; // closed flag
+
+
+pub type DynArray<T> = Vec<T>;
+
+pub trait DynArrayExts<T> {
+ fn Reset(&mut self, shrink: bool);
+ fn GetCount(&self) -> usize;
+ fn SetCount(&mut self, count: usize);
+ fn GetDataBuffer(&self) -> &[T];
+}
+
+impl<T> DynArrayExts<T> for DynArray<T> {
+ fn Reset(&mut self, shrink: bool) {
+ self.clear();
+ if shrink {
+ self.shrink_to_fit();
+ }
+ }
+ fn GetCount(&self) -> usize {
+ self.len()
+ }
+ fn SetCount(&mut self, count: usize) {
+ assert!(count <= self.len());
+ self.truncate(count);
+ }
+
+ fn GetDataBuffer(&self) -> &[T] {
+ self
+ }
+}
+
+pub struct CHwPipelineBuilder;
+
+pub mod CoordinateSpace {
+ #[derive(Default, Clone)]
+ pub struct Shape;
+ #[derive(Default, Clone)]
+ pub struct Device;
+}
+
+pub trait IShapeData {
+ fn GetFillMode(&self) -> MilFillMode;
+}
+
+pub type MilVertexFormat = DWORD;
+
+pub enum MilVertexFormatAttribute {
+ MILVFAttrNone = 0x0,
+ MILVFAttrXY = 0x1,
+ MILVFAttrZ = 0x2,
+ MILVFAttrXYZ = 0x3,
+ MILVFAttrNormal = 0x4,
+ MILVFAttrDiffuse = 0x8,
+ MILVFAttrSpecular = 0x10,
+ MILVFAttrUV1 = 0x100,
+ MILVFAttrUV2 = 0x300,
+ MILVFAttrUV3 = 0x700,
+ MILVFAttrUV4 = 0xf00,
+ MILVFAttrUV5 = 0x1f00,
+ MILVFAttrUV6 = 0x3f00,
+ MILVFAttrUV7 = 0x7f00,
+ MILVFAttrUV8 = 0xff00, // Vertex fields that are pre-generated
+
+}
+
+pub struct CHwPipeline;
+
+pub struct CBufferDispenser;
+#[derive(Default)]
+pub struct PointXYA
+{
+ pub x: f32,pub y: f32, pub a: f32,
+}