зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1367734 - Update webrender to cset a54cc729259588dd1ff52c86d0c62cb2a1767137. r=jrmuizel,jerry
In addition to updating webrender and webrender_traits, this patch: - bumps the euclid dependency in webrender_bindings to match webrender - updates the Cargo.lock files and re-vendors third-party rust packages - updates the push_yuv_image callers due to an API change in WR cset a4b9e25.
This commit is contained in:
Родитель
df7247c629
Коммит
cab614e181
|
@ -79,4 +79,4 @@ to make sure that mozjs_sys also has its Cargo.lock file updated if needed, henc
|
|||
the need to run the cargo update command in js/src as well. Hopefully this will
|
||||
be resolved soon.
|
||||
|
||||
Latest Commit: 76a3213080ca5c2e2a612c3023c50c81a111fd55
|
||||
Latest Commit: a54cc729259588dd1ff52c86d0c62cb2a1767137
|
||||
|
|
|
@ -223,7 +223,7 @@ WebRenderImageLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
|
|||
if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
|
||||
// Use the hardware MacIOSurface with YCbCr interleaved format.
|
||||
MOZ_ASSERT(mVideoKeys.Length() == 1);
|
||||
aBuilder.PushYCbCrInterleavedImage(sc.ToRelativeWrRect(rect), clip, mVideoKeys[0], WrYuvColorSpace::Rec601);
|
||||
aBuilder.PushYCbCrInterleavedImage(sc.ToRelativeWrRect(rect), clip, mVideoKeys[0], WrYuvColorSpace::Rec601, filter);
|
||||
} else {
|
||||
// Use libyuv to convert the buffer to rgba format.
|
||||
MOZ_ASSERT(mVideoKeys.Length() == 1);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "webrender"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/servo/webrender"
|
||||
|
@ -17,7 +17,7 @@ app_units = "0.4"
|
|||
bincode = "1.0.0-alpha6"
|
||||
bit-set = "0.4"
|
||||
byteorder = "1.0"
|
||||
euclid = "0.11.2"
|
||||
euclid = "0.13"
|
||||
fnv = "1.0"
|
||||
gleam = "0.4.3"
|
||||
lazy_static = "0.2"
|
||||
|
@ -30,7 +30,7 @@ webrender_traits = {path = "../webrender_traits"}
|
|||
bitflags = "0.7"
|
||||
gamma-lut = "0.2"
|
||||
thread_profiler = "0.1.1"
|
||||
plane-split = "0.3"
|
||||
plane-split = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
angle = {git = "https://github.com/servo/angle", branch = "servo"}
|
||||
|
|
|
@ -16,7 +16,7 @@ use std::env;
|
|||
use std::path::PathBuf;
|
||||
use webrender_traits::{ColorF, Epoch};
|
||||
use webrender_traits::{DeviceIntPoint, DeviceUintSize, LayoutPoint, LayoutRect, LayoutSize};
|
||||
use webrender_traits::{ImageData, ImageDescriptor, ImageFormat};
|
||||
use webrender_traits::{ImageData, ImageDescriptor, ImageFormat, ImageRendering};
|
||||
use webrender_traits::{PipelineId, TransformStyle};
|
||||
use webrender_traits::{YuvColorSpace, YuvData};
|
||||
|
||||
|
@ -282,6 +282,7 @@ fn main() {
|
|||
clip,
|
||||
YuvData::NV12(yuv_chanel1, yuv_chanel2),
|
||||
YuvColorSpace::Rec601,
|
||||
ImageRendering::Auto,
|
||||
);
|
||||
|
||||
let clip = builder.push_clip_region(&bounds, vec![], None);
|
||||
|
@ -290,6 +291,7 @@ fn main() {
|
|||
clip,
|
||||
YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
|
||||
YuvColorSpace::Rec601,
|
||||
ImageRendering::Auto,
|
||||
);
|
||||
|
||||
builder.pop_stacking_context();
|
||||
|
|
|
@ -84,23 +84,16 @@ vec2 clamp_rect(vec2 point, RectWithEndpoint rect) {
|
|||
return clamp(point, rect.p0, rect.p1);
|
||||
}
|
||||
|
||||
// Clamp 2 points at once.
|
||||
vec4 clamp_rect(vec4 points, RectWithSize rect) {
|
||||
return clamp(points, rect.p0.xyxy, rect.p0.xyxy + rect.size.xyxy);
|
||||
}
|
||||
|
||||
vec4 clamp_rect(vec4 points, RectWithEndpoint rect) {
|
||||
return clamp(points, rect.p0.xyxy, rect.p1.xyxy);
|
||||
RectWithEndpoint intersect_rect(RectWithEndpoint a, RectWithEndpoint b) {
|
||||
vec2 p0 = clamp_rect(a.p0, b);
|
||||
vec2 p1 = clamp_rect(a.p1, b);
|
||||
return RectWithEndpoint(p0, max(p0, p1));
|
||||
}
|
||||
|
||||
RectWithSize intersect_rect(RectWithSize a, RectWithSize b) {
|
||||
vec4 p = clamp_rect(vec4(a.p0, a.p0 + a.size), b);
|
||||
return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
|
||||
}
|
||||
|
||||
RectWithEndpoint intersect_rect(RectWithEndpoint a, RectWithEndpoint b) {
|
||||
vec4 p = clamp_rect(vec4(a.p0, a.p1), b);
|
||||
return RectWithEndpoint(p.xy, max(p.xy, p.zw));
|
||||
RectWithEndpoint r = intersect_rect(to_rect_with_endpoint(a),
|
||||
to_rect_with_endpoint(b));
|
||||
return to_rect_with_size(r);
|
||||
}
|
||||
|
||||
float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
|
||||
|
@ -563,6 +556,30 @@ vec4 get_layer_pos(vec2 pos, Layer layer) {
|
|||
return untransform(pos, n, a, layer.inv_transform);
|
||||
}
|
||||
|
||||
// Compute a snapping offset in world space (adjusted to pixel ratio),
|
||||
// given local position on the layer and a snap rectangle.
|
||||
vec2 compute_snap_offset(vec2 local_pos,
|
||||
RectWithSize local_clip_rect,
|
||||
Layer layer,
|
||||
RectWithSize raw_snap_rect) {
|
||||
// Clamp the snap rectangle.
|
||||
RectWithSize snap_rect = intersect_rect(intersect_rect(raw_snap_rect, local_clip_rect),
|
||||
layer.local_clip_rect);
|
||||
// Transform the snap corners to the world space.
|
||||
vec4 world_snap_p0 = layer.transform * vec4(snap_rect.p0, 0.0, 1.0);
|
||||
vec4 world_snap_p1 = layer.transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
|
||||
// Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right
|
||||
vec4 world_snap = uDevicePixelRatio * vec4(world_snap_p0.xy, world_snap_p1.xy) /
|
||||
vec4(world_snap_p0.ww, world_snap_p1.ww);
|
||||
/// World offsets applied to the corners of the snap rectangle.
|
||||
vec4 snap_offsets = floor(world_snap + 0.5) - world_snap;
|
||||
|
||||
/// Compute the position of this vertex inside the snap rectangle.
|
||||
vec2 normalized_snap_pos = (local_pos - snap_rect.p0) / snap_rect.size;
|
||||
/// Compute the actual world offset for this vertex needed to make it snap.
|
||||
return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
|
||||
}
|
||||
|
||||
struct VertexInfo {
|
||||
vec2 local_pos;
|
||||
vec2 screen_pos;
|
||||
|
@ -573,38 +590,32 @@ VertexInfo write_vertex(RectWithSize instance_rect,
|
|||
float z,
|
||||
Layer layer,
|
||||
AlphaBatchTask task,
|
||||
vec2 snap_ref) {
|
||||
RectWithSize snap_rect) {
|
||||
|
||||
// Select the corner of the local rect that we are processing.
|
||||
vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
|
||||
|
||||
// xy = top left corner of the local rect, zw = position of current vertex.
|
||||
vec4 local_p0_pos = vec4(snap_ref, local_pos);
|
||||
|
||||
// Clamp to the two local clip rects.
|
||||
local_p0_pos = clamp_rect(local_p0_pos, local_clip_rect);
|
||||
local_p0_pos = clamp_rect(local_p0_pos, layer.local_clip_rect);
|
||||
vec2 clamped_local_pos = clamp_rect(clamp_rect(local_pos, local_clip_rect),
|
||||
layer.local_clip_rect);
|
||||
|
||||
// Transform the top corner and current vertex to world space.
|
||||
vec4 world_p0 = layer.transform * vec4(local_p0_pos.xy, 0.0, 1.0);
|
||||
world_p0.xyz /= world_p0.w;
|
||||
vec4 world_pos = layer.transform * vec4(local_p0_pos.zw, 0.0, 1.0);
|
||||
world_pos.xyz /= world_pos.w;
|
||||
/// Compute the snapping offset.
|
||||
vec2 snap_offset = compute_snap_offset(clamped_local_pos, local_clip_rect, layer, snap_rect);
|
||||
|
||||
// Convert the world positions to device pixel space. xy=top left corner. zw=current vertex.
|
||||
vec4 device_p0_pos = vec4(world_p0.xy, world_pos.xy) * uDevicePixelRatio;
|
||||
// Transform the current vertex to the world cpace.
|
||||
vec4 world_pos = layer.transform * vec4(clamped_local_pos, 0.0, 1.0);
|
||||
|
||||
// Calculate the distance to snap the vertex by (snap top left corner).
|
||||
vec2 snap_delta = device_p0_pos.xy - floor(device_p0_pos.xy + 0.5);
|
||||
// Convert the world positions to device pixel space.
|
||||
vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
|
||||
|
||||
// Apply offsets for the render task to get correct screen location.
|
||||
vec2 final_pos = device_p0_pos.zw -
|
||||
snap_delta -
|
||||
vec2 final_pos = device_pos + snap_offset -
|
||||
task.screen_space_origin +
|
||||
task.render_target_origin;
|
||||
|
||||
gl_Position = uTransform * vec4(final_pos, z, 1.0);
|
||||
|
||||
VertexInfo vi = VertexInfo(local_p0_pos.zw, device_p0_pos.zw);
|
||||
VertexInfo vi = VertexInfo(clamped_local_pos, device_pos);
|
||||
return vi;
|
||||
}
|
||||
|
||||
|
@ -639,7 +650,7 @@ TransformVertexInfo write_transform_vertex(RectWithSize instance_rect,
|
|||
float z,
|
||||
Layer layer,
|
||||
AlphaBatchTask task,
|
||||
vec2 snap_ref) {
|
||||
RectWithSize snap_rect) {
|
||||
RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
|
||||
|
||||
vec2 current_local_pos, prev_local_pos, next_local_pos;
|
||||
|
@ -698,14 +709,14 @@ TransformVertexInfo write_transform_vertex(RectWithSize instance_rect,
|
|||
adjusted_next_p0,
|
||||
adjusted_next_p1);
|
||||
|
||||
// Calculate the snap amount based on the first vertex as a reference point.
|
||||
vec4 world_p0 = layer.transform * vec4(snap_ref, 0.0, 1.0);
|
||||
vec2 device_p0 = uDevicePixelRatio * world_p0.xy / world_p0.w;
|
||||
vec2 snap_delta = device_p0 - floor(device_p0 + 0.5);
|
||||
vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
|
||||
|
||||
/// Compute the snapping offset.
|
||||
vec2 snap_offset = compute_snap_offset(layer_pos.xy / layer_pos.w,
|
||||
local_clip_rect, layer, snap_rect);
|
||||
|
||||
// Apply offsets for the render task to get correct screen location.
|
||||
vec2 final_pos = device_pos -
|
||||
snap_delta -
|
||||
vec2 final_pos = device_pos + snap_offset -
|
||||
task.screen_space_origin +
|
||||
task.render_target_origin;
|
||||
|
||||
|
@ -713,8 +724,6 @@ TransformVertexInfo write_transform_vertex(RectWithSize instance_rect,
|
|||
|
||||
vLocalBounds = vec4(local_rect.p0, local_rect.p1);
|
||||
|
||||
vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
|
||||
|
||||
return TransformVertexInfo(layer_pos.xyw, device_pos);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
|
||||
vPos = vi.local_pos - prim.local_rect.p0;
|
||||
|
||||
|
|
|
@ -27,15 +27,19 @@ void set_radii(int style,
|
|||
case BORDER_STYLE_RIDGE:
|
||||
case BORDER_STYLE_GROOVE:
|
||||
vRadii1.xy = radii - adjusted_widths;
|
||||
vRadii1.zw = -widths;
|
||||
// See comment in default branch
|
||||
vRadii1.zw = vec2(-100.0);
|
||||
break;
|
||||
case BORDER_STYLE_DOUBLE:
|
||||
vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
|
||||
vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
|
||||
break;
|
||||
default:
|
||||
vRadii1.xy = -widths;
|
||||
vRadii1.zw = -widths;
|
||||
// These aren't needed, so we set them to some reasonably large
|
||||
// negative value so later computations will discard them. This
|
||||
// avoids branches and numerical issues in the fragment shader.
|
||||
vRadii1.xy = vec2(-100.0);
|
||||
vRadii1.zw = vec2(-100.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -267,14 +271,14 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
#else
|
||||
VertexInfo vi = write_vertex(segment_rect,
|
||||
prim.local_clip_rect,
|
||||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
#endif
|
||||
|
||||
vLocalPos = vi.local_pos;
|
||||
|
|
|
@ -184,14 +184,14 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
#else
|
||||
VertexInfo vi = write_vertex(segment_rect,
|
||||
prim.local_clip_rect,
|
||||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
#endif
|
||||
|
||||
vLocalPos = vi.local_pos;
|
||||
|
|
|
@ -13,7 +13,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
|
||||
RenderTaskData child_task = fetch_render_task(prim.user_data1);
|
||||
vUv.z = child_task.data1.x;
|
||||
|
|
|
@ -14,7 +14,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
|
||||
RenderTaskData child_task = fetch_render_task(prim.user_data1);
|
||||
vUv.z = child_task.data1.x;
|
||||
|
|
|
@ -62,7 +62,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
vLocalPos = vi.local_pos;
|
||||
vec2 f = (vi.local_pos.xy - prim.local_rect.p0) / prim.local_rect.size;
|
||||
#else
|
||||
|
@ -71,7 +71,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
|
||||
vec2 f = (vi.local_pos - segment_rect.p0) / segment_rect.size;
|
||||
vPos = vi.local_pos;
|
||||
|
|
|
@ -14,7 +14,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
vLocalPos = vi.local_pos;
|
||||
#else
|
||||
VertexInfo vi = write_vertex(prim.local_rect,
|
||||
|
@ -22,7 +22,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
vLocalPos = vi.local_pos - prim.local_rect.p0;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
|
||||
vPos = vi.local_pos - prim.local_rect.p0;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
vLocalPos = vi.local_pos;
|
||||
#else
|
||||
VertexInfo vi = write_vertex(prim.local_rect,
|
||||
|
@ -21,7 +21,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
#endif
|
||||
|
||||
#ifdef WR_FEATURE_CLIP
|
||||
|
|
|
@ -18,7 +18,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
local_rect.p0);
|
||||
local_rect);
|
||||
vLocalPos = vi.local_pos;
|
||||
vec2 f = (vi.local_pos.xy / vi.local_pos.z - local_rect.p0) / local_rect.size;
|
||||
#else
|
||||
|
@ -27,7 +27,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
local_rect.p0);
|
||||
local_rect);
|
||||
vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
vLocalPos = vi.local_pos;
|
||||
#else
|
||||
VertexInfo vi = write_vertex(prim.local_rect,
|
||||
|
@ -19,7 +19,7 @@ void main(void) {
|
|||
prim.z,
|
||||
prim.layer,
|
||||
prim.task,
|
||||
prim.local_rect.p0);
|
||||
prim.local_rect);
|
||||
vLocalPos = vi.local_pos - prim.local_rect.p0;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -348,7 +348,9 @@ impl FrameBuilder {
|
|||
*e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
|
||||
});
|
||||
|
||||
if all_corners_simple && all_edges_simple {
|
||||
let has_no_curve = radius.is_zero();
|
||||
|
||||
if has_no_curve && all_corners_simple && all_edges_simple {
|
||||
let p0 = rect.origin;
|
||||
let p1 = rect.bottom_right();
|
||||
let rect_width = rect.size.width;
|
||||
|
|
|
@ -13,6 +13,7 @@ use clip_scroll_tree::{ClipScrollTree, ScrollStates};
|
|||
use profiler::TextureCacheProfileCounters;
|
||||
use resource_cache::ResourceCache;
|
||||
use scene::{Scene, SceneProperties};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
|
||||
|
@ -466,7 +467,8 @@ impl Frame {
|
|||
item.rect(),
|
||||
item.clip_region(),
|
||||
info.yuv_data,
|
||||
info.color_space);
|
||||
info.color_space,
|
||||
info.image_rendering);
|
||||
}
|
||||
SpecificDisplayItem::Text(ref text_info) => {
|
||||
context.builder.add_text(clip_and_scroll,
|
||||
|
@ -976,7 +978,10 @@ impl Frame {
|
|||
display_lists,
|
||||
device_pixel_ratio,
|
||||
texture_cache_profile);
|
||||
resource_cache.expire_old_resources(self.id);
|
||||
// Expire any resources that haven't been used for `cache_expiry_frames`.
|
||||
let num_frames_back = self.frame_builder_config.cache_expiry_frames;
|
||||
let expiry_frame = FrameId(cmp::max(num_frames_back, self.id.0) - num_frames_back);
|
||||
resource_cache.expire_old_resources(expiry_frame);
|
||||
frame
|
||||
}
|
||||
|
||||
|
|
|
@ -104,19 +104,7 @@ pub struct FrameBuilderConfig {
|
|||
pub enable_scrollbars: bool,
|
||||
pub default_font_render_mode: FontRenderMode,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl FrameBuilderConfig {
|
||||
pub fn new(enable_scrollbars: bool,
|
||||
default_font_render_mode: FontRenderMode,
|
||||
debug: bool)
|
||||
-> FrameBuilderConfig {
|
||||
FrameBuilderConfig {
|
||||
enable_scrollbars: enable_scrollbars,
|
||||
default_font_render_mode: default_font_render_mode,
|
||||
debug: debug,
|
||||
}
|
||||
}
|
||||
pub cache_expiry_frames: u32,
|
||||
}
|
||||
|
||||
pub struct FrameBuilder {
|
||||
|
@ -1075,7 +1063,8 @@ impl FrameBuilder {
|
|||
rect: LayerRect,
|
||||
clip_region: &ClipRegion,
|
||||
yuv_data: YuvData,
|
||||
color_space: YuvColorSpace) {
|
||||
color_space: YuvColorSpace,
|
||||
image_rendering: ImageRendering) {
|
||||
let format = yuv_data.get_format();
|
||||
let yuv_key = match yuv_data {
|
||||
YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::new(0, 0)],
|
||||
|
@ -1091,6 +1080,7 @@ impl FrameBuilder {
|
|||
yuv_resource_address: GpuStoreAddress(0),
|
||||
format: format,
|
||||
color_space: color_space,
|
||||
image_rendering: image_rendering,
|
||||
};
|
||||
|
||||
let prim_gpu = YuvImagePrimitiveGpu::new(rect.size);
|
||||
|
|
|
@ -0,0 +1,395 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use device::TextureFilter;
|
||||
use frame::FrameId;
|
||||
use platform::font::{FontContext, RasterizedGlyph};
|
||||
use profiler::TextureCacheProfileCounters;
|
||||
use rayon::ThreadPool;
|
||||
use rayon::prelude::*;
|
||||
use resource_cache::ResourceClassCache;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::collections::HashSet;
|
||||
use std::mem;
|
||||
use texture_cache::{TextureCacheItemId, TextureCache};
|
||||
use internal_types::FontTemplate;
|
||||
use webrender_traits::{FontKey, FontRenderMode, ImageData, ImageFormat};
|
||||
use webrender_traits::{ImageDescriptor, ColorF, LayoutPoint};
|
||||
use webrender_traits::{GlyphKey, GlyphOptions, GlyphInstance, GlyphDimensions};
|
||||
|
||||
pub type GlyphCache = ResourceClassCache<GlyphRequest, Option<TextureCacheItemId>>;
|
||||
|
||||
pub struct FontContexts {
|
||||
// These worker are mostly accessed from their corresponding worker threads.
|
||||
// The goal is that there should be no noticeable contention on the muteces.
|
||||
worker_contexts: Vec<Mutex<FontContext>>,
|
||||
|
||||
// This worker should be accessed by threads that don't belong to thre thread pool
|
||||
// (in theory that's only the render backend thread so no contention expected either).
|
||||
shared_context: Mutex<FontContext>,
|
||||
|
||||
// Stored here as a convenience to get the current thread index.
|
||||
workers: Arc<ThreadPool>,
|
||||
}
|
||||
|
||||
impl FontContexts {
|
||||
/// Get access to the font context associated to the current thread.
|
||||
pub fn lock_current_context(&self) -> MutexGuard<FontContext> {
|
||||
let id = self.current_worker_id();
|
||||
self.lock_context(id)
|
||||
}
|
||||
|
||||
/// Get access to any particular font context.
|
||||
///
|
||||
/// The id is ```Some(i)``` where i is an index between 0 and num_worker_contexts
|
||||
/// for font contexts associated to the thread pool, and None for the shared
|
||||
/// global font context for use outside of the thread pool.
|
||||
pub fn lock_context(&self, id: Option<usize>) -> MutexGuard<FontContext> {
|
||||
match id {
|
||||
Some(index) => self.worker_contexts[index].lock().unwrap(),
|
||||
None => self.shared_context.lock().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get access to the font context usable outside of the thread pool.
|
||||
pub fn lock_shared_context(&self) -> MutexGuard<FontContext> {
|
||||
self.shared_context.lock().unwrap()
|
||||
}
|
||||
|
||||
// number of contexts associated to workers
|
||||
pub fn num_worker_contexts(&self) -> usize {
|
||||
self.worker_contexts.len()
|
||||
}
|
||||
|
||||
fn current_worker_id(&self) -> Option<usize> {
|
||||
self.workers.current_thread_index()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlyphRasterizer {
|
||||
workers: Arc<ThreadPool>,
|
||||
font_contexts: Arc<FontContexts>,
|
||||
|
||||
// Receives the rendered glyphs.
|
||||
glyph_rx: Receiver<Vec<GlyphRasterJob>>,
|
||||
glyph_tx: Sender<Vec<GlyphRasterJob>>,
|
||||
|
||||
// Maintain a set of glyphs that have been requested this
|
||||
// frame. This ensures the glyph thread won't rasterize
|
||||
// the same glyph more than once in a frame. This is required
|
||||
// because the glyph cache hash table is not updated
|
||||
// until the end of the frame when we wait for glyph requests
|
||||
// to be resolved.
|
||||
pending_glyphs: HashSet<GlyphRequest>,
|
||||
|
||||
// We defer removing fonts to the end of the frame so that:
|
||||
// - this work is done outside of the critical path,
|
||||
// - we don't have to worry about the ordering of events if a font is used on
|
||||
// a frame where it is used (although it seems unlikely).
|
||||
fonts_to_remove: Vec<FontKey>,
|
||||
}
|
||||
|
||||
impl GlyphRasterizer {
|
||||
pub fn new(workers: Arc<ThreadPool>) -> Self {
|
||||
let (glyph_tx, glyph_rx) = channel();
|
||||
|
||||
let num_workers = workers.current_num_threads();
|
||||
let mut contexts = Vec::with_capacity(num_workers);
|
||||
|
||||
for _ in 0..num_workers {
|
||||
contexts.push(Mutex::new(FontContext::new()));
|
||||
}
|
||||
|
||||
GlyphRasterizer {
|
||||
font_contexts: Arc::new(
|
||||
FontContexts {
|
||||
worker_contexts: contexts,
|
||||
shared_context: Mutex::new(FontContext::new()),
|
||||
workers: Arc::clone(&workers),
|
||||
}
|
||||
),
|
||||
glyph_rx: glyph_rx,
|
||||
glyph_tx: glyph_tx,
|
||||
pending_glyphs: HashSet::new(),
|
||||
workers: workers,
|
||||
fonts_to_remove: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
|
||||
let font_contexts = Arc::clone(&self.font_contexts);
|
||||
// It's important to synchronously add the font for the shared context because
|
||||
// we use it to check that fonts have been properly added when requesting glyphs.
|
||||
font_contexts.lock_shared_context().add_font(&font_key, &template);
|
||||
|
||||
// TODO: this locks each font context while adding the font data, probably not a big deal,
|
||||
// but if there is contention on this lock we could easily have a queue of per-context
|
||||
// operations to add and delete fonts, and have these queues lazily processed by each worker
|
||||
// before rendering a glyph.
|
||||
// We can also move this into a worker to free up some cycles in the calling (render backend)
|
||||
// thread.
|
||||
for i in 0..font_contexts.num_worker_contexts() {
|
||||
font_contexts.lock_context(Some(i)).add_font(&font_key, &template);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_font(&mut self, font_key: FontKey) {
|
||||
self.fonts_to_remove.push(font_key);
|
||||
}
|
||||
|
||||
pub fn request_glyphs(
|
||||
&mut self,
|
||||
glyph_cache: &mut GlyphCache,
|
||||
current_frame_id: FrameId,
|
||||
font_key: FontKey,
|
||||
size: Au,
|
||||
color: ColorF,
|
||||
glyph_instances: &[GlyphInstance],
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
) {
|
||||
assert!(self.font_contexts.lock_shared_context().has_font(&font_key));
|
||||
|
||||
let mut glyphs = Vec::with_capacity(glyph_instances.len());
|
||||
|
||||
{
|
||||
// TODO: If this takes too long we can resurect a dedicated glyph
|
||||
// dispatch thread, hopefully not.
|
||||
profile_scope!("glyph-requests");
|
||||
|
||||
// select glyphs that have not been requested yet.
|
||||
for glyph in glyph_instances {
|
||||
let glyph_request = GlyphRequest::new(
|
||||
font_key,
|
||||
size,
|
||||
color,
|
||||
glyph.index,
|
||||
glyph.point,
|
||||
render_mode,
|
||||
glyph_options,
|
||||
);
|
||||
|
||||
glyph_cache.mark_as_needed(&glyph_request, current_frame_id);
|
||||
if !glyph_cache.contains_key(&glyph_request) && !self.pending_glyphs.contains(&glyph_request) {
|
||||
self.pending_glyphs.insert(glyph_request.clone());
|
||||
glyphs.push(glyph_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if glyphs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let font_contexts = Arc::clone(&self.font_contexts);
|
||||
let glyph_tx = self.glyph_tx.clone();
|
||||
// spawn an async task to get off of the render backend thread as early as
|
||||
// possible and in that task use rayon's fork join dispatch to rasterize the
|
||||
// glyphs in the thread pool.
|
||||
self.workers.spawn_async(move || {
|
||||
let jobs = glyphs.par_iter().map(|request: &GlyphRequest| {
|
||||
profile_scope!("glyph-raster");
|
||||
let mut context = font_contexts.lock_current_context();
|
||||
let job = GlyphRasterJob {
|
||||
request: request.clone(),
|
||||
result: context.rasterize_glyph(
|
||||
&request.key,
|
||||
request.render_mode,
|
||||
request.glyph_options
|
||||
),
|
||||
};
|
||||
|
||||
// Sanity check.
|
||||
if let Some(ref glyph) = job.result {
|
||||
let bpp = 4; // We always render glyphs in 32 bits RGBA format.
|
||||
assert_eq!(glyph.bytes.len(), bpp * (glyph.width * glyph.height) as usize);
|
||||
}
|
||||
|
||||
job
|
||||
}).collect();
|
||||
|
||||
glyph_tx.send(jobs).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_glyph_dimensions(&mut self, glyph_key: &GlyphKey) -> Option<GlyphDimensions> {
|
||||
self.font_contexts.lock_shared_context().get_glyph_dimensions(glyph_key)
|
||||
}
|
||||
|
||||
pub fn resolve_glyphs(
|
||||
&mut self,
|
||||
current_frame_id: FrameId,
|
||||
glyph_cache: &mut GlyphCache,
|
||||
texture_cache: &mut TextureCache,
|
||||
texture_cache_profile: &mut TextureCacheProfileCounters,
|
||||
) {
|
||||
let mut rasterized_glyphs = Vec::with_capacity(self.pending_glyphs.len());
|
||||
|
||||
// Pull rasterized glyphs from the queue.
|
||||
while !self.pending_glyphs.is_empty() {
|
||||
// TODO: rather than blocking until all pending glyphs are available
|
||||
// we could try_recv and steal work from the thread pool to take advantage
|
||||
// of the fact that this thread is alive and we avoid the added latency
|
||||
// of blocking it.
|
||||
let raster_jobs = self.glyph_rx.recv().expect("BUG: Should be glyphs pending!");
|
||||
for job in raster_jobs {
|
||||
debug_assert!(self.pending_glyphs.contains(&job.request));
|
||||
self.pending_glyphs.remove(&job.request);
|
||||
|
||||
rasterized_glyphs.push(job);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the glyphs are always processed in the same
|
||||
// order for a given text run (since iterating a hash set doesn't
|
||||
// guarantee order). This can show up as very small float inaccuacry
|
||||
// differences in rasterizers due to the different coordinates
|
||||
// that text runs get associated with by the texture cache allocator.
|
||||
rasterized_glyphs.sort_by(|a, b| a.request.cmp(&b.request));
|
||||
|
||||
// Update the caches.
|
||||
for job in rasterized_glyphs {
|
||||
let image_id = job.result.and_then(
|
||||
|glyph| if glyph.width > 0 && glyph.height > 0 {
|
||||
let image_id = texture_cache.new_item_id();
|
||||
texture_cache.insert(
|
||||
image_id,
|
||||
ImageDescriptor {
|
||||
width: glyph.width,
|
||||
height: glyph.height,
|
||||
stride: None,
|
||||
format: ImageFormat::RGBA8,
|
||||
is_opaque: false,
|
||||
offset: 0,
|
||||
},
|
||||
TextureFilter::Linear,
|
||||
ImageData::Raw(Arc::new(glyph.bytes)),
|
||||
texture_cache_profile,
|
||||
);
|
||||
Some(image_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
);
|
||||
|
||||
glyph_cache.insert(job.request, image_id, current_frame_id);
|
||||
}
|
||||
|
||||
// Now that we are done with the critical path (rendering the glyphs),
|
||||
// we can schedule removing the fonts if needed.
|
||||
if !self.fonts_to_remove.is_empty() {
|
||||
let font_contexts = Arc::clone(&self.font_contexts);
|
||||
let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
|
||||
self.workers.spawn_async(move || {
|
||||
for font_key in &fonts_to_remove {
|
||||
font_contexts.lock_shared_context().delete_font(font_key);
|
||||
}
|
||||
for i in 0..font_contexts.num_worker_contexts() {
|
||||
let mut context = font_contexts.lock_context(Some(i));
|
||||
for font_key in &fonts_to_remove {
|
||||
context.delete_font(font_key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FontContext {
|
||||
fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
|
||||
match template {
|
||||
&FontTemplate::Raw(ref bytes, index) => {
|
||||
self.add_raw_font(&font_key, &**bytes, index);
|
||||
}
|
||||
&FontTemplate::Native(ref native_font_handle) => {
|
||||
self.add_native_font(&font_key, (*native_font_handle).clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
|
||||
pub struct GlyphRequest {
|
||||
pub key: GlyphKey,
|
||||
pub render_mode: FontRenderMode,
|
||||
pub glyph_options: Option<GlyphOptions>,
|
||||
}
|
||||
|
||||
impl GlyphRequest {
|
||||
pub fn new(
|
||||
font_key: FontKey,
|
||||
size: Au,
|
||||
color: ColorF,
|
||||
index: u32,
|
||||
point: LayoutPoint,
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>,
|
||||
) -> GlyphRequest {
|
||||
GlyphRequest {
|
||||
key: GlyphKey::new(font_key, size, color, index, point, render_mode),
|
||||
render_mode: render_mode,
|
||||
glyph_options: glyph_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GlyphRasterJob {
|
||||
request: GlyphRequest,
|
||||
result: Option<RasterizedGlyph>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raterize_200_glyphs() {
|
||||
// This test loads a font from disc, the renders 4 requests containing
|
||||
// 50 glyphs each, deletes the font and waits for the result.
|
||||
|
||||
use rayon::Configuration;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let workers = Arc::new(ThreadPool::new(Configuration::new()).unwrap());
|
||||
let mut glyph_rasterizer = GlyphRasterizer::new(workers);
|
||||
let mut glyph_cache = GlyphCache::new();
|
||||
|
||||
let mut font_file = File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
|
||||
let mut font_data = vec![];
|
||||
font_file.read_to_end(&mut font_data).expect("failed to read font file");
|
||||
|
||||
let font_key = FontKey::new(0, 0);
|
||||
glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
|
||||
|
||||
let frame_id = FrameId(1);
|
||||
|
||||
let mut glyph_instances = Vec::with_capacity(200);
|
||||
for i in 0..200 {
|
||||
glyph_instances.push(GlyphInstance {
|
||||
index: i, // It doesn't matter which glyphs we are actually rendering.
|
||||
point: LayoutPoint::new(0.0, 0.0),
|
||||
});
|
||||
}
|
||||
|
||||
for i in 0..4 {
|
||||
glyph_rasterizer.request_glyphs(
|
||||
&mut glyph_cache,
|
||||
frame_id,
|
||||
font_key,
|
||||
Au::from_px(32),
|
||||
ColorF::new(0.0, 0.0, 0.0, 1.0),
|
||||
&glyph_instances[(50 * i)..(50 * (i + 1))],
|
||||
FontRenderMode::Subpixel,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
glyph_rasterizer.delete_font(font_key);
|
||||
|
||||
glyph_rasterizer.resolve_glyphs(
|
||||
frame_id,
|
||||
&mut glyph_cache,
|
||||
&mut TextureCache::new(4096),
|
||||
&mut TextureCacheProfileCounters::new(),
|
||||
);
|
||||
}
|
|
@ -57,6 +57,7 @@ mod frame;
|
|||
mod frame_builder;
|
||||
mod freelist;
|
||||
mod geometry;
|
||||
mod glyph_rasterizer;
|
||||
mod gpu_store;
|
||||
mod internal_types;
|
||||
mod mask_cache;
|
||||
|
|
|
@ -26,6 +26,10 @@ pub struct FontContext {
|
|||
gamma_lut: GammaLut,
|
||||
}
|
||||
|
||||
// core text is safe to use on multiple threads and non-shareable resources are
|
||||
// all hidden inside their font context.
|
||||
unsafe impl Send for FontContext {}
|
||||
|
||||
pub struct RasterizedGlyph {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
|
@ -76,6 +80,22 @@ fn get_glyph_metrics(ct_font: &CTFont,
|
|||
subpixel_point: &SubpixelPoint) -> GlyphMetrics {
|
||||
let bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]);
|
||||
|
||||
if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() ||
|
||||
bounds.size.width.is_nan() || bounds.size.height.is_nan() {
|
||||
// If an unexpected glyph index is requested, core text will return NaN values
|
||||
// which causes us to do bad thing as the value is cast into an integer and
|
||||
// overflow when expanding the bounds a few lines below.
|
||||
// Instead we are better off returning zero-sized metrics because this special
|
||||
// case is handled by the callers of this method.
|
||||
return GlyphMetrics {
|
||||
rasterized_left: 0,
|
||||
rasterized_width: 0,
|
||||
rasterized_height: 0,
|
||||
rasterized_ascent: 0,
|
||||
rasterized_descent: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let (x_offset, y_offset) = subpixel_point.to_f64();
|
||||
|
||||
// First round out to pixel boundaries
|
||||
|
@ -127,6 +147,10 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn has_font(&self, font_key: &FontKey) -> bool {
|
||||
self.cg_fonts.contains_key(font_key)
|
||||
}
|
||||
|
||||
pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8], index: u32) {
|
||||
if self.cg_fonts.contains_key(font_key) {
|
||||
return
|
||||
|
|
|
@ -27,6 +27,11 @@ pub struct FontContext {
|
|||
faces: HashMap<FontKey, Face>,
|
||||
}
|
||||
|
||||
// FreeType resources are safe to move between threads as long as they
|
||||
// are not concurrently accessed. In our case, everything is hidden inside
|
||||
// a given FontContext so it is safe to move the latter between threads.
|
||||
unsafe impl Send for FontContext {}
|
||||
|
||||
pub struct RasterizedGlyph {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
|
@ -63,6 +68,10 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn has_font(&self, font_key: &FontKey) -> bool {
|
||||
self.faces.contains_key(font_key)
|
||||
}
|
||||
|
||||
pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: &[u8], index: u32) {
|
||||
if !self.faces.contains_key(&font_key) {
|
||||
let mut face: FT_Face = ptr::null_mut();
|
||||
|
@ -171,7 +180,11 @@ impl FontContext {
|
|||
return None;
|
||||
}
|
||||
|
||||
let dimensions = Self::get_glyph_dimensions_impl(slot).unwrap();
|
||||
let dimensions = match Self::get_glyph_dimensions_impl(slot) {
|
||||
Some(val) => val,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let bitmap = unsafe { &(*slot).bitmap };
|
||||
let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
|
||||
info!("Rasterizing {:?} as {:?} with dimensions {:?}", key, render_mode, dimensions);
|
||||
|
|
|
@ -24,6 +24,10 @@ pub struct FontContext {
|
|||
gdi_gamma_lut: GammaLut,
|
||||
}
|
||||
|
||||
// DirectWrite is safe to use on multiple threads and non-shareable resources are
|
||||
// all hidden inside their font context.
|
||||
unsafe impl Send for FontContext {}
|
||||
|
||||
pub struct RasterizedGlyph {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
|
@ -117,6 +121,10 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn has_font(&self, font_key: &FontKey) -> bool {
|
||||
self.fonts.contains_key(font_key)
|
||||
}
|
||||
|
||||
pub fn add_raw_font(&mut self, font_key: &FontKey, data: &[u8], index: u32) {
|
||||
if self.fonts.contains_key(font_key) {
|
||||
return
|
||||
|
@ -271,9 +279,11 @@ impl FontContext {
|
|||
let width = (bounds.right - bounds.left) as usize;
|
||||
let height = (bounds.bottom - bounds.top) as usize;
|
||||
|
||||
// We should not get here since glyph_dimensions would return
|
||||
// None for empty glyphs.
|
||||
assert!(width > 0 && height > 0);
|
||||
// Alpha texture bounds can sometimes return an empty rect
|
||||
// Such as for spaces
|
||||
if width == 0 || height == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
|
||||
|
||||
|
|
|
@ -197,6 +197,8 @@ pub struct YuvImagePrimitiveCpu {
|
|||
// The first address of yuv resource_address. Use "yuv_resource_address + N-th" to get the N-th channel data.
|
||||
// e.g. yuv_resource_address + 0 => y channel resource_address
|
||||
pub yuv_resource_address: GpuStoreAddress,
|
||||
|
||||
pub image_rendering: ImageRendering,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -1092,7 +1094,7 @@ impl PrimitiveStore {
|
|||
&mut deferred_resolves,
|
||||
image_cpu.yuv_key[channel],
|
||||
resource_address,
|
||||
ImageRendering::Auto,
|
||||
image_cpu.image_rendering,
|
||||
None);
|
||||
// texture_id
|
||||
image_cpu.yuv_texture_id[channel] = texture_id;
|
||||
|
@ -1330,7 +1332,7 @@ impl PrimitiveStore {
|
|||
let channel_num = image_cpu.format.get_plane_num();
|
||||
debug_assert!(channel_num <= 3);
|
||||
for channel in 0..channel_num {
|
||||
resource_cache.request_image(image_cpu.yuv_key[channel], ImageRendering::Auto, None);
|
||||
resource_cache.request_image(image_cpu.yuv_key[channel], image_cpu.image_rendering, None);
|
||||
}
|
||||
|
||||
// TODO(nical): Currently assuming no tile_spacing for yuv images.
|
||||
|
|
|
@ -65,8 +65,8 @@ pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
|
|||
const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
|
||||
const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { label: "C_Clip", color: debug_colors::PURPLE };
|
||||
const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "C_TextRun", color: debug_colors::MISTYROSE };
|
||||
const GPU_TAG_INIT: GpuProfileTag = GpuProfileTag { label: "Init", color: debug_colors::WHITE };
|
||||
const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { label: "Target", color: debug_colors::SLATEGREY };
|
||||
const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { label: "target", color: debug_colors::SLATEGREY };
|
||||
const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag { label: "data init", color: debug_colors::LIGHTGREY };
|
||||
const GPU_TAG_PRIM_RECT: GpuProfileTag = GpuProfileTag { label: "Rect", color: debug_colors::RED };
|
||||
const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag { label: "Image", color: debug_colors::GREEN };
|
||||
const GPU_TAG_PRIM_YUV_IMAGE: GpuProfileTag = GpuProfileTag { label: "YuvImage", color: debug_colors::DARKGREEN };
|
||||
|
@ -575,6 +575,7 @@ pub struct Renderer {
|
|||
max_recorded_profiles: usize,
|
||||
clear_framebuffer: bool,
|
||||
clear_color: ColorF,
|
||||
enable_clear_scissor: bool,
|
||||
debug: DebugRenderer,
|
||||
render_target_debug: bool,
|
||||
enable_batcher: bool,
|
||||
|
@ -1055,9 +1056,12 @@ impl Renderer {
|
|||
(false, _) => FontRenderMode::Mono,
|
||||
};
|
||||
|
||||
let config = FrameBuilderConfig::new(options.enable_scrollbars,
|
||||
default_font_render_mode,
|
||||
options.debug);
|
||||
let config = FrameBuilderConfig {
|
||||
enable_scrollbars: options.enable_scrollbars,
|
||||
default_font_render_mode,
|
||||
debug: options.debug,
|
||||
cache_expiry_frames: options.cache_expiry_frames,
|
||||
};
|
||||
|
||||
let device_pixel_ratio = options.device_pixel_ratio;
|
||||
let render_target_debug = options.render_target_debug;
|
||||
|
@ -1130,6 +1134,7 @@ impl Renderer {
|
|||
max_recorded_profiles: options.max_recorded_profiles,
|
||||
clear_framebuffer: options.clear_framebuffer,
|
||||
clear_color: options.clear_color,
|
||||
enable_clear_scissor: options.enable_clear_scissor,
|
||||
last_time: 0,
|
||||
color_render_targets: Vec::new(),
|
||||
alpha_render_targets: Vec::new(),
|
||||
|
@ -1276,31 +1281,36 @@ impl Renderer {
|
|||
if let Some(ref mut frame) = frame.frame {
|
||||
let mut profile_timers = RendererProfileTimers::new();
|
||||
|
||||
// Block CPU waiting for last frame's GPU profiles to arrive.
|
||||
// In general this shouldn't block unless heavily GPU limited.
|
||||
if let Some((gpu_frame_id, samples)) = self.gpu_profile.build_samples() {
|
||||
if self.max_recorded_profiles > 0 {
|
||||
while self.gpu_profiles.len() >= self.max_recorded_profiles {
|
||||
self.gpu_profiles.pop_front();
|
||||
{
|
||||
//Note: avoiding `self.gpu_profile.add_marker` - it would block here
|
||||
let _gm = GpuMarker::new(self.device.rc_gl(), "build samples");
|
||||
// Block CPU waiting for last frame's GPU profiles to arrive.
|
||||
// In general this shouldn't block unless heavily GPU limited.
|
||||
if let Some((gpu_frame_id, samples)) = self.gpu_profile.build_samples() {
|
||||
if self.max_recorded_profiles > 0 {
|
||||
while self.gpu_profiles.len() >= self.max_recorded_profiles {
|
||||
self.gpu_profiles.pop_front();
|
||||
}
|
||||
self.gpu_profiles.push_back(GpuProfile::new(gpu_frame_id, &samples));
|
||||
}
|
||||
self.gpu_profiles.push_back(GpuProfile::new(gpu_frame_id, &samples));
|
||||
profile_timers.gpu_samples = samples;
|
||||
}
|
||||
profile_timers.gpu_samples = samples;
|
||||
}
|
||||
|
||||
let cpu_frame_id = profile_timers.cpu_time.profile(|| {
|
||||
let cpu_frame_id = self.device.begin_frame(frame.device_pixel_ratio);
|
||||
self.gpu_profile.begin_frame(cpu_frame_id);
|
||||
{
|
||||
let _gm = self.gpu_profile.add_marker(GPU_TAG_INIT);
|
||||
let cpu_frame_id = {
|
||||
let _gm = GpuMarker::new(self.device.rc_gl(), "begin frame");
|
||||
let frame_id = self.device.begin_frame(frame.device_pixel_ratio);
|
||||
self.gpu_profile.begin_frame(frame_id);
|
||||
|
||||
self.device.disable_scissor();
|
||||
self.device.disable_depth();
|
||||
self.device.set_blend(false);
|
||||
|
||||
//self.update_shaders();
|
||||
self.update_texture_cache();
|
||||
}
|
||||
|
||||
frame_id
|
||||
};
|
||||
|
||||
self.draw_tile_frame(frame, &framebuffer_size);
|
||||
|
||||
|
@ -1337,7 +1347,10 @@ impl Renderer {
|
|||
let debug_size = DeviceUintSize::new(framebuffer_size.width as u32,
|
||||
framebuffer_size.height as u32);
|
||||
self.debug.render(&mut self.device, &debug_size);
|
||||
self.device.end_frame();
|
||||
{
|
||||
let _gm = GpuMarker::new(self.device.rc_gl(), "end frame");
|
||||
self.device.end_frame();
|
||||
}
|
||||
self.last_time = current_time;
|
||||
}
|
||||
|
||||
|
@ -1693,7 +1706,7 @@ impl Renderer {
|
|||
self.device.set_blend(false);
|
||||
self.device.set_blend_mode_alpha();
|
||||
match render_target {
|
||||
Some(..) => {
|
||||
Some(..) if self.enable_clear_scissor => {
|
||||
// TODO(gw): Applying a scissor rect and minimal clear here
|
||||
// is a very large performance win on the Intel and nVidia
|
||||
// GPUs that I have tested with. It's possible it may be a
|
||||
|
@ -1703,7 +1716,7 @@ impl Renderer {
|
|||
Some(1.0),
|
||||
target.used_rect());
|
||||
}
|
||||
None => {
|
||||
_ => {
|
||||
self.device.clear_target(clear_color, Some(1.0));
|
||||
}
|
||||
}
|
||||
|
@ -1968,6 +1981,66 @@ impl Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
fn start_frame(&mut self, frame: &mut Frame) {
|
||||
let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_DATA);
|
||||
|
||||
// Assign render targets to the passes.
|
||||
for pass in &mut frame.passes {
|
||||
debug_assert!(pass.color_texture_id.is_none());
|
||||
debug_assert!(pass.alpha_texture_id.is_none());
|
||||
|
||||
if pass.needs_render_target_kind(RenderTargetKind::Color) {
|
||||
pass.color_texture_id = Some(self.color_render_targets
|
||||
.pop()
|
||||
.unwrap_or_else(|| {
|
||||
self.device
|
||||
.create_texture_ids(1, TextureTarget::Array)[0]
|
||||
}));
|
||||
}
|
||||
|
||||
if pass.needs_render_target_kind(RenderTargetKind::Alpha) {
|
||||
pass.alpha_texture_id = Some(self.alpha_render_targets
|
||||
.pop()
|
||||
.unwrap_or_else(|| {
|
||||
self.device
|
||||
.create_texture_ids(1, TextureTarget::Array)[0]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Init textures and render targets to match this scene.
|
||||
for pass in &frame.passes {
|
||||
if let Some(texture_id) = pass.color_texture_id {
|
||||
let target_count = pass.required_target_count(RenderTargetKind::Color);
|
||||
self.device.init_texture(texture_id,
|
||||
frame.cache_size.width as u32,
|
||||
frame.cache_size.height as u32,
|
||||
ImageFormat::RGBA8,
|
||||
TextureFilter::Linear,
|
||||
RenderTargetMode::LayerRenderTarget(target_count as i32),
|
||||
None);
|
||||
}
|
||||
if let Some(texture_id) = pass.alpha_texture_id {
|
||||
let target_count = pass.required_target_count(RenderTargetKind::Alpha);
|
||||
self.device.init_texture(texture_id,
|
||||
frame.cache_size.width as u32,
|
||||
frame.cache_size.height as u32,
|
||||
ImageFormat::A8,
|
||||
TextureFilter::Nearest,
|
||||
RenderTargetMode::LayerRenderTarget(target_count as i32),
|
||||
None);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gw): This is a hack / workaround for #728.
|
||||
// We should find a better way to implement these updates rather
|
||||
// than wasting this extra memory, but for now it removes a large
|
||||
// number of driver stalls.
|
||||
self.gpu_data_textures[self.gdt_index].init_frame(&mut self.device, frame);
|
||||
self.gdt_index = (self.gdt_index + 1) % GPU_DATA_TEXTURE_POOL;
|
||||
}
|
||||
|
||||
fn draw_tile_frame(&mut self,
|
||||
frame: &mut Frame,
|
||||
framebuffer_size: &DeviceUintSize) {
|
||||
|
@ -1987,60 +2060,7 @@ impl Renderer {
|
|||
if frame.passes.is_empty() {
|
||||
self.device.clear_target(Some(self.clear_color.to_array()), Some(1.0));
|
||||
} else {
|
||||
// Assign render targets to the passes.
|
||||
for pass in &mut frame.passes {
|
||||
debug_assert!(pass.color_texture_id.is_none());
|
||||
debug_assert!(pass.alpha_texture_id.is_none());
|
||||
|
||||
if pass.needs_render_target_kind(RenderTargetKind::Color) {
|
||||
pass.color_texture_id = Some(self.color_render_targets
|
||||
.pop()
|
||||
.unwrap_or_else(|| {
|
||||
self.device
|
||||
.create_texture_ids(1, TextureTarget::Array)[0]
|
||||
}));
|
||||
}
|
||||
|
||||
if pass.needs_render_target_kind(RenderTargetKind::Alpha) {
|
||||
pass.alpha_texture_id = Some(self.alpha_render_targets
|
||||
.pop()
|
||||
.unwrap_or_else(|| {
|
||||
self.device
|
||||
.create_texture_ids(1, TextureTarget::Array)[0]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Init textures and render targets to match this scene.
|
||||
for pass in &frame.passes {
|
||||
if let Some(texture_id) = pass.color_texture_id {
|
||||
let target_count = pass.required_target_count(RenderTargetKind::Color);
|
||||
self.device.init_texture(texture_id,
|
||||
frame.cache_size.width as u32,
|
||||
frame.cache_size.height as u32,
|
||||
ImageFormat::RGBA8,
|
||||
TextureFilter::Linear,
|
||||
RenderTargetMode::LayerRenderTarget(target_count as i32),
|
||||
None);
|
||||
}
|
||||
if let Some(texture_id) = pass.alpha_texture_id {
|
||||
let target_count = pass.required_target_count(RenderTargetKind::Alpha);
|
||||
self.device.init_texture(texture_id,
|
||||
frame.cache_size.width as u32,
|
||||
frame.cache_size.height as u32,
|
||||
ImageFormat::A8,
|
||||
TextureFilter::Nearest,
|
||||
RenderTargetMode::LayerRenderTarget(target_count as i32),
|
||||
None);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gw): This is a hack / workaround for #728.
|
||||
// We should find a better way to implement these updates rather
|
||||
// than wasting this extra memory, but for now it removes a large
|
||||
// number of driver stalls.
|
||||
self.gpu_data_textures[self.gdt_index].init_frame(&mut self.device, frame);
|
||||
self.gdt_index = (self.gdt_index + 1) % GPU_DATA_TEXTURE_POOL;
|
||||
self.start_frame(frame);
|
||||
|
||||
let mut src_color_id = self.dummy_cache_texture_id;
|
||||
let mut src_alpha_id = self.dummy_cache_texture_id;
|
||||
|
@ -2257,9 +2277,11 @@ pub struct RendererOptions {
|
|||
pub enable_subpixel_aa: bool,
|
||||
pub clear_framebuffer: bool,
|
||||
pub clear_color: ColorF,
|
||||
pub enable_clear_scissor: bool,
|
||||
pub enable_batcher: bool,
|
||||
pub render_target_debug: bool,
|
||||
pub max_texture_size: Option<u32>,
|
||||
pub cache_expiry_frames: u32,
|
||||
pub workers: Option<Arc<ThreadPool>>,
|
||||
pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
|
||||
pub recorder: Option<Box<ApiRecordingReceiver>>,
|
||||
|
@ -2281,9 +2303,11 @@ impl Default for RendererOptions {
|
|||
enable_subpixel_aa: false,
|
||||
clear_framebuffer: true,
|
||||
clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0),
|
||||
enable_clear_scissor: true,
|
||||
enable_batcher: true,
|
||||
render_target_debug: false,
|
||||
max_texture_size: None,
|
||||
cache_expiry_frames: 600, // roughly, 10 seconds
|
||||
workers: None,
|
||||
blob_image_renderer: None,
|
||||
recorder: None,
|
||||
|
|
|
@ -7,54 +7,26 @@ use device::TextureFilter;
|
|||
use fnv::FnvHasher;
|
||||
use frame::FrameId;
|
||||
use internal_types::{FontTemplate, SourceTexture, TextureUpdateList};
|
||||
use platform::font::{FontContext, RasterizedGlyph};
|
||||
use profiler::TextureCacheProfileCounters;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::hash_map::Entry::{self, Occupied, Vacant};
|
||||
use std::fmt::Debug;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread;
|
||||
use std::sync::Arc;
|
||||
use texture_cache::{TextureCache, TextureCacheItemId};
|
||||
use thread_profiler::register_thread_with_profiler;
|
||||
use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering};
|
||||
use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageRendering};
|
||||
use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
|
||||
use webrender_traits::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
|
||||
use webrender_traits::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
|
||||
use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData, ImageStore};
|
||||
use webrender_traits::{ExternalImageData, ExternalImageType, LayoutPoint};
|
||||
use rayon::ThreadPool;
|
||||
use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
|
||||
|
||||
const DEFAULT_TILE_SIZE: TileSize = 512;
|
||||
|
||||
thread_local!(pub static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new()));
|
||||
|
||||
type GlyphCache = ResourceClassCache<RenderedGlyphKey, Option<TextureCacheItemId>>;
|
||||
|
||||
/// Message sent from the resource cache to the glyph cache thread.
|
||||
enum GlyphCacheMsg {
|
||||
/// Begin the frame - pass ownership of the glyph cache to the thread.
|
||||
BeginFrame(FrameId, GlyphCache),
|
||||
/// Add a new font.
|
||||
AddFont(FontKey, FontTemplate),
|
||||
/// Request glyphs for a text run.
|
||||
RequestGlyphs(FontKey, Au, ColorF, Vec<GlyphInstance>, FontRenderMode, Option<GlyphOptions>),
|
||||
// Remove an existing font.
|
||||
DeleteFont(FontKey),
|
||||
/// Finished requesting glyphs. Reply with new glyphs.
|
||||
EndFrame,
|
||||
}
|
||||
|
||||
/// Results send from glyph cache thread back to main resource cache.
|
||||
enum GlyphCacheResultMsg {
|
||||
/// Return the glyph cache, and a list of newly rasterized glyphs.
|
||||
EndFrame(GlyphCache, Vec<GlyphRasterJob>),
|
||||
}
|
||||
|
||||
// These coordinates are always in texels.
|
||||
// They are converted to normalized ST
|
||||
// values in the vertex shader. The reason
|
||||
|
@ -70,30 +42,6 @@ pub struct CacheItem {
|
|||
pub uv1: DevicePoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
|
||||
pub struct RenderedGlyphKey {
|
||||
pub key: GlyphKey,
|
||||
pub render_mode: FontRenderMode,
|
||||
pub glyph_options: Option<GlyphOptions>,
|
||||
}
|
||||
|
||||
impl RenderedGlyphKey {
|
||||
pub fn new(font_key: FontKey,
|
||||
size: Au,
|
||||
color: ColorF,
|
||||
index: u32,
|
||||
point: LayoutPoint,
|
||||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>) -> RenderedGlyphKey {
|
||||
RenderedGlyphKey {
|
||||
key: GlyphKey::new(font_key, size, color, index,
|
||||
point, render_mode),
|
||||
render_mode: render_mode,
|
||||
glyph_options: glyph_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageProperties {
|
||||
pub descriptor: ImageDescriptor,
|
||||
pub external_image: Option<ExternalImageData>,
|
||||
|
@ -160,14 +108,14 @@ pub struct ResourceClassCache<K,V> {
|
|||
}
|
||||
|
||||
impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug, V: Resource {
|
||||
fn new() -> ResourceClassCache<K,V> {
|
||||
pub fn new() -> ResourceClassCache<K,V> {
|
||||
ResourceClassCache {
|
||||
resources: HashMap::default(),
|
||||
last_access_times: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_key(&self, key: &K) -> bool {
|
||||
pub fn contains_key(&self, key: &K) -> bool {
|
||||
self.resources.contains_key(key)
|
||||
}
|
||||
|
||||
|
@ -181,7 +129,7 @@ impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug, V: Resourc
|
|||
self.resources.get(key).expect("Didn't find a cached resource with that ID!")
|
||||
}
|
||||
|
||||
fn insert(&mut self, key: K, value: V, frame: FrameId) {
|
||||
pub fn insert(&mut self, key: K, value: V, frame: FrameId) {
|
||||
self.last_access_times.insert(key.clone(), frame);
|
||||
self.resources.insert(key, value);
|
||||
}
|
||||
|
@ -191,7 +139,7 @@ impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug, V: Resourc
|
|||
self.resources.entry(key)
|
||||
}
|
||||
|
||||
fn mark_as_needed(&mut self, key: &K, frame: FrameId) {
|
||||
pub fn mark_as_needed(&mut self, key: &K, frame: FrameId) {
|
||||
self.last_access_times.insert((*key).clone(), frame);
|
||||
}
|
||||
|
||||
|
@ -232,18 +180,13 @@ impl Into<BlobImageRequest> for ImageRequest {
|
|||
}
|
||||
}
|
||||
|
||||
struct GlyphRasterJob {
|
||||
key: RenderedGlyphKey,
|
||||
result: Option<RasterizedGlyph>,
|
||||
}
|
||||
|
||||
struct WebGLTexture {
|
||||
id: SourceTexture,
|
||||
size: DeviceIntSize,
|
||||
}
|
||||
|
||||
pub struct ResourceCache {
|
||||
cached_glyphs: Option<GlyphCache>,
|
||||
cached_glyphs: GlyphCache,
|
||||
cached_images: ResourceClassCache<ImageRequest, CachedImageInfo>,
|
||||
|
||||
// TODO(pcwalton): Figure out the lifecycle of these.
|
||||
|
@ -259,8 +202,7 @@ pub struct ResourceCache {
|
|||
// TODO(gw): We should expire (parts of) this cache semi-regularly!
|
||||
cached_glyph_dimensions: HashMap<GlyphKey, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
|
||||
pending_image_requests: Vec<ImageRequest>,
|
||||
glyph_cache_tx: Sender<GlyphCacheMsg>,
|
||||
glyph_cache_result_queue: Receiver<GlyphCacheResultMsg>,
|
||||
glyph_rasterizer: GlyphRasterizer,
|
||||
|
||||
blob_image_renderer: Option<Box<BlobImageRenderer>>,
|
||||
blob_image_requests: HashSet<ImageRequest>,
|
||||
|
@ -270,10 +212,8 @@ impl ResourceCache {
|
|||
pub fn new(texture_cache: TextureCache,
|
||||
workers: Arc<ThreadPool>,
|
||||
blob_image_renderer: Option<Box<BlobImageRenderer>>) -> ResourceCache {
|
||||
let (glyph_cache_tx, glyph_cache_result_queue) = spawn_glyph_cache_thread(workers);
|
||||
|
||||
ResourceCache {
|
||||
cached_glyphs: Some(ResourceClassCache::new()),
|
||||
cached_glyphs: ResourceClassCache::new(),
|
||||
cached_images: ResourceClassCache::new(),
|
||||
webgl_textures: HashMap::default(),
|
||||
font_templates: HashMap::default(),
|
||||
|
@ -283,8 +223,7 @@ impl ResourceCache {
|
|||
state: State::Idle,
|
||||
current_frame_id: FrameId(0),
|
||||
pending_image_requests: Vec::new(),
|
||||
glyph_cache_tx: glyph_cache_tx,
|
||||
glyph_cache_result_queue: glyph_cache_result_queue,
|
||||
glyph_rasterizer: GlyphRasterizer::new(workers),
|
||||
|
||||
blob_image_renderer: blob_image_renderer,
|
||||
blob_image_requests: HashSet::new(),
|
||||
|
@ -309,18 +248,14 @@ impl ResourceCache {
|
|||
}
|
||||
|
||||
pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
|
||||
// Push the new font to the glyph cache thread, and also store
|
||||
// Push the new font to the font renderer, and also store
|
||||
// it locally for glyph metric requests.
|
||||
self.glyph_cache_tx
|
||||
.send(GlyphCacheMsg::AddFont(font_key, template.clone()))
|
||||
.unwrap();
|
||||
self.glyph_rasterizer.add_font(font_key, template.clone());
|
||||
self.font_templates.insert(font_key, template);
|
||||
}
|
||||
|
||||
pub fn delete_font_template(&mut self, font_key: FontKey) {
|
||||
self.glyph_cache_tx
|
||||
.send(GlyphCacheMsg::DeleteFont(font_key))
|
||||
.unwrap();
|
||||
self.glyph_rasterizer.delete_font(font_key);
|
||||
self.font_templates.remove(&font_key);
|
||||
}
|
||||
|
||||
|
@ -492,16 +427,17 @@ impl ResourceCache {
|
|||
render_mode: FontRenderMode,
|
||||
glyph_options: Option<GlyphOptions>) {
|
||||
debug_assert_eq!(self.state, State::AddResources);
|
||||
// Immediately request that the glyph cache thread start
|
||||
// rasterizing glyphs from this request if they aren't
|
||||
// already cached.
|
||||
let msg = GlyphCacheMsg::RequestGlyphs(key,
|
||||
size,
|
||||
color,
|
||||
glyph_instances.to_vec(),
|
||||
render_mode,
|
||||
glyph_options);
|
||||
self.glyph_cache_tx.send(msg).unwrap();
|
||||
|
||||
self.glyph_rasterizer.request_glyphs(
|
||||
&mut self.cached_glyphs,
|
||||
self.current_frame_id,
|
||||
key,
|
||||
size,
|
||||
color,
|
||||
glyph_instances,
|
||||
render_mode,
|
||||
glyph_options,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn pending_updates(&mut self) -> TextureUpdateList {
|
||||
|
@ -517,20 +453,21 @@ impl ResourceCache {
|
|||
glyph_options: Option<GlyphOptions>,
|
||||
mut f: F) -> SourceTexture where F: FnMut(usize, DevicePoint, DevicePoint) {
|
||||
debug_assert_eq!(self.state, State::QueryResources);
|
||||
let cache = self.cached_glyphs.as_ref().unwrap();
|
||||
let mut glyph_key = RenderedGlyphKey::new(font_key,
|
||||
size,
|
||||
color,
|
||||
0,
|
||||
LayoutPoint::new(0.0, 0.0),
|
||||
render_mode,
|
||||
glyph_options);
|
||||
let mut glyph_key = GlyphRequest::new(
|
||||
font_key,
|
||||
size,
|
||||
color,
|
||||
0,
|
||||
LayoutPoint::new(0.0, 0.0),
|
||||
render_mode,
|
||||
glyph_options
|
||||
);
|
||||
let mut texture_id = None;
|
||||
for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
|
||||
glyph_key.key.index = glyph_instance.index;
|
||||
glyph_key.key.subpixel_point.set_offset(glyph_instance.point, render_mode);
|
||||
|
||||
let image_id = cache.get(&glyph_key, self.current_frame_id);
|
||||
let image_id = self.cached_glyphs.get(&glyph_key, self.current_frame_id);
|
||||
let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
|
||||
if let Some(cache_item) = cache_item {
|
||||
let uv0 = DevicePoint::new(cache_item.pixel_rect.top_left.x as f32,
|
||||
|
@ -551,25 +488,7 @@ impl ResourceCache {
|
|||
match self.cached_glyph_dimensions.entry(glyph_key.clone()) {
|
||||
Occupied(entry) => *entry.get(),
|
||||
Vacant(entry) => {
|
||||
let mut dimensions = None;
|
||||
let font_template = &self.font_templates[&glyph_key.font_key];
|
||||
|
||||
FONT_CONTEXT.with(|font_context| {
|
||||
let mut font_context = font_context.borrow_mut();
|
||||
match *font_template {
|
||||
FontTemplate::Raw(ref bytes, index) => {
|
||||
font_context.add_raw_font(&glyph_key.font_key, &**bytes, index);
|
||||
}
|
||||
FontTemplate::Native(ref native_font_handle) => {
|
||||
font_context.add_native_font(&glyph_key.font_key,
|
||||
(*native_font_handle).clone());
|
||||
}
|
||||
}
|
||||
|
||||
dimensions = font_context.get_glyph_dimensions(glyph_key);
|
||||
});
|
||||
|
||||
*entry.insert(dimensions)
|
||||
*entry.insert(self.glyph_rasterizer.get_glyph_dimensions(glyph_key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -638,17 +557,13 @@ impl ResourceCache {
|
|||
|
||||
pub fn expire_old_resources(&mut self, frame_id: FrameId) {
|
||||
self.cached_images.expire_old_resources(&mut self.texture_cache, frame_id);
|
||||
|
||||
let cached_glyphs = self.cached_glyphs.as_mut().unwrap();
|
||||
cached_glyphs.expire_old_resources(&mut self.texture_cache, frame_id);
|
||||
self.cached_glyphs.expire_old_resources(&mut self.texture_cache, frame_id);
|
||||
}
|
||||
|
||||
pub fn begin_frame(&mut self, frame_id: FrameId) {
|
||||
debug_assert_eq!(self.state, State::Idle);
|
||||
self.state = State::AddResources;
|
||||
self.current_frame_id = frame_id;
|
||||
let glyph_cache = self.cached_glyphs.take().unwrap();
|
||||
self.glyph_cache_tx.send(GlyphCacheMsg::BeginFrame(frame_id, glyph_cache)).ok();
|
||||
}
|
||||
|
||||
pub fn block_until_all_resources_added(&mut self,
|
||||
|
@ -658,53 +573,12 @@ impl ResourceCache {
|
|||
debug_assert_eq!(self.state, State::AddResources);
|
||||
self.state = State::QueryResources;
|
||||
|
||||
// Tell the glyph cache thread that all glyphs have been requested
|
||||
// and block, waiting for any pending glyphs to be rasterized. In the
|
||||
// future, we will expand this to have a timeout. If the glyph rasterizing
|
||||
// takes longer than the timeout, then we will select the best glyphs
|
||||
// available in the cache, render with those, and then re-render at
|
||||
// a later point when the correct resolution glyphs finally become
|
||||
// available.
|
||||
self.glyph_cache_tx.send(GlyphCacheMsg::EndFrame).unwrap();
|
||||
|
||||
// Loop until the end frame message is retrieved here. This loop
|
||||
// doesn't serve any real purpose right now, but in the future
|
||||
// it will be receiving small amounts of glyphs at a time, up until
|
||||
// it decides that it should just render the frame.
|
||||
while let Ok(result) = self.glyph_cache_result_queue.recv() {
|
||||
match result {
|
||||
GlyphCacheResultMsg::EndFrame(mut cache, glyph_jobs) => {
|
||||
// Add any newly rasterized glyphs to the texture cache.
|
||||
for job in glyph_jobs {
|
||||
let image_id = job.result.and_then(|glyph| {
|
||||
if glyph.width > 0 && glyph.height > 0 {
|
||||
let image_id = self.texture_cache.new_item_id();
|
||||
self.texture_cache.insert(image_id,
|
||||
ImageDescriptor {
|
||||
width: glyph.width,
|
||||
height: glyph.height,
|
||||
stride: None,
|
||||
format: ImageFormat::RGBA8,
|
||||
is_opaque: false,
|
||||
offset: 0,
|
||||
},
|
||||
TextureFilter::Linear,
|
||||
ImageData::Raw(Arc::new(glyph.bytes)),
|
||||
texture_cache_profile);
|
||||
Some(image_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
cache.insert(job.key, image_id, self.current_frame_id);
|
||||
}
|
||||
|
||||
self.cached_glyphs = Some(cache);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.glyph_rasterizer.resolve_glyphs(
|
||||
self.current_frame_id,
|
||||
&mut self.cached_glyphs,
|
||||
&mut self.texture_cache,
|
||||
texture_cache_profile,
|
||||
);
|
||||
|
||||
let mut image_requests = mem::replace(&mut self.pending_image_requests, Vec::new());
|
||||
for request in image_requests.drain(..) {
|
||||
|
@ -877,174 +751,6 @@ impl Resource for CachedImageInfo {
|
|||
}
|
||||
}
|
||||
|
||||
fn spawn_glyph_cache_thread(workers: Arc<ThreadPool>) -> (Sender<GlyphCacheMsg>, Receiver<GlyphCacheResultMsg>) {
|
||||
let worker_count = {
|
||||
workers.current_num_threads()
|
||||
};
|
||||
// Used for messages from resource cache -> glyph cache thread.
|
||||
let (msg_tx, msg_rx) = channel();
|
||||
// Used for returning results from glyph cache thread -> resource cache.
|
||||
let (result_tx, result_rx) = channel();
|
||||
// Used for rasterizer worker threads to send glyphs -> glyph cache thread.
|
||||
let (glyph_tx, glyph_rx) = channel();
|
||||
|
||||
thread::Builder::new().name("GlyphCache".to_string()).spawn(move|| {
|
||||
let mut glyph_cache = None;
|
||||
let mut current_frame_id = FrameId(0);
|
||||
|
||||
register_thread_with_profiler("GlyphCache".to_string());
|
||||
|
||||
let barrier = Arc::new(Barrier::new(worker_count));
|
||||
for i in 0..worker_count {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
workers.spawn_async(move || {
|
||||
register_thread_with_profiler(format!("Glyph Worker {}", i));
|
||||
barrier.wait();
|
||||
});
|
||||
}
|
||||
|
||||
// Maintain a set of glyphs that have been requested this
|
||||
// frame. This ensures the glyph thread won't rasterize
|
||||
// the same glyph more than once in a frame. This is required
|
||||
// because the glyph cache hash table is not updated
|
||||
// until the glyph cache is passed back to the resource
|
||||
// cache which is able to add the items to the texture cache.
|
||||
let mut pending_glyphs = HashSet::new();
|
||||
|
||||
while let Ok(msg) = msg_rx.recv() {
|
||||
profile_scope!("handle_msg");
|
||||
match msg {
|
||||
GlyphCacheMsg::BeginFrame(frame_id, cache) => {
|
||||
profile_scope!("BeginFrame");
|
||||
|
||||
// We are beginning a new frame. Take ownership of the glyph
|
||||
// cache hash map, so we can easily see which glyph requests
|
||||
// actually need to be rasterized.
|
||||
current_frame_id = frame_id;
|
||||
glyph_cache = Some(cache);
|
||||
}
|
||||
GlyphCacheMsg::AddFont(font_key, font_template) => {
|
||||
profile_scope!("AddFont");
|
||||
|
||||
// Add a new font to the font context in each worker thread.
|
||||
// Use a barrier to ensure that each worker in the pool handles
|
||||
// one of these messages, to ensure that the new font gets
|
||||
// added to each worker thread.
|
||||
let barrier = Arc::new(Barrier::new(worker_count));
|
||||
for _ in 0..worker_count {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let font_template = font_template.clone();
|
||||
workers.spawn_async(move || {
|
||||
FONT_CONTEXT.with(|font_context| {
|
||||
let mut font_context = font_context.borrow_mut();
|
||||
match font_template {
|
||||
FontTemplate::Raw(ref bytes, index) => {
|
||||
font_context.add_raw_font(&font_key, &**bytes, index);
|
||||
}
|
||||
FontTemplate::Native(ref native_font_handle) => {
|
||||
font_context.add_native_font(&font_key,
|
||||
(*native_font_handle).clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
barrier.wait();
|
||||
});
|
||||
}
|
||||
}
|
||||
GlyphCacheMsg::DeleteFont(font_key) => {
|
||||
profile_scope!("DeleteFont");
|
||||
|
||||
// Delete a font from the font context in each worker thread.
|
||||
let barrier = Arc::new(Barrier::new(worker_count));
|
||||
for _ in 0..worker_count {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
workers.spawn_async(move || {
|
||||
FONT_CONTEXT.with(|font_context| {
|
||||
let mut font_context = font_context.borrow_mut();
|
||||
font_context.delete_font(&font_key);
|
||||
});
|
||||
barrier.wait();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
GlyphCacheMsg::RequestGlyphs(key, size, color, glyph_instances, render_mode, glyph_options) => {
|
||||
profile_scope!("RequestGlyphs");
|
||||
|
||||
// Request some glyphs for a text run.
|
||||
// For any glyph that isn't currently in the cache,
|
||||
// immeediately push a job to the worker thread pool
|
||||
// to start rasterizing this glyph now!
|
||||
let glyph_cache = glyph_cache.as_mut().unwrap();
|
||||
|
||||
for glyph_instance in glyph_instances {
|
||||
let glyph_key = RenderedGlyphKey::new(key,
|
||||
size,
|
||||
color,
|
||||
glyph_instance.index,
|
||||
glyph_instance.point,
|
||||
render_mode,
|
||||
glyph_options);
|
||||
|
||||
glyph_cache.mark_as_needed(&glyph_key, current_frame_id);
|
||||
if !glyph_cache.contains_key(&glyph_key) &&
|
||||
!pending_glyphs.contains(&glyph_key) {
|
||||
let glyph_tx = glyph_tx.clone();
|
||||
pending_glyphs.insert(glyph_key.clone());
|
||||
workers.spawn_async(move || {
|
||||
profile_scope!("glyph");
|
||||
FONT_CONTEXT.with(move |font_context| {
|
||||
let mut font_context = font_context.borrow_mut();
|
||||
let result = font_context.rasterize_glyph(&glyph_key.key,
|
||||
render_mode,
|
||||
glyph_options);
|
||||
if let Some(ref glyph) = result {
|
||||
assert_eq!(glyph.bytes.len(), 4 * (glyph.width * glyph.height) as usize);
|
||||
}
|
||||
glyph_tx.send((glyph_key, result)).unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
GlyphCacheMsg::EndFrame => {
|
||||
profile_scope!("EndFrame");
|
||||
|
||||
// The resource cache has finished requesting glyphs. Block
|
||||
// on completion of any pending glyph rasterizing jobs, and then
|
||||
// return the list of new glyphs to the resource cache.
|
||||
let cache = glyph_cache.take().unwrap();
|
||||
let mut rasterized_glyphs = Vec::new();
|
||||
while !pending_glyphs.is_empty() {
|
||||
let (key, glyph) = glyph_rx.recv()
|
||||
.expect("BUG: Should be glyphs pending!");
|
||||
debug_assert!(pending_glyphs.contains(&key));
|
||||
pending_glyphs.remove(&key);
|
||||
if let Some(ref v) = glyph {
|
||||
debug!("received {}x{} data len {}", v.width, v.height, v.bytes.len());
|
||||
}
|
||||
rasterized_glyphs.push(GlyphRasterJob {
|
||||
key: key,
|
||||
result: glyph,
|
||||
});
|
||||
}
|
||||
// Ensure that the glyphs are always processed in the same
|
||||
// order for a given text run (since iterating a hash set doesn't
|
||||
// guarantee order). This can show up as very small float inaccuacry
|
||||
// differences in rasterizers due to the different coordinates
|
||||
// that text runs get associated with by the texture cache allocator.
|
||||
rasterized_glyphs.sort_by(|a, b| {
|
||||
a.key.cmp(&b.key)
|
||||
});
|
||||
result_tx.send(GlyphCacheResultMsg::EndFrame(cache, rasterized_glyphs)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).unwrap();
|
||||
|
||||
(msg_tx, result_rx)
|
||||
}
|
||||
|
||||
// Compute the width and height of a tile depending on its position in the image.
|
||||
pub fn compute_tile_size(descriptor: &ImageDescriptor,
|
||||
|
|
|
@ -29,8 +29,7 @@ use webrender_traits::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, Devi
|
|||
use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
|
||||
use webrender_traits::{ExternalImageType, FontRenderMode, ImageRendering, LayerPoint, LayerRect};
|
||||
use webrender_traits::{LayerToWorldTransform, MixBlendMode, PipelineId, TransformStyle};
|
||||
use webrender_traits::{WorldPoint4D, WorldToLayerTransform};
|
||||
use webrender_traits::{YuvColorSpace, YuvFormat};
|
||||
use webrender_traits::{WorldToLayerTransform, YuvColorSpace, YuvFormat};
|
||||
|
||||
// Special sentinel value recognized by the shader. It is considered to be
|
||||
// a dummy task that doesn't mask out anything.
|
||||
|
|
|
@ -150,6 +150,15 @@ pub fn subtract_rect<U>(rect: &TypedRect<f32, U>,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_normal(x: f32) -> Option<f32> {
|
||||
if x.is_normal() {
|
||||
Some(x)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum TransformedRectKind {
|
||||
|
@ -234,8 +243,8 @@ impl TransformedRect {
|
|||
|
||||
for (vertex, (x, y)) in vertices.iter().zip(xs.iter_mut().zip(ys.iter_mut())) {
|
||||
let inv_w = 1.0 / vertex.w;
|
||||
*x = vertex.x * inv_w;
|
||||
*y = vertex.y * inv_w;
|
||||
*x = get_normal(vertex.x * inv_w).unwrap_or(0.0);
|
||||
*y = get_normal(vertex.y * inv_w).unwrap_or(0.0);
|
||||
}
|
||||
|
||||
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
@ -254,11 +263,11 @@ impl TransformedRect {
|
|||
local_rect: *rect,
|
||||
vertices: vertices,
|
||||
bounding_rect: DeviceIntRect::new(outer_min_dp,
|
||||
DeviceIntSize::new(outer_max_dp.x - outer_min_dp.x,
|
||||
outer_max_dp.y - outer_min_dp.y)),
|
||||
DeviceIntSize::new(outer_max_dp.x.saturating_sub(outer_min_dp.x),
|
||||
outer_max_dp.y.saturating_sub(outer_min_dp.y))),
|
||||
inner_rect: DeviceIntRect::new(inner_min_dp,
|
||||
DeviceIntSize::new(inner_max_dp.x - inner_min_dp.x,
|
||||
inner_max_dp.y - inner_min_dp.y)),
|
||||
DeviceIntSize::new(inner_max_dp.x.saturating_sub(inner_min_dp.x),
|
||||
inner_max_dp.y.saturating_sub(inner_min_dp.y))),
|
||||
kind: kind,
|
||||
}
|
||||
/*
|
||||
|
|
|
@ -5,12 +5,12 @@ authors = ["The Mozilla Project Developers"]
|
|||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
webrender_traits = {path = "../webrender_traits", version = "0.39.0"}
|
||||
euclid = "0.11"
|
||||
webrender_traits = {path = "../webrender_traits", version = "0.40.0"}
|
||||
euclid = "0.13"
|
||||
app_units = "0.4"
|
||||
gleam = "0.4"
|
||||
|
||||
[dependencies.webrender]
|
||||
path = "../webrender"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
default-features = false
|
||||
|
|
|
@ -704,7 +704,8 @@ DisplayListBuilder::PushYCbCrPlanarImage(const WrRect& aBounds,
|
|||
wr::ImageKey aImageChannel0,
|
||||
wr::ImageKey aImageChannel1,
|
||||
wr::ImageKey aImageChannel2,
|
||||
WrYuvColorSpace aColorSpace)
|
||||
WrYuvColorSpace aColorSpace,
|
||||
wr::ImageRendering aRendering)
|
||||
{
|
||||
wr_dp_push_yuv_planar_image(mWrState,
|
||||
aBounds,
|
||||
|
@ -712,7 +713,8 @@ DisplayListBuilder::PushYCbCrPlanarImage(const WrRect& aBounds,
|
|||
aImageChannel0,
|
||||
aImageChannel1,
|
||||
aImageChannel2,
|
||||
aColorSpace);
|
||||
aColorSpace,
|
||||
aRendering);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -720,27 +722,31 @@ DisplayListBuilder::PushNV12Image(const WrRect& aBounds,
|
|||
const WrClipRegionToken aClip,
|
||||
wr::ImageKey aImageChannel0,
|
||||
wr::ImageKey aImageChannel1,
|
||||
WrYuvColorSpace aColorSpace)
|
||||
WrYuvColorSpace aColorSpace,
|
||||
wr::ImageRendering aRendering)
|
||||
{
|
||||
wr_dp_push_yuv_NV12_image(mWrState,
|
||||
aBounds,
|
||||
aClip,
|
||||
aImageChannel0,
|
||||
aImageChannel1,
|
||||
aColorSpace);
|
||||
aColorSpace,
|
||||
aRendering);
|
||||
}
|
||||
|
||||
void
|
||||
DisplayListBuilder::PushYCbCrInterleavedImage(const WrRect& aBounds,
|
||||
const WrClipRegionToken aClip,
|
||||
wr::ImageKey aImageChannel0,
|
||||
WrYuvColorSpace aColorSpace)
|
||||
WrYuvColorSpace aColorSpace,
|
||||
wr::ImageRendering aRendering)
|
||||
{
|
||||
wr_dp_push_yuv_interleaved_image(mWrState,
|
||||
aBounds,
|
||||
aClip,
|
||||
aImageChannel0,
|
||||
aColorSpace);
|
||||
aColorSpace,
|
||||
aRendering);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -207,18 +207,21 @@ public:
|
|||
wr::ImageKey aImageChannel0,
|
||||
wr::ImageKey aImageChannel1,
|
||||
wr::ImageKey aImageChannel2,
|
||||
WrYuvColorSpace aColorSpace);
|
||||
WrYuvColorSpace aColorSpace,
|
||||
wr::ImageRendering aFilter);
|
||||
|
||||
void PushNV12Image(const WrRect& aBounds,
|
||||
const WrClipRegionToken aClip,
|
||||
wr::ImageKey aImageChannel0,
|
||||
wr::ImageKey aImageChannel1,
|
||||
WrYuvColorSpace aColorSpace);
|
||||
WrYuvColorSpace aColorSpace,
|
||||
wr::ImageRendering aFilter);
|
||||
|
||||
void PushYCbCrInterleavedImage(const WrRect& aBounds,
|
||||
const WrClipRegionToken aClip,
|
||||
wr::ImageKey aImageChannel0,
|
||||
WrYuvColorSpace aColorSpace);
|
||||
WrYuvColorSpace aColorSpace,
|
||||
wr::ImageRendering aFilter);
|
||||
|
||||
void PushIFrame(const WrRect& aBounds,
|
||||
const WrClipRegionToken aClip,
|
||||
|
|
|
@ -1382,7 +1382,8 @@ pub extern "C" fn wr_dp_push_yuv_planar_image(state: &mut WrState,
|
|||
image_key_0: WrImageKey,
|
||||
image_key_1: WrImageKey,
|
||||
image_key_2: WrImageKey,
|
||||
color_space: WrYuvColorSpace) {
|
||||
color_space: WrYuvColorSpace,
|
||||
image_rendering: WrImageRendering) {
|
||||
assert!(unsafe { is_in_main_thread() });
|
||||
|
||||
state.frame_builder
|
||||
|
@ -1390,7 +1391,8 @@ pub extern "C" fn wr_dp_push_yuv_planar_image(state: &mut WrState,
|
|||
.push_yuv_image(bounds.into(),
|
||||
clip.into(),
|
||||
YuvData::PlanarYCbCr(image_key_0, image_key_1, image_key_2),
|
||||
color_space);
|
||||
color_space,
|
||||
image_rendering);
|
||||
}
|
||||
|
||||
/// Push a 2 planar NV12 image.
|
||||
|
@ -1400,7 +1402,8 @@ pub extern "C" fn wr_dp_push_yuv_NV12_image(state: &mut WrState,
|
|||
clip: WrClipRegionToken,
|
||||
image_key_0: WrImageKey,
|
||||
image_key_1: WrImageKey,
|
||||
color_space: WrYuvColorSpace) {
|
||||
color_space: WrYuvColorSpace,
|
||||
image_rendering: WrImageRendering) {
|
||||
assert!(unsafe { is_in_main_thread() });
|
||||
|
||||
state.frame_builder
|
||||
|
@ -1408,7 +1411,8 @@ pub extern "C" fn wr_dp_push_yuv_NV12_image(state: &mut WrState,
|
|||
.push_yuv_image(bounds.into(),
|
||||
clip.into(),
|
||||
YuvData::NV12(image_key_0, image_key_1),
|
||||
color_space);
|
||||
color_space,
|
||||
image_rendering);
|
||||
}
|
||||
|
||||
/// Push a yuv interleaved image.
|
||||
|
@ -1417,7 +1421,8 @@ pub extern "C" fn wr_dp_push_yuv_interleaved_image(state: &mut WrState,
|
|||
bounds: WrRect,
|
||||
clip: WrClipRegionToken,
|
||||
image_key_0: WrImageKey,
|
||||
color_space: WrYuvColorSpace) {
|
||||
color_space: WrYuvColorSpace,
|
||||
image_rendering: WrImageRendering) {
|
||||
assert!(unsafe { is_in_main_thread() });
|
||||
|
||||
state.frame_builder
|
||||
|
@ -1425,7 +1430,8 @@ pub extern "C" fn wr_dp_push_yuv_interleaved_image(state: &mut WrState,
|
|||
.push_yuv_image(bounds.into(),
|
||||
clip.into(),
|
||||
YuvData::InterleavedYCbCr(image_key_0),
|
||||
color_space);
|
||||
color_space,
|
||||
image_rendering);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
|
@ -804,7 +804,8 @@ void wr_dp_push_yuv_NV12_image(WrState *aState,
|
|||
WrClipRegionToken aClip,
|
||||
WrImageKey aImageKey0,
|
||||
WrImageKey aImageKey1,
|
||||
WrYuvColorSpace aColorSpace)
|
||||
WrYuvColorSpace aColorSpace,
|
||||
WrImageRendering aImageRendering)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
|
@ -812,7 +813,8 @@ void wr_dp_push_yuv_interleaved_image(WrState *aState,
|
|||
WrRect aBounds,
|
||||
WrClipRegionToken aClip,
|
||||
WrImageKey aImageKey0,
|
||||
WrYuvColorSpace aColorSpace)
|
||||
WrYuvColorSpace aColorSpace,
|
||||
WrImageRendering aImageRendering)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
|
@ -822,7 +824,8 @@ void wr_dp_push_yuv_planar_image(WrState *aState,
|
|||
WrImageKey aImageKey0,
|
||||
WrImageKey aImageKey1,
|
||||
WrImageKey aImageKey2,
|
||||
WrYuvColorSpace aColorSpace)
|
||||
WrYuvColorSpace aColorSpace,
|
||||
WrImageRendering aImageRendering)
|
||||
WR_FUNC;
|
||||
|
||||
WR_INLINE
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "webrender_traits"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/servo/webrender"
|
||||
|
@ -14,9 +14,9 @@ webgl = ["offscreen_gl_context"]
|
|||
app_units = "0.4"
|
||||
bincode = "1.0.0-alpha2"
|
||||
byteorder = "1.0"
|
||||
euclid = "0.11"
|
||||
euclid = "0.13"
|
||||
gleam = "0.4.5"
|
||||
heapsize = "0.3.6"
|
||||
heapsize = ">= 0.3.6, < 0.5"
|
||||
ipc-channel = {version = "0.7.2", optional = true}
|
||||
offscreen_gl_context = {version = "0.8", features = ["serde"], optional = true}
|
||||
serde = "0.9"
|
||||
|
|
|
@ -350,6 +350,7 @@ pub enum ImageRendering {
|
|||
pub struct YuvImageDisplayItem {
|
||||
pub yuv_data: YuvData,
|
||||
pub color_space: YuvColorSpace,
|
||||
pub image_rendering: ImageRendering
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
|
@ -427,7 +428,7 @@ pub struct ClipRegion {
|
|||
pub complex_clips: ItemRange<ComplexClipRegion>,
|
||||
#[serde(default, skip_serializing, skip_deserializing)]
|
||||
pub complex_clip_count: usize,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct ComplexClipRegion {
|
||||
|
|
|
@ -535,10 +535,12 @@ impl DisplayListBuilder {
|
|||
rect: LayoutRect,
|
||||
_token: ClipRegionToken,
|
||||
yuv_data: YuvData,
|
||||
color_space: YuvColorSpace) {
|
||||
color_space: YuvColorSpace,
|
||||
image_rendering: ImageRendering) {
|
||||
let item = SpecificDisplayItem::YuvImage(YuvImageDisplayItem {
|
||||
yuv_data: yuv_data,
|
||||
color_space: color_space,
|
||||
image_rendering: image_rendering,
|
||||
});
|
||||
self.push_item(item, rect);
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"118514fd9c4958df0d25584cda4917186c46011569f55ef350530c1ad3fbdb48",".travis.yml":"13d3e5a7bf83b04c8e8cfa14f0297bd8366d68391d977dd547f64707dffc275a","COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"10cfe5580ee83ae883a60d96f504dda8ae7885ae5fd3a3faf95c2a2b8b38fad0","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"52f974f01c1e15182413e4321c8817d5e66fe4d92c5ec223c857dd0440f5c229","src/approxeq.rs":"2987e046c90d948b6c7d7ddba52d10c8b7520d71dc0a50dbe7665de128d7410e","src/length.rs":"d7c6369f2fe2a17c845b57749bd48c471159f0571a7314d3bf90737d53f697d3","src/lib.rs":"e2e621f05304278d020429d0349acf7a4e7c7a9a72bd23fc0e55680267472ee9","src/macros.rs":"b63dabdb52df84ea170dc1dab5fe8d7a78c054562d1566bab416124708d2d7af","src/matrix2d.rs":"2361338f59813adf4eebaab76e4dd82be0fbfb9ff2461da8dd9ac9d43583b322","src/matrix4d.rs":"b8547bed6108b037192021c97169c00ad456120b849e9b7ac7bec40363edaec1","src/num.rs":"62286aa642ce3afa7ebd950f50bf2197d8722907f2e23a2e2ea6690484d8b250","src/point.rs":"53f3c9018c822e0a6dc5018005e153775479f41fe55c082d0be10f331fda773f","src/rect.rs":"db62b3af8939529509ae21b3bf6ae498d73a95b4ff3a6eba4db614be08e95f8b","src/scale_factor.rs":"df6dbd1f0f9f63210b92809f84a383dad982a74f09789cf22c7d8f9b62199d39","src/side_offsets.rs":"f85526a421ffda63ff01a3478d4162c8717eef68e942acfa2fd9a1adee02ebb2","src/size.rs":"19d1c08f678d793c6eff49a44f69e5b7179e574aa9b81fb4e73210733af38718","src/trig.rs":"6b207980052d13c625272f2a70a22f7741b59513c2a4882385926f497c763a63"},"package":"f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"}
|
|
@ -0,0 +1,2 @@
|
|||
Cargo.lock
|
||||
/target/
|
|
@ -0,0 +1,19 @@
|
|||
language: rust
|
||||
|
||||
notifications:
|
||||
webhooks: http://build.servo.org:54856/travis
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: stable
|
||||
env: FEATURES=""
|
||||
- rust: beta
|
||||
env: FEATURES=""
|
||||
- rust: nightly
|
||||
env: FEATURES=""
|
||||
- rust: nightly
|
||||
env: FEATURES="unstable"
|
||||
|
||||
script:
|
||||
- cargo build --verbose --features "$FEATURES"
|
||||
- cargo test --verbose --features "$FEATURES"
|
|
@ -0,0 +1,5 @@
|
|||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
option. All files in the project carrying such notice may not be
|
||||
copied, modified, or distributed except according to those terms.
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "euclid"
|
||||
version = "0.11.3"
|
||||
authors = ["The Servo Project Developers"]
|
||||
description = "Geometry primitives"
|
||||
documentation = "https://docs.rs/euclid/"
|
||||
repository = "https://github.com/servo/euclid"
|
||||
license = "MIT / Apache-2.0"
|
||||
|
||||
[features]
|
||||
unstable = []
|
||||
|
||||
[dependencies]
|
||||
heapsize = "0.3"
|
||||
rustc-serialize = "0.3.2"
|
||||
num-traits = {version = "0.1.32", default-features = false}
|
||||
log = "0.3.1"
|
||||
serde = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.3.7"
|
||||
serde_test = "0.9"
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2012-2013 Mozilla Foundation
|
||||
|
||||
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.
|
|
@ -0,0 +1,5 @@
|
|||
# euclid
|
||||
|
||||
This is a small library for geometric types.
|
||||
|
||||
[Documentation](https://docs.rs/euclid/)
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
|
||||
/// Trait for testing approximate equality
|
||||
pub trait ApproxEq<Eps> {
|
||||
fn approx_epsilon() -> Eps;
|
||||
fn approx_eq(&self, other: &Self) -> bool;
|
||||
fn approx_eq_eps(&self, other: &Self, approx_epsilon: &Eps) -> bool;
|
||||
}
|
||||
|
||||
impl ApproxEq<f32> for f32 {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> f32 { 1.0e-6 }
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &f32) -> bool {
|
||||
self.approx_eq_eps(other, &1.0e-6)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &f32, approx_epsilon: &f32) -> bool {
|
||||
(*self - *other).abs() < *approx_epsilon
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl ApproxEq<f64> for f64 {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> f64 { 1.0e-6 }
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &f64) -> bool {
|
||||
self.approx_eq_eps(other, &1.0e-6)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &f64, approx_epsilon: &f64) -> bool {
|
||||
(*self - *other).abs() < *approx_epsilon
|
||||
}
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
//! A one-dimensional length, tagged with its units.
|
||||
|
||||
use scale_factor::ScaleFactor;
|
||||
use num::Zero;
|
||||
|
||||
use heapsize::HeapSizeOf;
|
||||
use num_traits::{NumCast, Saturating};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Add, Sub, Mul, Div, Neg};
|
||||
use std::ops::{AddAssign, SubAssign};
|
||||
use std::marker::PhantomData;
|
||||
use std::fmt;
|
||||
|
||||
/// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`.
|
||||
///
|
||||
/// `T` can be any numeric type, for example a primitive type like `u64` or `f32`.
|
||||
///
|
||||
/// `Unit` is not used in the representation of a `Length` value. It is used only at compile time
|
||||
/// to ensure that a `Length` stored with one unit is converted explicitly before being used in an
|
||||
/// expression that requires a different unit. It may be a type without values, such as an empty
|
||||
/// enum.
|
||||
///
|
||||
/// You can multiply a `Length` by a `scale_factor::ScaleFactor` to convert it from one unit to
|
||||
/// another. See the `ScaleFactor` docs for an example.
|
||||
// Uncomment the derive, and remove the macro call, once heapsize gets
|
||||
// PhantomData<T> support.
|
||||
#[repr(C)]
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct Length<T, Unit>(pub T, PhantomData<Unit>);
|
||||
|
||||
impl<T: Clone, Unit> Clone for Length<T, Unit> {
|
||||
fn clone(&self) -> Self {
|
||||
Length(self.0.clone(), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, Unit> Copy for Length<T, Unit> {}
|
||||
|
||||
impl<Unit, T: HeapSizeOf> HeapSizeOf for Length<T, Unit> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.0.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Unit, T> Deserialize for Length<T, Unit> where T: Deserialize {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Length<T, Unit>,D::Error>
|
||||
where D: Deserializer {
|
||||
Ok(Length(try!(Deserialize::deserialize(deserializer)), PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Unit> Serialize for Length<T, Unit> where T: Serialize {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Unit> Length<T, Unit> {
|
||||
pub fn new(x: T) -> Length<T, Unit> {
|
||||
Length(x, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Unit, T: Clone> Length<T, Unit> {
|
||||
pub fn get(&self) -> T {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Clone, U> fmt::Debug for Length<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.get().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display + Clone, U> fmt::Display for Length<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.get().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
// length + length
|
||||
impl<U, T: Clone + Add<T, Output=T>> Add for Length<T, U> {
|
||||
type Output = Length<T, U>;
|
||||
fn add(self, other: Length<T, U>) -> Length<T, U> {
|
||||
Length::new(self.get() + other.get())
|
||||
}
|
||||
}
|
||||
|
||||
// length += length
|
||||
impl<U, T: Clone + AddAssign<T>> AddAssign for Length<T, U> {
|
||||
fn add_assign(&mut self, other: Length<T, U>) {
|
||||
self.0 += other.get();
|
||||
}
|
||||
}
|
||||
|
||||
// length - length
|
||||
impl<U, T: Clone + Sub<T, Output=T>> Sub<Length<T, U>> for Length<T, U> {
|
||||
type Output = Length<T, U>;
|
||||
fn sub(self, other: Length<T, U>) -> <Self as Sub>::Output {
|
||||
Length::new(self.get() - other.get())
|
||||
}
|
||||
}
|
||||
|
||||
// length -= length
|
||||
impl<U, T: Clone + SubAssign<T>> SubAssign for Length<T, U> {
|
||||
fn sub_assign(&mut self, other: Length<T, U>) {
|
||||
self.0 -= other.get();
|
||||
}
|
||||
}
|
||||
|
||||
// Saturating length + length and length - length.
|
||||
impl<U, T: Clone + Saturating> Saturating for Length<T, U> {
|
||||
fn saturating_add(self, other: Length<T, U>) -> Length<T, U> {
|
||||
Length::new(self.get().saturating_add(other.get()))
|
||||
}
|
||||
|
||||
fn saturating_sub(self, other: Length<T, U>) -> Length<T, U> {
|
||||
Length::new(self.get().saturating_sub(other.get()))
|
||||
}
|
||||
}
|
||||
|
||||
// length / length
|
||||
impl<Src, Dst, T: Clone + Div<T, Output=T>> Div<Length<T, Src>> for Length<T, Dst> {
|
||||
type Output = ScaleFactor<T, Src, Dst>;
|
||||
#[inline]
|
||||
fn div(self, other: Length<T, Src>) -> ScaleFactor<T, Src, Dst> {
|
||||
ScaleFactor::new(self.get() / other.get())
|
||||
}
|
||||
}
|
||||
|
||||
// length * scaleFactor
|
||||
impl<Src, Dst, T: Clone + Mul<T, Output=T>> Mul<ScaleFactor<T, Src, Dst>> for Length<T, Src> {
|
||||
type Output = Length<T, Dst>;
|
||||
#[inline]
|
||||
fn mul(self, scale: ScaleFactor<T, Src, Dst>) -> Length<T, Dst> {
|
||||
Length::new(self.get() * scale.get())
|
||||
}
|
||||
}
|
||||
|
||||
// length / scaleFactor
|
||||
impl<Src, Dst, T: Clone + Div<T, Output=T>> Div<ScaleFactor<T, Src, Dst>> for Length<T, Dst> {
|
||||
type Output = Length<T, Src>;
|
||||
#[inline]
|
||||
fn div(self, scale: ScaleFactor<T, Src, Dst>) -> Length<T, Src> {
|
||||
Length::new(self.get() / scale.get())
|
||||
}
|
||||
}
|
||||
|
||||
// -length
|
||||
impl <U, T:Clone + Neg<Output=T>> Neg for Length<T, U> {
|
||||
type Output = Length<T, U>;
|
||||
#[inline]
|
||||
fn neg(self) -> Length<T, U> {
|
||||
Length::new(-self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Unit, T0: NumCast + Clone> Length<T0, Unit> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
pub fn cast<T1: NumCast + Clone>(&self) -> Option<Length<T1, Unit>> {
|
||||
NumCast::from(self.get()).map(Length::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Unit, T: Clone + PartialEq> PartialEq for Length<T, Unit> {
|
||||
fn eq(&self, other: &Length<T, Unit>) -> bool { self.get().eq(&other.get()) }
|
||||
}
|
||||
|
||||
impl<Unit, T: Clone + PartialOrd> PartialOrd for Length<T, Unit> {
|
||||
fn partial_cmp(&self, other: &Length<T, Unit>) -> Option<Ordering> {
|
||||
self.get().partial_cmp(&other.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Unit, T: Clone + Eq> Eq for Length<T, Unit> {}
|
||||
|
||||
impl<Unit, T: Clone + Ord> Ord for Length<T, Unit> {
|
||||
fn cmp(&self, other: &Length<T, Unit>) -> Ordering { self.get().cmp(&other.get()) }
|
||||
}
|
||||
|
||||
impl<Unit, T: Zero> Zero for Length<T, Unit> {
|
||||
fn zero() -> Length<T, Unit> {
|
||||
Length::new(Zero::zero())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Length;
|
||||
use num::Zero;
|
||||
|
||||
use heapsize::HeapSizeOf;
|
||||
use num_traits::Saturating;
|
||||
use scale_factor::ScaleFactor;
|
||||
use std::f32::INFINITY;
|
||||
|
||||
extern crate serde_test;
|
||||
use self::serde_test::Token;
|
||||
use self::serde_test::assert_tokens;
|
||||
|
||||
enum Inch {}
|
||||
enum Mm {}
|
||||
enum Cm {}
|
||||
enum Second {}
|
||||
|
||||
#[test]
|
||||
fn test_clone() {
|
||||
// A cloned Length is a separate length with the state matching the
|
||||
// original Length at the point it was cloned.
|
||||
let mut variable_length: Length<f32, Inch> = Length::new(12.0);
|
||||
|
||||
let one_foot = variable_length.clone();
|
||||
variable_length.0 = 24.0;
|
||||
|
||||
assert_eq!(one_foot.get(), 12.0);
|
||||
assert_eq!(variable_length.get(), 24.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_heapsizeof_builtins() {
|
||||
// Heap size of built-ins is zero by default.
|
||||
let one_foot: Length<f32, Inch> = Length::new(12.0);
|
||||
|
||||
let heap_size_length_f32 = one_foot.heap_size_of_children();
|
||||
|
||||
assert_eq!(heap_size_length_f32, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_heapsizeof_length_vector() {
|
||||
// Heap size of any Length is just the heap size of the length value.
|
||||
for n in 0..5 {
|
||||
let length: Length<Vec<f32>, Inch> = Length::new(Vec::with_capacity(n));
|
||||
|
||||
assert_eq!(length.heap_size_of_children(), length.0.heap_size_of_children());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_serde() {
|
||||
let one_cm: Length<f32, Mm> = Length::new(10.0);
|
||||
|
||||
assert_tokens(&one_cm, &[Token::F32(10.0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_clones_length_value() {
|
||||
// Calling get returns a clone of the Length's value.
|
||||
// To test this, we need something clone-able - hence a vector.
|
||||
let mut length: Length<Vec<i32>, Inch> = Length::new(vec![1, 2, 3]);
|
||||
|
||||
let value = length.get();
|
||||
length.0.push(4);
|
||||
|
||||
assert_eq!(value, vec![1, 2, 3]);
|
||||
assert_eq!(length.get(), vec![1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_debug() {
|
||||
// Debug and display format the value only.
|
||||
let one_cm: Length<f32, Mm> = Length::new(10.0);
|
||||
|
||||
let result = format!("{:?}", one_cm);
|
||||
|
||||
assert_eq!(result, "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_display() {
|
||||
// Debug and display format the value only.
|
||||
let one_cm: Length<f32, Mm> = Length::new(10.0);
|
||||
|
||||
let result = format!("{}", one_cm);
|
||||
|
||||
assert_eq!(result, "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let length1: Length<u8, Mm> = Length::new(250);
|
||||
let length2: Length<u8, Mm> = Length::new(5);
|
||||
|
||||
let result = length1 + length2;
|
||||
|
||||
assert_eq!(result.get(), 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addassign() {
|
||||
let one_cm: Length<f32, Mm> = Length::new(10.0);
|
||||
let mut measurement: Length<f32, Mm> = Length::new(5.0);
|
||||
|
||||
measurement += one_cm;
|
||||
|
||||
assert_eq!(measurement.get(), 15.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub() {
|
||||
let length1: Length<u8, Mm> = Length::new(250);
|
||||
let length2: Length<u8, Mm> = Length::new(5);
|
||||
|
||||
let result = length1 - length2;
|
||||
|
||||
assert_eq!(result.get(), 245);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subassign() {
|
||||
let one_cm: Length<f32, Mm> = Length::new(10.0);
|
||||
let mut measurement: Length<f32, Mm> = Length::new(5.0);
|
||||
|
||||
measurement -= one_cm;
|
||||
|
||||
assert_eq!(measurement.get(), -5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturating_add() {
|
||||
let length1: Length<u8, Mm> = Length::new(250);
|
||||
let length2: Length<u8, Mm> = Length::new(6);
|
||||
|
||||
let result = length1.saturating_add(length2);
|
||||
|
||||
assert_eq!(result.get(), 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturating_sub() {
|
||||
let length1: Length<u8, Mm> = Length::new(5);
|
||||
let length2: Length<u8, Mm> = Length::new(10);
|
||||
|
||||
let result = length1.saturating_sub(length2);
|
||||
|
||||
assert_eq!(result.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_division_by_length() {
|
||||
// Division results in a ScaleFactor from denominator units
|
||||
// to numerator units.
|
||||
let length: Length<f32, Cm> = Length::new(5.0);
|
||||
let duration: Length<f32, Second> = Length::new(10.0);
|
||||
|
||||
let result = length / duration;
|
||||
|
||||
let expected: ScaleFactor<f32, Second, Cm> = ScaleFactor::new(0.5);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiplication() {
|
||||
let length_mm: Length<f32, Mm> = Length::new(10.0);
|
||||
let cm_per_mm: ScaleFactor<f32, Mm, Cm> = ScaleFactor::new(0.1);
|
||||
|
||||
let result = length_mm * cm_per_mm;
|
||||
|
||||
let expected: Length<f32, Cm> = Length::new(1.0);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_division_by_scalefactor() {
|
||||
let length: Length<f32, Cm> = Length::new(5.0);
|
||||
let cm_per_second: ScaleFactor<f32, Second, Cm> = ScaleFactor::new(10.0);
|
||||
|
||||
let result = length / cm_per_second;
|
||||
|
||||
let expected: Length<f32, Second> = Length::new(0.5);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negation() {
|
||||
let length: Length<f32, Cm> = Length::new(5.0);
|
||||
|
||||
let result = -length;
|
||||
|
||||
let expected: Length<f32, Cm> = Length::new(-5.0);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cast() {
|
||||
let length_as_i32: Length<i32, Cm> = Length::new(5);
|
||||
|
||||
let result: Length<f32, Cm> = length_as_i32.cast().unwrap();
|
||||
|
||||
let length_as_f32: Length<f32, Cm> = Length::new(5.0);
|
||||
assert_eq!(result, length_as_f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equality() {
|
||||
let length_5_point_0: Length<f32, Cm> = Length::new(5.0);
|
||||
let length_5_point_1: Length<f32, Cm> = Length::new(5.1);
|
||||
let length_0_point_1: Length<f32, Cm> = Length::new(0.1);
|
||||
|
||||
assert!(length_5_point_0 == length_5_point_1 - length_0_point_1);
|
||||
assert!(length_5_point_0 != length_5_point_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_order() {
|
||||
let length_5_point_0: Length<f32, Cm> = Length::new(5.0);
|
||||
let length_5_point_1: Length<f32, Cm> = Length::new(5.1);
|
||||
let length_0_point_1: Length<f32, Cm> = Length::new(0.1);
|
||||
|
||||
assert!(length_5_point_0 < length_5_point_1);
|
||||
assert!(length_5_point_0 <= length_5_point_1);
|
||||
assert!(length_5_point_0 <= length_5_point_1 - length_0_point_1);
|
||||
assert!(length_5_point_1 > length_5_point_0);
|
||||
assert!(length_5_point_1 >= length_5_point_0);
|
||||
assert!(length_5_point_0 >= length_5_point_1 - length_0_point_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_add() {
|
||||
type LengthCm = Length<f32, Cm>;
|
||||
let length: LengthCm = Length::new(5.0);
|
||||
|
||||
let result = length - LengthCm::zero();
|
||||
|
||||
assert_eq!(result, length);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_division() {
|
||||
type LengthCm = Length<f32, Cm>;
|
||||
let length: LengthCm = Length::new(5.0);
|
||||
let length_zero: LengthCm = Length::zero();
|
||||
|
||||
let result = length / length_zero;
|
||||
|
||||
let expected: ScaleFactor<f32, Cm, Cm> = ScaleFactor::new(INFINITY);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![cfg_attr(feature = "unstable", feature(asm, repr_simd, test))]
|
||||
|
||||
//! A collection of strongly typed math tools for computer graphics with an inclination
|
||||
//! towards 2d graphics and layout.
|
||||
//!
|
||||
//! All types are generic over the scalar type of their component (`f32`, `i32`, etc.),
|
||||
//! and tagged with a generic Unit parameter which is useful to prevent mixing
|
||||
//! values from different spaces. For example it should not be legal to translate
|
||||
//! a screen-space position by a world-space vector and this can be expressed using
|
||||
//! the generic Unit parameter.
|
||||
//!
|
||||
//! This unit system is not mandatory and all Typed* structures have an alias
|
||||
//! with the default unit: `UnknownUnit`.
|
||||
//! for example ```Point2D<T>``` is equivalent to ```TypedPoint2D<T, UnknownUnit>```.
|
||||
//! Client code typically creates a set of aliases for each type and doesn't need
|
||||
//! to deal with the specifics of typed units further. For example:
|
||||
//!
|
||||
//! All euclid types are marked `#[repr(C)]` in order to facilitate exposing them to
|
||||
//! foreign function interfaces (provided the underlying scalar type is also `repr(C)`).
|
||||
//!
|
||||
//! ```rust
|
||||
//! use euclid::*;
|
||||
//! pub struct ScreenSpace;
|
||||
//! pub type ScreenPoint = TypedPoint2D<f32, ScreenSpace>;
|
||||
//! pub type ScreenSize = TypedSize2D<f32, ScreenSpace>;
|
||||
//! pub struct WorldSpace;
|
||||
//! pub type WorldPoint = TypedPoint3D<f32, WorldSpace>;
|
||||
//! pub type ProjectionMatrix = TypedMatrix4D<f32, WorldSpace, ScreenSpace>;
|
||||
//! // etc...
|
||||
//! ```
|
||||
//!
|
||||
//! Components are accessed in their scalar form by default for convenience, and most
|
||||
//! types additionally implement strongly typed accessors which return typed ```Length``` wrappers.
|
||||
//! For example:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use euclid::*;
|
||||
//! # pub struct WorldSpace;
|
||||
//! # pub type WorldPoint = TypedPoint3D<f32, WorldSpace>;
|
||||
//! let p = WorldPoint::new(0.0, 1.0, 1.0);
|
||||
//! // p.x is an f32.
|
||||
//! println!("p.x = {:?} ", p.x);
|
||||
//! // p.x is a Length<f32, WorldSpace>.
|
||||
//! println!("p.x_typed() = {:?} ", p.x_typed());
|
||||
//! // Length::get returns the scalar value (f32).
|
||||
//! assert_eq!(p.x, p.x_typed().get());
|
||||
//! ```
|
||||
|
||||
extern crate heapsize;
|
||||
|
||||
#[cfg_attr(test, macro_use)]
|
||||
extern crate log;
|
||||
extern crate rustc_serialize;
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate rand;
|
||||
#[cfg(feature = "unstable")]
|
||||
extern crate test;
|
||||
extern crate num_traits;
|
||||
|
||||
pub use length::Length;
|
||||
pub use scale_factor::ScaleFactor;
|
||||
pub use matrix2d::{Matrix2D, TypedMatrix2D};
|
||||
pub use matrix4d::{Matrix4D, TypedMatrix4D};
|
||||
pub use point::{
|
||||
Point2D, TypedPoint2D,
|
||||
Point3D, TypedPoint3D,
|
||||
Point4D, TypedPoint4D,
|
||||
};
|
||||
pub use rect::{Rect, TypedRect};
|
||||
pub use side_offsets::{SideOffsets2D, TypedSideOffsets2D};
|
||||
#[cfg(feature = "unstable")] pub use side_offsets::SideOffsets2DSimdI32;
|
||||
pub use size::{Size2D, TypedSize2D};
|
||||
|
||||
pub mod approxeq;
|
||||
pub mod length;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
pub mod matrix2d;
|
||||
pub mod matrix4d;
|
||||
pub mod num;
|
||||
pub mod point;
|
||||
pub mod rect;
|
||||
pub mod scale_factor;
|
||||
pub mod side_offsets;
|
||||
pub mod size;
|
||||
pub mod trig;
|
||||
|
||||
/// The default unit.
|
||||
#[derive(Clone, Copy, RustcDecodable, RustcEncodable)]
|
||||
pub struct UnknownUnit;
|
||||
|
||||
/// Unit for angles in radians.
|
||||
pub struct Rad;
|
||||
|
||||
/// Unit for angles in degrees.
|
||||
pub struct Deg;
|
||||
|
||||
/// A value in radians.
|
||||
pub type Radians<T> = Length<T, Rad>;
|
||||
|
||||
/// A value in Degrees.
|
||||
pub type Degrees<T> = Length<T, Deg>;
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
macro_rules! define_matrix {
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
pub struct $name:ident<T, $($phantom:ident),+> {
|
||||
$(pub $field:ident: T,)+
|
||||
}
|
||||
) => (
|
||||
#[repr(C)]
|
||||
$(#[$attr])*
|
||||
pub struct $name<T, $($phantom),+> {
|
||||
$(pub $field: T,)+
|
||||
_unit: PhantomData<($($phantom),+)>
|
||||
}
|
||||
|
||||
impl<T: Clone, $($phantom),+> Clone for $name<T, $($phantom),+> {
|
||||
fn clone(&self) -> Self {
|
||||
$name {
|
||||
$($field: self.$field.clone(),)+
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, $($phantom),+> Copy for $name<T, $($phantom),+> {}
|
||||
|
||||
impl<T, $($phantom),+> ::heapsize::HeapSizeOf for $name<T, $($phantom),+>
|
||||
where T: ::heapsize::HeapSizeOf
|
||||
{
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
$(self.$field.heap_size_of_children() +)+ 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, $($phantom),+> ::serde::Deserialize for $name<T, $($phantom),+>
|
||||
where T: ::serde::Deserialize
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: ::serde::Deserializer
|
||||
{
|
||||
let ($($field,)+) =
|
||||
try!(::serde::Deserialize::deserialize(deserializer));
|
||||
Ok($name {
|
||||
$($field: $field,)+
|
||||
_unit: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, $($phantom),+> ::serde::Serialize for $name<T, $($phantom),+>
|
||||
where T: ::serde::Serialize
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: ::serde::Serializer
|
||||
{
|
||||
($(&self.$field,)+).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, $($phantom),+> ::std::cmp::Eq for $name<T, $($phantom),+>
|
||||
where T: ::std::cmp::Eq {}
|
||||
|
||||
impl<T, $($phantom),+> ::std::cmp::PartialEq for $name<T, $($phantom),+>
|
||||
where T: ::std::cmp::PartialEq
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
true $(&& self.$field == other.$field)+
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, $($phantom),+> ::std::hash::Hash for $name<T, $($phantom),+>
|
||||
where T: ::std::hash::Hash
|
||||
{
|
||||
fn hash<H: ::std::hash::Hasher>(&self, h: &mut H) {
|
||||
$(self.$field.hash(h);)+
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,431 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::{UnknownUnit, Radians};
|
||||
use num::{One, Zero};
|
||||
use point::TypedPoint2D;
|
||||
use rect::TypedRect;
|
||||
use std::ops::{Add, Mul, Div, Sub};
|
||||
use std::marker::PhantomData;
|
||||
use approxeq::ApproxEq;
|
||||
use trig::Trig;
|
||||
use std::fmt;
|
||||
|
||||
define_matrix! {
|
||||
/// A 2d transform stored as a 2 by 3 matrix in row-major order in memory,
|
||||
/// useful to represent 2d transformations.
|
||||
///
|
||||
/// Matrices can be parametrized over the source and destination units, to describe a
|
||||
/// transformation from a space to another.
|
||||
/// For example, `TypedMatrix2D<f32, WordSpace, ScreenSpace>::transform_point4d`
|
||||
/// takes a `TypedPoint2D<f32, WordSpace>` and returns a `TypedPoint2D<f32, ScreenSpace>`.
|
||||
///
|
||||
/// Matrices expose a set of convenience methods for pre- and post-transformations.
|
||||
/// A pre-transformation corresponds to adding an operation that is applied before
|
||||
/// the rest of the transformation, while a post-transformation adds an operation
|
||||
/// that is applied after.
|
||||
pub struct TypedMatrix2D<T, Src, Dst> {
|
||||
pub m11: T, pub m12: T,
|
||||
pub m21: T, pub m22: T,
|
||||
pub m31: T, pub m32: T,
|
||||
}
|
||||
}
|
||||
|
||||
/// The default 2d matrix type with no units.
|
||||
pub type Matrix2D<T> = TypedMatrix2D<T, UnknownUnit, UnknownUnit>;
|
||||
|
||||
impl<T: Copy, Src, Dst> TypedMatrix2D<T, Src, Dst> {
|
||||
/// Create a matrix specifying its components in row-major order.
|
||||
pub fn row_major(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
TypedMatrix2D {
|
||||
m11: m11, m12: m12,
|
||||
m21: m21, m22: m22,
|
||||
m31: m31, m32: m32,
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a matrix specifying its components in column-major order.
|
||||
pub fn column_major(m11: T, m21: T, m31: T, m12: T, m22: T, m32: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
TypedMatrix2D {
|
||||
m11: m11, m12: m12,
|
||||
m21: m21, m22: m22,
|
||||
m31: m31, m32: m32,
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an array containing this matrix's terms in row-major order (the order
|
||||
/// in which the matrix is actually laid out in memory).
|
||||
pub fn to_row_major_array(&self) -> [T; 6] {
|
||||
[
|
||||
self.m11, self.m12,
|
||||
self.m21, self.m22,
|
||||
self.m31, self.m32
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns an array containing this matrix's terms in column-major order.
|
||||
pub fn to_column_major_array(&self) -> [T; 6] {
|
||||
[
|
||||
self.m11, self.m21, self.m31,
|
||||
self.m12, self.m22, self.m32
|
||||
]
|
||||
}
|
||||
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
pub fn to_untyped(&self) -> Matrix2D<T> {
|
||||
Matrix2D::row_major(
|
||||
self.m11, self.m12,
|
||||
self.m21, self.m22,
|
||||
self.m31, self.m32
|
||||
)
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
pub fn from_untyped(p: &Matrix2D<T>) -> TypedMatrix2D<T, Src, Dst> {
|
||||
TypedMatrix2D::row_major(
|
||||
p.m11, p.m12,
|
||||
p.m21, p.m22,
|
||||
p.m31, p.m32
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Src, Dst> TypedMatrix2D<T, Src, Dst>
|
||||
where T: Copy +
|
||||
PartialEq +
|
||||
One + Zero {
|
||||
pub fn identity() -> TypedMatrix2D<T, Src, Dst> {
|
||||
let (_0, _1) = (Zero::zero(), One::one());
|
||||
TypedMatrix2D::row_major(
|
||||
_1, _0,
|
||||
_0, _1,
|
||||
_0, _0
|
||||
)
|
||||
}
|
||||
|
||||
// Intentional not public, because it checks for exact equivalence
|
||||
// while most consumers will probably want some sort of approximate
|
||||
// equivalence to deal with floating-point errors.
|
||||
fn is_identity(&self) -> bool {
|
||||
*self == TypedMatrix2D::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Src, Dst> TypedMatrix2D<T, Src, Dst>
|
||||
where T: Copy + Clone +
|
||||
Add<T, Output=T> +
|
||||
Mul<T, Output=T> +
|
||||
Div<T, Output=T> +
|
||||
Sub<T, Output=T> +
|
||||
Trig +
|
||||
PartialOrd +
|
||||
One + Zero {
|
||||
|
||||
/// Returns the multiplication of the two matrices such that mat's transformation
|
||||
/// applies after self's transformation.
|
||||
pub fn post_mul<NewDst>(&self, mat: &TypedMatrix2D<T, Dst, NewDst>) -> TypedMatrix2D<T, Src, NewDst> {
|
||||
TypedMatrix2D::row_major(
|
||||
self.m11 * mat.m11 + self.m12 * mat.m21,
|
||||
self.m11 * mat.m12 + self.m12 * mat.m22,
|
||||
self.m21 * mat.m11 + self.m22 * mat.m21,
|
||||
self.m21 * mat.m12 + self.m22 * mat.m22,
|
||||
self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31,
|
||||
self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the multiplication of the two matrices such that mat's transformation
|
||||
/// applies before self's transformation.
|
||||
pub fn pre_mul<NewSrc>(&self, mat: &TypedMatrix2D<T, NewSrc, Src>) -> TypedMatrix2D<T, NewSrc, Dst> {
|
||||
mat.post_mul(self)
|
||||
}
|
||||
|
||||
/// Returns a translation matrix.
|
||||
pub fn create_translation(x: T, y: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
TypedMatrix2D::row_major(
|
||||
_1, _0,
|
||||
_0, _1,
|
||||
x, y
|
||||
)
|
||||
}
|
||||
|
||||
/// Applies a translation after self's transformation and returns the resulting matrix.
|
||||
pub fn post_translated(&self, x: T, y: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
self.post_mul(&TypedMatrix2D::create_translation(x, y))
|
||||
}
|
||||
|
||||
/// Applies a translation before self's transformation and returns the resulting matrix.
|
||||
pub fn pre_translated(&self, x: T, y: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
self.pre_mul(&TypedMatrix2D::create_translation(x, y))
|
||||
}
|
||||
|
||||
/// Returns a scale matrix.
|
||||
pub fn create_scale(x: T, y: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
let _0 = Zero::zero();
|
||||
TypedMatrix2D::row_major(
|
||||
x, _0,
|
||||
_0, y,
|
||||
_0, _0
|
||||
)
|
||||
}
|
||||
|
||||
/// Applies a scale after self's transformation and returns the resulting matrix.
|
||||
pub fn post_scaled(&self, x: T, y: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
self.post_mul(&TypedMatrix2D::create_scale(x, y))
|
||||
}
|
||||
|
||||
/// Applies a scale before self's transformation and returns the resulting matrix.
|
||||
pub fn pre_scaled(&self, x: T, y: T) -> TypedMatrix2D<T, Src, Dst> {
|
||||
TypedMatrix2D::row_major(
|
||||
self.m11 * x, self.m12,
|
||||
self.m21, self.m22 * y,
|
||||
self.m31, self.m32
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a rotation matrix.
|
||||
pub fn create_rotation(theta: Radians<T>) -> TypedMatrix2D<T, Src, Dst> {
|
||||
let _0 = Zero::zero();
|
||||
let cos = theta.get().cos();
|
||||
let sin = theta.get().sin();
|
||||
TypedMatrix2D::row_major(
|
||||
cos, _0 - sin,
|
||||
sin, cos,
|
||||
_0, _0
|
||||
)
|
||||
}
|
||||
|
||||
/// Applies a rotation after self's transformation and returns the resulting matrix.
|
||||
pub fn post_rotated(&self, theta: Radians<T>) -> TypedMatrix2D<T, Src, Dst> {
|
||||
self.post_mul(&TypedMatrix2D::create_rotation(theta))
|
||||
}
|
||||
|
||||
/// Applies a rotation after self's transformation and returns the resulting matrix.
|
||||
pub fn pre_rotated(&self, theta: Radians<T>) -> TypedMatrix2D<T, Src, Dst> {
|
||||
self.pre_mul(&TypedMatrix2D::create_rotation(theta))
|
||||
}
|
||||
|
||||
/// Returns the given point transformed by this matrix.
|
||||
#[inline]
|
||||
pub fn transform_point(&self, point: &TypedPoint2D<T, Src>) -> TypedPoint2D<T, Dst> {
|
||||
TypedPoint2D::new(point.x * self.m11 + point.y * self.m21 + self.m31,
|
||||
point.x * self.m12 + point.y * self.m22 + self.m32)
|
||||
}
|
||||
|
||||
/// Returns a rectangle that encompasses the result of transforming the given rectangle by this
|
||||
/// matrix.
|
||||
#[inline]
|
||||
pub fn transform_rect(&self, rect: &TypedRect<T, Src>) -> TypedRect<T, Dst> {
|
||||
TypedRect::from_points(&[
|
||||
self.transform_point(&rect.origin),
|
||||
self.transform_point(&rect.top_right()),
|
||||
self.transform_point(&rect.bottom_left()),
|
||||
self.transform_point(&rect.bottom_right()),
|
||||
])
|
||||
}
|
||||
|
||||
/// Computes and returns the determinant of this matrix.
|
||||
pub fn determinant(&self) -> T {
|
||||
self.m11 * self.m22 - self.m12 * self.m21
|
||||
}
|
||||
|
||||
/// Returns the inverse matrix if possible.
|
||||
pub fn inverse(&self) -> Option<TypedMatrix2D<T, Dst, Src>> {
|
||||
let det = self.determinant();
|
||||
|
||||
let _0: T = Zero::zero();
|
||||
let _1: T = One::one();
|
||||
|
||||
if det == _0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inv_det = _1 / det;
|
||||
Some(TypedMatrix2D::row_major(
|
||||
inv_det * self.m22,
|
||||
inv_det * (_0 - self.m12),
|
||||
inv_det * (_0 - self.m21),
|
||||
inv_det * self.m11,
|
||||
inv_det * (self.m21 * self.m32 - self.m22 * self.m31),
|
||||
inv_det * (self.m31 * self.m12 - self.m11 * self.m32),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the same matrix with a different destination unit.
|
||||
#[inline]
|
||||
pub fn with_destination<NewDst>(&self) -> TypedMatrix2D<T, Src, NewDst> {
|
||||
TypedMatrix2D::row_major(
|
||||
self.m11, self.m12,
|
||||
self.m21, self.m22,
|
||||
self.m31, self.m32,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the same matrix with a different source unit.
|
||||
#[inline]
|
||||
pub fn with_source<NewSrc>(&self) -> TypedMatrix2D<T, NewSrc, Dst> {
|
||||
TypedMatrix2D::row_major(
|
||||
self.m11, self.m12,
|
||||
self.m21, self.m22,
|
||||
self.m31, self.m32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ApproxEq<T>, Src, Dst> TypedMatrix2D<T, Src, Dst> {
|
||||
pub fn approx_eq(&self, other: &Self) -> bool {
|
||||
self.m11.approx_eq(&other.m11) && self.m12.approx_eq(&other.m12) &&
|
||||
self.m21.approx_eq(&other.m21) && self.m22.approx_eq(&other.m22) &&
|
||||
self.m31.approx_eq(&other.m31) && self.m32.approx_eq(&other.m32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + fmt::Debug, Src, Dst> fmt::Debug for TypedMatrix2D<T, Src, Dst>
|
||||
where T: Copy + fmt::Debug +
|
||||
PartialEq +
|
||||
One + Zero {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_identity() {
|
||||
write!(f, "[I]")
|
||||
} else {
|
||||
self.to_row_major_array().fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use approxeq::ApproxEq;
|
||||
use point::Point2D;
|
||||
use Radians;
|
||||
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
type Mat = Matrix2D<f32>;
|
||||
|
||||
fn rad(v: f32) -> Radians<f32> { Radians::new(v) }
|
||||
|
||||
#[test]
|
||||
pub fn test_translation() {
|
||||
let t1 = Mat::create_translation(1.0, 2.0);
|
||||
let t2 = Mat::identity().pre_translated(1.0, 2.0);
|
||||
let t3 = Mat::identity().post_translated(1.0, 2.0);
|
||||
assert_eq!(t1, t2);
|
||||
assert_eq!(t1, t3);
|
||||
|
||||
assert_eq!(t1.transform_point(&Point2D::new(1.0, 1.0)), Point2D::new(2.0, 3.0));
|
||||
|
||||
assert_eq!(t1.post_mul(&t1), Mat::create_translation(2.0, 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_rotation() {
|
||||
let r1 = Mat::create_rotation(rad(FRAC_PI_2));
|
||||
let r2 = Mat::identity().pre_rotated(rad(FRAC_PI_2));
|
||||
let r3 = Mat::identity().post_rotated(rad(FRAC_PI_2));
|
||||
assert_eq!(r1, r2);
|
||||
assert_eq!(r1, r3);
|
||||
|
||||
assert!(r1.transform_point(&Point2D::new(1.0, 2.0)).approx_eq(&Point2D::new(2.0, -1.0)));
|
||||
|
||||
assert!(r1.post_mul(&r1).approx_eq(&Mat::create_rotation(rad(FRAC_PI_2*2.0))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_scale() {
|
||||
let s1 = Mat::create_scale(2.0, 3.0);
|
||||
let s2 = Mat::identity().pre_scaled(2.0, 3.0);
|
||||
let s3 = Mat::identity().post_scaled(2.0, 3.0);
|
||||
assert_eq!(s1, s2);
|
||||
assert_eq!(s1, s3);
|
||||
|
||||
assert!(s1.transform_point(&Point2D::new(2.0, 2.0)).approx_eq(&Point2D::new(4.0, 6.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_major() {
|
||||
assert_eq!(
|
||||
Mat::row_major(
|
||||
1.0, 2.0,
|
||||
3.0, 4.0,
|
||||
5.0, 6.0
|
||||
),
|
||||
Mat::column_major(
|
||||
1.0, 3.0, 5.0,
|
||||
2.0, 4.0, 6.0,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_simple() {
|
||||
let m1 = Mat::identity();
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.approx_eq(&m2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_scale() {
|
||||
let m1 = Mat::create_scale(1.5, 0.3);
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.pre_mul(&m2).approx_eq(&Mat::identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_translate() {
|
||||
let m1 = Mat::create_translation(-132.0, 0.3);
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.pre_mul(&m2).approx_eq(&Mat::identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse_none() {
|
||||
assert!(Mat::create_scale(2.0, 0.0).inverse().is_none());
|
||||
assert!(Mat::create_scale(2.0, 2.0).inverse().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_pre_post() {
|
||||
let m1 = Matrix2D::identity().post_scaled(1.0, 2.0).post_translated(1.0, 2.0);
|
||||
let m2 = Matrix2D::identity().pre_translated(1.0, 2.0).pre_scaled(1.0, 2.0);
|
||||
assert!(m1.approx_eq(&m2));
|
||||
|
||||
let r = Mat::create_rotation(rad(FRAC_PI_2));
|
||||
let t = Mat::create_translation(2.0, 3.0);
|
||||
|
||||
let a = Point2D::new(1.0, 1.0);
|
||||
|
||||
assert!(r.post_mul(&t).transform_point(&a).approx_eq(&Point2D::new(3.0, 2.0)));
|
||||
assert!(t.post_mul(&r).transform_point(&a).approx_eq(&Point2D::new(4.0, -3.0)));
|
||||
assert!(t.post_mul(&r).transform_point(&a).approx_eq(&r.transform_point(&t.transform_point(&a))));
|
||||
|
||||
assert!(r.pre_mul(&t).transform_point(&a).approx_eq(&Point2D::new(4.0, -3.0)));
|
||||
assert!(t.pre_mul(&r).transform_point(&a).approx_eq(&Point2D::new(3.0, 2.0)));
|
||||
assert!(t.pre_mul(&r).transform_point(&a).approx_eq(&t.transform_point(&r.transform_point(&a))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size_of() {
|
||||
use std::mem::size_of;
|
||||
assert_eq!(size_of::<Matrix2D<f32>>(), 6*size_of::<f32>());
|
||||
assert_eq!(size_of::<Matrix2D<f64>>(), 6*size_of::<f64>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_identity() {
|
||||
let m1 = Matrix2D::identity();
|
||||
assert!(m1.is_identity());
|
||||
let m2 = m1.post_translated(0.1, 0.0);
|
||||
assert!(!m2.is_identity());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,825 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::{UnknownUnit, Radians};
|
||||
use approxeq::ApproxEq;
|
||||
use trig::Trig;
|
||||
use point::{TypedPoint2D, TypedPoint3D, TypedPoint4D};
|
||||
use rect::TypedRect;
|
||||
use matrix2d::TypedMatrix2D;
|
||||
use scale_factor::ScaleFactor;
|
||||
use num::{One, Zero};
|
||||
use std::ops::{Add, Mul, Sub, Div, Neg};
|
||||
use std::marker::PhantomData;
|
||||
use std::fmt;
|
||||
|
||||
define_matrix! {
|
||||
/// A 4 by 4 matrix stored in row-major order in memory, useful to represent
|
||||
/// 3d transformations.
|
||||
///
|
||||
/// Matrices can be parametrized over the source and destination units, to describe a
|
||||
/// transformation from a space to another.
|
||||
/// For example, `TypedMatrix4D<f32, WordSpace, ScreenSpace>::transform_point4d`
|
||||
/// takes a `TypedPoint4D<f32, WordSpace>` and returns a `TypedPoint4D<f32, ScreenSpace>`.
|
||||
///
|
||||
/// Matrices expose a set of convenience methods for pre- and post-transformations.
|
||||
/// A pre-transformation corresponds to adding an operation that is applied before
|
||||
/// the rest of the transformation, while a post-transformation adds an operation
|
||||
/// that is applied after.
|
||||
pub struct TypedMatrix4D<T, Src, Dst> {
|
||||
pub m11: T, pub m12: T, pub m13: T, pub m14: T,
|
||||
pub m21: T, pub m22: T, pub m23: T, pub m24: T,
|
||||
pub m31: T, pub m32: T, pub m33: T, pub m34: T,
|
||||
pub m41: T, pub m42: T, pub m43: T, pub m44: T,
|
||||
}
|
||||
}
|
||||
|
||||
/// The default 4d matrix type with no units.
|
||||
pub type Matrix4D<T> = TypedMatrix4D<T, UnknownUnit, UnknownUnit>;
|
||||
|
||||
impl<T, Src, Dst> TypedMatrix4D<T, Src, Dst> {
|
||||
/// Create a matrix specifying its components in row-major order.
|
||||
///
|
||||
/// For example, the translation terms m41, m42, m43 on the last row with the
|
||||
/// row-major convention) are the 13rd, 14th and 15th parameters.
|
||||
#[inline]
|
||||
pub fn row_major(
|
||||
m11: T, m12: T, m13: T, m14: T,
|
||||
m21: T, m22: T, m23: T, m24: T,
|
||||
m31: T, m32: T, m33: T, m34: T,
|
||||
m41: T, m42: T, m43: T, m44: T)
|
||||
-> TypedMatrix4D<T, Src, Dst> {
|
||||
TypedMatrix4D {
|
||||
m11: m11, m12: m12, m13: m13, m14: m14,
|
||||
m21: m21, m22: m22, m23: m23, m24: m24,
|
||||
m31: m31, m32: m32, m33: m33, m34: m34,
|
||||
m41: m41, m42: m42, m43: m43, m44: m44,
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a matrix specifying its components in column-major order.
|
||||
///
|
||||
/// For example, the translation terms m41, m42, m43 on the last column with the
|
||||
/// column-major convention) are the 4th, 8th and 12nd parameters.
|
||||
#[inline]
|
||||
pub fn column_major(
|
||||
m11: T, m21: T, m31: T, m41: T,
|
||||
m12: T, m22: T, m32: T, m42: T,
|
||||
m13: T, m23: T, m33: T, m43: T,
|
||||
m14: T, m24: T, m34: T, m44: T)
|
||||
-> TypedMatrix4D<T, Src, Dst> {
|
||||
TypedMatrix4D {
|
||||
m11: m11, m12: m12, m13: m13, m14: m14,
|
||||
m21: m21, m22: m22, m23: m23, m24: m24,
|
||||
m31: m31, m32: m32, m33: m33, m34: m34,
|
||||
m41: m41, m42: m42, m43: m43, m44: m44,
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <T, Src, Dst> TypedMatrix4D<T, Src, Dst>
|
||||
where T: Copy + Clone +
|
||||
PartialEq +
|
||||
One + Zero {
|
||||
#[inline]
|
||||
pub fn identity() -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
TypedMatrix4D::row_major(
|
||||
_1, _0, _0, _0,
|
||||
_0, _1, _0, _0,
|
||||
_0, _0, _1, _0,
|
||||
_0, _0, _0, _1
|
||||
)
|
||||
}
|
||||
|
||||
// Intentional not public, because it checks for exact equivalence
|
||||
// while most consumers will probably want some sort of approximate
|
||||
// equivalence to deal with floating-point errors.
|
||||
#[inline]
|
||||
fn is_identity(&self) -> bool {
|
||||
*self == TypedMatrix4D::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl <T, Src, Dst> TypedMatrix4D<T, Src, Dst>
|
||||
where T: Copy + Clone +
|
||||
Add<T, Output=T> +
|
||||
Sub<T, Output=T> +
|
||||
Mul<T, Output=T> +
|
||||
Div<T, Output=T> +
|
||||
Neg<Output=T> +
|
||||
ApproxEq<T> +
|
||||
PartialOrd +
|
||||
Trig +
|
||||
One + Zero {
|
||||
|
||||
/// Create a 4 by 4 matrix representing a 2d transformation, specifying its components
|
||||
/// in row-major order.
|
||||
#[inline]
|
||||
pub fn row_major_2d(m11: T, m12: T, m21: T, m22: T, m41: T, m42: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
TypedMatrix4D::row_major(
|
||||
m11, m12, _0, _0,
|
||||
m21, m22, _0, _0,
|
||||
_0, _0, _1, _0,
|
||||
m41, m42, _0, _1
|
||||
)
|
||||
}
|
||||
|
||||
/// Create an orthogonal projection matrix.
|
||||
pub fn ortho(left: T, right: T,
|
||||
bottom: T, top: T,
|
||||
near: T, far: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let tx = -((right + left) / (right - left));
|
||||
let ty = -((top + bottom) / (top - bottom));
|
||||
let tz = -((far + near) / (far - near));
|
||||
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
let _2 = _1 + _1;
|
||||
TypedMatrix4D::row_major(
|
||||
_2 / (right - left), _0 , _0 , _0,
|
||||
_0 , _2 / (top - bottom), _0 , _0,
|
||||
_0 , _0 , -_2 / (far - near), _0,
|
||||
tx , ty , tz , _1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if this matrix can be represented with a TypedMatrix2D.
|
||||
///
|
||||
/// See https://drafts.csswg.org/css-transforms/#2d-matrix
|
||||
#[inline]
|
||||
pub fn is_2d(&self) -> bool {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
self.m31 == _0 && self.m32 == _0 &&
|
||||
self.m13 == _0 && self.m23 == _0 &&
|
||||
self.m43 == _0 && self.m14 == _0 &&
|
||||
self.m24 == _0 && self.m34 == _0 &&
|
||||
self.m33 == _1 && self.m44 == _1
|
||||
}
|
||||
|
||||
/// Create a 2D matrix picking the relevent terms from this matrix.
|
||||
///
|
||||
/// This method assumes that self represents a 2d transformation, callers
|
||||
/// should check that self.is_2d() returns true beforehand.
|
||||
pub fn to_2d(&self) -> TypedMatrix2D<T, Src, Dst> {
|
||||
TypedMatrix2D::row_major(
|
||||
self.m11, self.m12,
|
||||
self.m21, self.m22,
|
||||
self.m41, self.m42
|
||||
)
|
||||
}
|
||||
|
||||
pub fn approx_eq(&self, other: &TypedMatrix4D<T, Src, Dst>) -> bool {
|
||||
self.m11.approx_eq(&other.m11) && self.m12.approx_eq(&other.m12) &&
|
||||
self.m13.approx_eq(&other.m13) && self.m14.approx_eq(&other.m14) &&
|
||||
self.m21.approx_eq(&other.m21) && self.m22.approx_eq(&other.m22) &&
|
||||
self.m23.approx_eq(&other.m23) && self.m24.approx_eq(&other.m24) &&
|
||||
self.m31.approx_eq(&other.m31) && self.m32.approx_eq(&other.m32) &&
|
||||
self.m33.approx_eq(&other.m33) && self.m34.approx_eq(&other.m34) &&
|
||||
self.m41.approx_eq(&other.m41) && self.m42.approx_eq(&other.m42) &&
|
||||
self.m43.approx_eq(&other.m43) && self.m44.approx_eq(&other.m44)
|
||||
}
|
||||
|
||||
/// Returns the same matrix with a different destination unit.
|
||||
#[inline]
|
||||
pub fn with_destination<NewDst>(&self) -> TypedMatrix4D<T, Src, NewDst> {
|
||||
TypedMatrix4D::row_major(
|
||||
self.m11, self.m12, self.m13, self.m14,
|
||||
self.m21, self.m22, self.m23, self.m24,
|
||||
self.m31, self.m32, self.m33, self.m34,
|
||||
self.m41, self.m42, self.m43, self.m44,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the same matrix with a different source unit.
|
||||
#[inline]
|
||||
pub fn with_source<NewSrc>(&self) -> TypedMatrix4D<T, NewSrc, Dst> {
|
||||
TypedMatrix4D::row_major(
|
||||
self.m11, self.m12, self.m13, self.m14,
|
||||
self.m21, self.m22, self.m23, self.m24,
|
||||
self.m31, self.m32, self.m33, self.m34,
|
||||
self.m41, self.m42, self.m43, self.m44,
|
||||
)
|
||||
}
|
||||
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
#[inline]
|
||||
pub fn to_untyped(&self) -> Matrix4D<T> {
|
||||
Matrix4D::row_major(
|
||||
self.m11, self.m12, self.m13, self.m14,
|
||||
self.m21, self.m22, self.m23, self.m24,
|
||||
self.m31, self.m32, self.m33, self.m34,
|
||||
self.m41, self.m42, self.m43, self.m44,
|
||||
)
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
#[inline]
|
||||
pub fn from_untyped(m: &Matrix4D<T>) -> Self {
|
||||
TypedMatrix4D::row_major(
|
||||
m.m11, m.m12, m.m13, m.m14,
|
||||
m.m21, m.m22, m.m23, m.m24,
|
||||
m.m31, m.m32, m.m33, m.m34,
|
||||
m.m41, m.m42, m.m43, m.m44,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the multiplication of the two matrices such that mat's transformation
|
||||
/// applies after self's transformation.
|
||||
pub fn post_mul<NewDst>(&self, mat: &TypedMatrix4D<T, Dst, NewDst>) -> TypedMatrix4D<T, Src, NewDst> {
|
||||
TypedMatrix4D::row_major(
|
||||
self.m11 * mat.m11 + self.m12 * mat.m21 + self.m13 * mat.m31 + self.m14 * mat.m41,
|
||||
self.m11 * mat.m12 + self.m12 * mat.m22 + self.m13 * mat.m32 + self.m14 * mat.m42,
|
||||
self.m11 * mat.m13 + self.m12 * mat.m23 + self.m13 * mat.m33 + self.m14 * mat.m43,
|
||||
self.m11 * mat.m14 + self.m12 * mat.m24 + self.m13 * mat.m34 + self.m14 * mat.m44,
|
||||
self.m21 * mat.m11 + self.m22 * mat.m21 + self.m23 * mat.m31 + self.m24 * mat.m41,
|
||||
self.m21 * mat.m12 + self.m22 * mat.m22 + self.m23 * mat.m32 + self.m24 * mat.m42,
|
||||
self.m21 * mat.m13 + self.m22 * mat.m23 + self.m23 * mat.m33 + self.m24 * mat.m43,
|
||||
self.m21 * mat.m14 + self.m22 * mat.m24 + self.m23 * mat.m34 + self.m24 * mat.m44,
|
||||
self.m31 * mat.m11 + self.m32 * mat.m21 + self.m33 * mat.m31 + self.m34 * mat.m41,
|
||||
self.m31 * mat.m12 + self.m32 * mat.m22 + self.m33 * mat.m32 + self.m34 * mat.m42,
|
||||
self.m31 * mat.m13 + self.m32 * mat.m23 + self.m33 * mat.m33 + self.m34 * mat.m43,
|
||||
self.m31 * mat.m14 + self.m32 * mat.m24 + self.m33 * mat.m34 + self.m34 * mat.m44,
|
||||
self.m41 * mat.m11 + self.m42 * mat.m21 + self.m43 * mat.m31 + self.m44 * mat.m41,
|
||||
self.m41 * mat.m12 + self.m42 * mat.m22 + self.m43 * mat.m32 + self.m44 * mat.m42,
|
||||
self.m41 * mat.m13 + self.m42 * mat.m23 + self.m43 * mat.m33 + self.m44 * mat.m43,
|
||||
self.m41 * mat.m14 + self.m42 * mat.m24 + self.m43 * mat.m34 + self.m44 * mat.m44,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the multiplication of the two matrices such that mat's transformation
|
||||
/// applies before self's transformation.
|
||||
pub fn pre_mul<NewSrc>(&self, mat: &TypedMatrix4D<T, NewSrc, Src>) -> TypedMatrix4D<T, NewSrc, Dst> {
|
||||
mat.post_mul(self)
|
||||
}
|
||||
|
||||
/// Returns the inverse matrix if possible.
|
||||
pub fn inverse(&self) -> Option<TypedMatrix4D<T, Dst, Src>> {
|
||||
let det = self.determinant();
|
||||
|
||||
if det == Zero::zero() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// todo(gw): this could be made faster by special casing
|
||||
// for simpler matrix types.
|
||||
let m = TypedMatrix4D::row_major(
|
||||
self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 +
|
||||
self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 -
|
||||
self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44,
|
||||
|
||||
self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 -
|
||||
self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 +
|
||||
self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44,
|
||||
|
||||
self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 +
|
||||
self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 -
|
||||
self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44,
|
||||
|
||||
self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 -
|
||||
self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 +
|
||||
self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34,
|
||||
|
||||
self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 -
|
||||
self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 +
|
||||
self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44,
|
||||
|
||||
self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 +
|
||||
self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 -
|
||||
self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44,
|
||||
|
||||
self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 -
|
||||
self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 +
|
||||
self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44,
|
||||
|
||||
self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 +
|
||||
self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 -
|
||||
self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34,
|
||||
|
||||
self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 +
|
||||
self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 -
|
||||
self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44,
|
||||
|
||||
self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 -
|
||||
self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 +
|
||||
self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44,
|
||||
|
||||
self.m12*self.m24*self.m41 - self.m14*self.m22*self.m41 +
|
||||
self.m14*self.m21*self.m42 - self.m11*self.m24*self.m42 -
|
||||
self.m12*self.m21*self.m44 + self.m11*self.m22*self.m44,
|
||||
|
||||
self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 -
|
||||
self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 +
|
||||
self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34,
|
||||
|
||||
self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 -
|
||||
self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 +
|
||||
self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43,
|
||||
|
||||
self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 +
|
||||
self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 -
|
||||
self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43,
|
||||
|
||||
self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 -
|
||||
self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 +
|
||||
self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43,
|
||||
|
||||
self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 +
|
||||
self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 -
|
||||
self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33
|
||||
);
|
||||
|
||||
let _1: T = One::one();
|
||||
Some(m.mul_s(_1 / det))
|
||||
}
|
||||
|
||||
/// Compute the determinant of the matrix.
|
||||
pub fn determinant(&self) -> T {
|
||||
self.m14 * self.m23 * self.m32 * self.m41 -
|
||||
self.m13 * self.m24 * self.m32 * self.m41 -
|
||||
self.m14 * self.m22 * self.m33 * self.m41 +
|
||||
self.m12 * self.m24 * self.m33 * self.m41 +
|
||||
self.m13 * self.m22 * self.m34 * self.m41 -
|
||||
self.m12 * self.m23 * self.m34 * self.m41 -
|
||||
self.m14 * self.m23 * self.m31 * self.m42 +
|
||||
self.m13 * self.m24 * self.m31 * self.m42 +
|
||||
self.m14 * self.m21 * self.m33 * self.m42 -
|
||||
self.m11 * self.m24 * self.m33 * self.m42 -
|
||||
self.m13 * self.m21 * self.m34 * self.m42 +
|
||||
self.m11 * self.m23 * self.m34 * self.m42 +
|
||||
self.m14 * self.m22 * self.m31 * self.m43 -
|
||||
self.m12 * self.m24 * self.m31 * self.m43 -
|
||||
self.m14 * self.m21 * self.m32 * self.m43 +
|
||||
self.m11 * self.m24 * self.m32 * self.m43 +
|
||||
self.m12 * self.m21 * self.m34 * self.m43 -
|
||||
self.m11 * self.m22 * self.m34 * self.m43 -
|
||||
self.m13 * self.m22 * self.m31 * self.m44 +
|
||||
self.m12 * self.m23 * self.m31 * self.m44 +
|
||||
self.m13 * self.m21 * self.m32 * self.m44 -
|
||||
self.m11 * self.m23 * self.m32 * self.m44 -
|
||||
self.m12 * self.m21 * self.m33 * self.m44 +
|
||||
self.m11 * self.m22 * self.m33 * self.m44
|
||||
}
|
||||
|
||||
/// Multiplies all of the matrix's component by a scalar and returns the result.
|
||||
pub fn mul_s(&self, x: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
TypedMatrix4D::row_major(
|
||||
self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x,
|
||||
self.m21 * x, self.m22 * x, self.m23 * x, self.m24 * x,
|
||||
self.m31 * x, self.m32 * x, self.m33 * x, self.m34 * x,
|
||||
self.m41 * x, self.m42 * x, self.m43 * x, self.m44 * x
|
||||
)
|
||||
}
|
||||
|
||||
/// Convenience function to create a scale matrix from a ScaleFactor.
|
||||
pub fn from_scale_factor(scale: ScaleFactor<T, Src, Dst>) -> TypedMatrix4D<T, Src, Dst> {
|
||||
TypedMatrix4D::create_scale(scale.get(), scale.get(), scale.get())
|
||||
}
|
||||
|
||||
/// Returns the given 2d point transformed by this matrix.
|
||||
///
|
||||
/// The input point must be use the unit Src, and the returned point has the unit Dst.
|
||||
#[inline]
|
||||
pub fn transform_point(&self, p: &TypedPoint2D<T, Src>) -> TypedPoint2D<T, Dst> {
|
||||
self.transform_point4d(&TypedPoint4D::new(p.x, p.y, Zero::zero(), One::one())).to_2d()
|
||||
}
|
||||
|
||||
/// Returns the given 3d point transformed by this matrix.
|
||||
///
|
||||
/// The input point must be use the unit Src, and the returned point has the unit Dst.
|
||||
#[inline]
|
||||
pub fn transform_point3d(&self, p: &TypedPoint3D<T, Src>) -> TypedPoint3D<T, Dst> {
|
||||
self.transform_point4d(&TypedPoint4D::new(p.x, p.y, p.z, One::one())).to_3d()
|
||||
}
|
||||
|
||||
/// Returns the given 4d point transformed by this matrix.
|
||||
///
|
||||
/// The input point must be use the unit Src, and the returned point has the unit Dst.
|
||||
#[inline]
|
||||
pub fn transform_point4d(&self, p: &TypedPoint4D<T, Src>) -> TypedPoint4D<T, Dst> {
|
||||
let x = p.x * self.m11 + p.y * self.m21 + p.z * self.m31 + p.w * self.m41;
|
||||
let y = p.x * self.m12 + p.y * self.m22 + p.z * self.m32 + p.w * self.m42;
|
||||
let z = p.x * self.m13 + p.y * self.m23 + p.z * self.m33 + p.w * self.m43;
|
||||
let w = p.x * self.m14 + p.y * self.m24 + p.z * self.m34 + p.w * self.m44;
|
||||
TypedPoint4D::new(x, y, z, w)
|
||||
}
|
||||
|
||||
/// Returns a rectangle that encompasses the result of transforming the given rectangle by this
|
||||
/// matrix.
|
||||
pub fn transform_rect(&self, rect: &TypedRect<T, Src>) -> TypedRect<T, Dst> {
|
||||
TypedRect::from_points(&[
|
||||
self.transform_point(&rect.origin),
|
||||
self.transform_point(&rect.top_right()),
|
||||
self.transform_point(&rect.bottom_left()),
|
||||
self.transform_point(&rect.bottom_right()),
|
||||
])
|
||||
}
|
||||
|
||||
/// Create a 3d translation matrix
|
||||
pub fn create_translation(x: T, y: T, z: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
TypedMatrix4D::row_major(
|
||||
_1, _0, _0, _0,
|
||||
_0, _1, _0, _0,
|
||||
_0, _0, _1, _0,
|
||||
x, y, z, _1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a matrix with a translation applied before self's transformation.
|
||||
pub fn pre_translated(&self, x: T, y: T, z: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
self.pre_mul(&TypedMatrix4D::create_translation(x, y, z))
|
||||
}
|
||||
|
||||
/// Returns a matrix with a translation applied after self's transformation.
|
||||
pub fn post_translated(&self, x: T, y: T, z: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
self.post_mul(&TypedMatrix4D::create_translation(x, y, z))
|
||||
}
|
||||
|
||||
/// Create a 3d scale matrix
|
||||
pub fn create_scale(x: T, y: T, z: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
TypedMatrix4D::row_major(
|
||||
x, _0, _0, _0,
|
||||
_0, y, _0, _0,
|
||||
_0, _0, z, _0,
|
||||
_0, _0, _0, _1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a matrix with a scale applied before self's transformation.
|
||||
pub fn pre_scaled(&self, x: T, y: T, z: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
TypedMatrix4D::row_major(
|
||||
self.m11 * x, self.m12, self.m13, self.m14,
|
||||
self.m21 , self.m22 * y, self.m23, self.m24,
|
||||
self.m31 , self.m32, self.m33 * z, self.m34,
|
||||
self.m41 , self.m42, self.m43, self.m44
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a matrix with a scale applied after self's transformation.
|
||||
pub fn post_scaled(&self, x: T, y: T, z: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
self.post_mul(&TypedMatrix4D::create_scale(x, y, z))
|
||||
}
|
||||
|
||||
/// Create a 3d rotation matrix from an angle / axis.
|
||||
/// The supplied axis must be normalized.
|
||||
pub fn create_rotation(x: T, y: T, z: T, theta: Radians<T>) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
let _2 = _1 + _1;
|
||||
|
||||
let xx = x * x;
|
||||
let yy = y * y;
|
||||
let zz = z * z;
|
||||
|
||||
let half_theta = theta.get() / _2;
|
||||
let sc = half_theta.sin() * half_theta.cos();
|
||||
let sq = half_theta.sin() * half_theta.sin();
|
||||
|
||||
TypedMatrix4D::row_major(
|
||||
_1 - _2 * (yy + zz) * sq,
|
||||
_2 * (x * y * sq - z * sc),
|
||||
_2 * (x * z * sq + y * sc),
|
||||
_0,
|
||||
|
||||
_2 * (x * y * sq + z * sc),
|
||||
_1 - _2 * (xx + zz) * sq,
|
||||
_2 * (y * z * sq - x * sc),
|
||||
_0,
|
||||
|
||||
_2 * (x * z * sq - y * sc),
|
||||
_2 * (y * z * sq + x * sc),
|
||||
_1 - _2 * (xx + yy) * sq,
|
||||
_0,
|
||||
|
||||
_0,
|
||||
_0,
|
||||
_0,
|
||||
_1
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a matrix with a rotation applied after self's transformation.
|
||||
pub fn post_rotated(&self, x: T, y: T, z: T, theta: Radians<T>) -> TypedMatrix4D<T, Src, Dst> {
|
||||
self.post_mul(&TypedMatrix4D::create_rotation(x, y, z, theta))
|
||||
}
|
||||
|
||||
/// Returns a matrix with a rotation applied before self's transformation.
|
||||
pub fn pre_rotated(&self, x: T, y: T, z: T, theta: Radians<T>) -> TypedMatrix4D<T, Src, Dst> {
|
||||
self.pre_mul(&TypedMatrix4D::create_rotation(x, y, z, theta))
|
||||
}
|
||||
|
||||
/// Create a 2d skew matrix.
|
||||
///
|
||||
/// See https://drafts.csswg.org/css-transforms/#funcdef-skew
|
||||
pub fn create_skew(alpha: Radians<T>, beta: Radians<T>) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
let (sx, sy) = (beta.get().tan(), alpha.get().tan());
|
||||
TypedMatrix4D::row_major(
|
||||
_1, sx, _0, _0,
|
||||
sy, _1, _0, _0,
|
||||
_0, _0, _1, _0,
|
||||
_0, _0, _0, _1
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a simple perspective projection matrix
|
||||
pub fn create_perspective(d: T) -> TypedMatrix4D<T, Src, Dst> {
|
||||
let (_0, _1): (T, T) = (Zero::zero(), One::one());
|
||||
TypedMatrix4D::row_major(
|
||||
_1, _0, _0, _0,
|
||||
_0, _1, _0, _0,
|
||||
_0, _0, _1, -_1 / d,
|
||||
_0, _0, _0, _1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, Src, Dst> TypedMatrix4D<T, Src, Dst> {
|
||||
/// Returns an array containing this matrix's terms in row-major order (the order
|
||||
/// in which the matrix is actually laid out in memory).
|
||||
pub fn to_row_major_array(&self) -> [T; 16] {
|
||||
[
|
||||
self.m11, self.m12, self.m13, self.m14,
|
||||
self.m21, self.m22, self.m23, self.m24,
|
||||
self.m31, self.m32, self.m33, self.m34,
|
||||
self.m41, self.m42, self.m43, self.m44
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns an array containing this matrix's terms in column-major order.
|
||||
pub fn to_column_major_array(&self) -> [T; 16] {
|
||||
[
|
||||
self.m11, self.m21, self.m31, self.m41,
|
||||
self.m12, self.m22, self.m32, self.m42,
|
||||
self.m13, self.m23, self.m33, self.m43,
|
||||
self.m14, self.m24, self.m34, self.m44
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns an array containing this matrix's 4 rows in (in row-major order)
|
||||
/// as arrays.
|
||||
///
|
||||
/// This is a convenience method to interface with other libraries like glium.
|
||||
pub fn to_row_arrays(&self) -> [[T; 4];4] {
|
||||
[
|
||||
[self.m11, self.m12, self.m13, self.m14],
|
||||
[self.m21, self.m22, self.m23, self.m24],
|
||||
[self.m31, self.m32, self.m33, self.m34],
|
||||
[self.m41, self.m42, self.m43, self.m44]
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns an array containing this matrix's 4 columns in (in row-major order,
|
||||
/// or 4 rows in column-major order) as arrays.
|
||||
///
|
||||
/// This is a convenience method to interface with other libraries like glium.
|
||||
pub fn to_column_arrays(&self) -> [[T; 4]; 4] {
|
||||
[
|
||||
[self.m11, self.m21, self.m31, self.m41],
|
||||
[self.m12, self.m22, self.m32, self.m42],
|
||||
[self.m13, self.m23, self.m33, self.m43],
|
||||
[self.m14, self.m24, self.m34, self.m44]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Src, Dst> fmt::Debug for TypedMatrix4D<T, Src, Dst>
|
||||
where T: Copy + fmt::Debug +
|
||||
PartialEq +
|
||||
One + Zero {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_identity() {
|
||||
write!(f, "[I]")
|
||||
} else {
|
||||
self.to_row_major_array().fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use approxeq::ApproxEq;
|
||||
use matrix2d::Matrix2D;
|
||||
use point::{Point2D, Point3D, Point4D};
|
||||
use Radians;
|
||||
use super::*;
|
||||
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
type Mf32 = Matrix4D<f32>;
|
||||
|
||||
// For convenience.
|
||||
fn rad(v: f32) -> Radians<f32> { Radians::new(v) }
|
||||
|
||||
#[test]
|
||||
pub fn test_translation() {
|
||||
let t1 = Mf32::create_translation(1.0, 2.0, 3.0);
|
||||
let t2 = Mf32::identity().pre_translated(1.0, 2.0, 3.0);
|
||||
let t3 = Mf32::identity().post_translated(1.0, 2.0, 3.0);
|
||||
assert_eq!(t1, t2);
|
||||
assert_eq!(t1, t3);
|
||||
|
||||
assert_eq!(t1.transform_point3d(&Point3D::new(1.0, 1.0, 1.0)), Point3D::new(2.0, 3.0, 4.0));
|
||||
assert_eq!(t1.transform_point(&Point2D::new(1.0, 1.0)), Point2D::new(2.0, 3.0));
|
||||
|
||||
assert_eq!(t1.post_mul(&t1), Mf32::create_translation(2.0, 4.0, 6.0));
|
||||
|
||||
assert!(!t1.is_2d());
|
||||
assert_eq!(Mf32::create_translation(1.0, 2.0, 3.0).to_2d(), Matrix2D::create_translation(1.0, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_rotation() {
|
||||
let r1 = Mf32::create_rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));
|
||||
let r2 = Mf32::identity().pre_rotated(0.0, 0.0, 1.0, rad(FRAC_PI_2));
|
||||
let r3 = Mf32::identity().post_rotated(0.0, 0.0, 1.0, rad(FRAC_PI_2));
|
||||
assert_eq!(r1, r2);
|
||||
assert_eq!(r1, r3);
|
||||
|
||||
assert!(r1.transform_point3d(&Point3D::new(1.0, 2.0, 3.0)).approx_eq(&Point3D::new(2.0, -1.0, 3.0)));
|
||||
assert!(r1.transform_point(&Point2D::new(1.0, 2.0)).approx_eq(&Point2D::new(2.0, -1.0)));
|
||||
|
||||
assert!(r1.post_mul(&r1).approx_eq(&Mf32::create_rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2*2.0))));
|
||||
|
||||
assert!(r1.is_2d());
|
||||
assert!(r1.to_2d().approx_eq(&Matrix2D::create_rotation(rad(FRAC_PI_2))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_scale() {
|
||||
let s1 = Mf32::create_scale(2.0, 3.0, 4.0);
|
||||
let s2 = Mf32::identity().pre_scaled(2.0, 3.0, 4.0);
|
||||
let s3 = Mf32::identity().post_scaled(2.0, 3.0, 4.0);
|
||||
assert_eq!(s1, s2);
|
||||
assert_eq!(s1, s3);
|
||||
|
||||
assert!(s1.transform_point3d(&Point3D::new(2.0, 2.0, 2.0)).approx_eq(&Point3D::new(4.0, 6.0, 8.0)));
|
||||
assert!(s1.transform_point(&Point2D::new(2.0, 2.0)).approx_eq(&Point2D::new(4.0, 6.0)));
|
||||
|
||||
assert_eq!(s1.post_mul(&s1), Mf32::create_scale(4.0, 9.0, 16.0));
|
||||
|
||||
assert!(!s1.is_2d());
|
||||
assert_eq!(Mf32::create_scale(2.0, 3.0, 0.0).to_2d(), Matrix2D::create_scale(2.0, 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_ortho() {
|
||||
let (left, right, bottom, top) = (0.0f32, 1.0f32, 0.1f32, 1.0f32);
|
||||
let (near, far) = (-1.0f32, 1.0f32);
|
||||
let result = Mf32::ortho(left, right, bottom, top, near, far);
|
||||
let expected = Mf32::row_major(
|
||||
2.0, 0.0, 0.0, 0.0,
|
||||
0.0, 2.22222222, 0.0, 0.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
-1.0, -1.22222222, -0.0, 1.0
|
||||
);
|
||||
debug!("result={:?} expected={:?}", result, expected);
|
||||
assert!(result.approx_eq(&expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_2d() {
|
||||
assert!(Mf32::identity().is_2d());
|
||||
assert!(Mf32::create_rotation(0.0, 0.0, 1.0, rad(0.7854)).is_2d());
|
||||
assert!(!Mf32::create_rotation(0.0, 1.0, 0.0, rad(0.7854)).is_2d());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_row_major_2d() {
|
||||
let m1 = Mf32::row_major_2d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
|
||||
let m2 = Mf32::row_major(
|
||||
1.0, 2.0, 0.0, 0.0,
|
||||
3.0, 4.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
5.0, 6.0, 0.0, 1.0
|
||||
);
|
||||
assert_eq!(m1, m2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_major() {
|
||||
assert_eq!(
|
||||
Mf32::row_major(
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
5.0, 6.0, 7.0, 8.0,
|
||||
9.0, 10.0, 11.0, 12.0,
|
||||
13.0, 14.0, 15.0, 16.0,
|
||||
),
|
||||
Mf32::column_major(
|
||||
1.0, 5.0, 9.0, 13.0,
|
||||
2.0, 6.0, 10.0, 14.0,
|
||||
3.0, 7.0, 11.0, 15.0,
|
||||
4.0, 8.0, 12.0, 16.0,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_simple() {
|
||||
let m1 = Mf32::identity();
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.approx_eq(&m2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_scale() {
|
||||
let m1 = Mf32::create_scale(1.5, 0.3, 2.1);
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_translate() {
|
||||
let m1 = Mf32::create_translation(-132.0, 0.3, 493.0);
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_rotate() {
|
||||
let m1 = Mf32::create_rotation(0.0, 1.0, 0.0, rad(1.57));
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_inverse_transform_point_2d() {
|
||||
let m1 = Mf32::create_translation(100.0, 200.0, 0.0);
|
||||
let m2 = m1.inverse().unwrap();
|
||||
assert!(m1.pre_mul(&m2).approx_eq(&Mf32::identity()));
|
||||
|
||||
let p1 = Point2D::new(1000.0, 2000.0);
|
||||
let p2 = m1.transform_point(&p1);
|
||||
assert!(p2.eq(&Point2D::new(1100.0, 2200.0)));
|
||||
|
||||
let p3 = m2.transform_point(&p2);
|
||||
assert!(p3.eq(&p1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse_none() {
|
||||
assert!(Mf32::create_scale(2.0, 0.0, 2.0).inverse().is_none());
|
||||
assert!(Mf32::create_scale(2.0, 2.0, 2.0).inverse().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_pre_post() {
|
||||
let m1 = Matrix4D::identity().post_scaled(1.0, 2.0, 3.0).post_translated(1.0, 2.0, 3.0);
|
||||
let m2 = Matrix4D::identity().pre_translated(1.0, 2.0, 3.0).pre_scaled(1.0, 2.0, 3.0);
|
||||
assert!(m1.approx_eq(&m2));
|
||||
|
||||
let r = Mf32::create_rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));
|
||||
let t = Mf32::create_translation(2.0, 3.0, 0.0);
|
||||
|
||||
let a = Point3D::new(1.0, 1.0, 1.0);
|
||||
|
||||
assert!(r.post_mul(&t).transform_point3d(&a).approx_eq(&Point3D::new(3.0, 2.0, 1.0)));
|
||||
assert!(t.post_mul(&r).transform_point3d(&a).approx_eq(&Point3D::new(4.0, -3.0, 1.0)));
|
||||
assert!(t.post_mul(&r).transform_point3d(&a).approx_eq(&r.transform_point3d(&t.transform_point3d(&a))));
|
||||
|
||||
assert!(r.pre_mul(&t).transform_point3d(&a).approx_eq(&Point3D::new(4.0, -3.0, 1.0)));
|
||||
assert!(t.pre_mul(&r).transform_point3d(&a).approx_eq(&Point3D::new(3.0, 2.0, 1.0)));
|
||||
assert!(t.pre_mul(&r).transform_point3d(&a).approx_eq(&t.transform_point3d(&r.transform_point3d(&a))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size_of() {
|
||||
use std::mem::size_of;
|
||||
assert_eq!(size_of::<Matrix4D<f32>>(), 16*size_of::<f32>());
|
||||
assert_eq!(size_of::<Matrix4D<f64>>(), 16*size_of::<f64>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_transform_associativity() {
|
||||
let m1 = Mf32::row_major(3.0, 2.0, 1.5, 1.0,
|
||||
0.0, 4.5, -1.0, -4.0,
|
||||
0.0, 3.5, 2.5, 40.0,
|
||||
0.0, 3.0, 0.0, 1.0);
|
||||
let m2 = Mf32::row_major(1.0, -1.0, 3.0, 0.0,
|
||||
-1.0, 0.5, 0.0, 2.0,
|
||||
1.5, -2.0, 6.0, 0.0,
|
||||
-2.5, 6.0, 1.0, 1.0);
|
||||
|
||||
let p = Point4D::new(1.0, 3.0, 5.0, 1.0);
|
||||
let p1 = m2.pre_mul(&m1).transform_point4d(&p);
|
||||
let p2 = m2.transform_point4d(&m1.transform_point4d(&p));
|
||||
assert!(p1.approx_eq(&p2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_identity() {
|
||||
let m1 = Matrix4D::identity();
|
||||
assert!(m1.is_identity());
|
||||
let m2 = m1.post_translated(0.1, 0.0, 0.0);
|
||||
assert!(!m2.is_identity());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
//! A one-dimensional length, tagged with its units.
|
||||
|
||||
use num_traits;
|
||||
|
||||
|
||||
pub trait Zero {
|
||||
fn zero() -> Self;
|
||||
}
|
||||
|
||||
impl<T: num_traits::Zero> Zero for T {
|
||||
fn zero() -> T { num_traits::Zero::zero() }
|
||||
}
|
||||
|
||||
pub trait One {
|
||||
fn one() -> Self;
|
||||
}
|
||||
|
||||
impl<T: num_traits::One> One for T {
|
||||
fn one() -> T { num_traits::One::one() }
|
||||
}
|
||||
|
||||
pub trait Round : Copy { fn round(self) -> Self; }
|
||||
pub trait Floor : Copy { fn floor(self) -> Self; }
|
||||
pub trait Ceil : Copy { fn ceil(self) -> Self; }
|
||||
|
||||
impl Round for f32 { fn round(self) -> Self { self.round() } }
|
||||
impl Round for f64 { fn round(self) -> Self { self.round() } }
|
||||
impl Round for i16 { fn round(self) -> Self { self } }
|
||||
impl Round for u16 { fn round(self) -> Self { self } }
|
||||
impl Round for i32 { fn round(self) -> Self { self } }
|
||||
impl Round for i64 { fn round(self) -> Self { self } }
|
||||
impl Round for u32 { fn round(self) -> Self { self } }
|
||||
impl Round for u64 { fn round(self) -> Self { self } }
|
||||
impl Round for usize { fn round(self) -> Self { self } }
|
||||
impl Round for isize { fn round(self) -> Self { self } }
|
||||
|
||||
impl Floor for f32 { fn floor(self) -> Self { self.floor() } }
|
||||
impl Floor for f64 { fn floor(self) -> Self { self.floor() } }
|
||||
impl Floor for i16 { fn floor(self) -> Self { self } }
|
||||
impl Floor for u16 { fn floor(self) -> Self { self } }
|
||||
impl Floor for i32 { fn floor(self) -> Self { self } }
|
||||
impl Floor for i64 { fn floor(self) -> Self { self } }
|
||||
impl Floor for u32 { fn floor(self) -> Self { self } }
|
||||
impl Floor for u64 { fn floor(self) -> Self { self } }
|
||||
impl Floor for usize { fn floor(self) -> Self { self } }
|
||||
impl Floor for isize { fn floor(self) -> Self { self } }
|
||||
|
||||
impl Ceil for f32 { fn ceil(self) -> Self { self.ceil() } }
|
||||
impl Ceil for f64 { fn ceil(self) -> Self { self.ceil() } }
|
||||
impl Ceil for i16 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for u16 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for i32 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for i64 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for u32 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for u64 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for usize { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for isize { fn ceil(self) -> Self { self } }
|
||||
|
|
@ -0,0 +1,995 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::UnknownUnit;
|
||||
use approxeq::ApproxEq;
|
||||
use length::Length;
|
||||
use scale_factor::ScaleFactor;
|
||||
use size::TypedSize2D;
|
||||
use num::*;
|
||||
use num_traits::{Float, NumCast};
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Neg, Mul, Sub, Div};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
define_matrix! {
|
||||
/// A 2d Point tagged with a unit.
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct TypedPoint2D<T, U> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default 2d point type with no unit.
|
||||
///
|
||||
/// `Point2D` provides the same methods as `TypedPoint2D`.
|
||||
pub type Point2D<T> = TypedPoint2D<T, UnknownUnit>;
|
||||
|
||||
impl<T: Copy + Zero, U> TypedPoint2D<T, U> {
|
||||
/// Constructor, setting all components to zero.
|
||||
#[inline]
|
||||
pub fn zero() -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(Zero::zero(), Zero::zero())
|
||||
}
|
||||
|
||||
/// Convert into a 3d point.
|
||||
#[inline]
|
||||
pub fn to_3d(&self) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.x, self.y, Zero::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, U> fmt::Debug for TypedPoint2D<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({:?},{:?})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display, U> fmt::Display for TypedPoint2D<T, U> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "({},{})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, U> TypedPoint2D<T, U> {
|
||||
/// Constructor taking scalar values directly.
|
||||
#[inline]
|
||||
pub fn new(x: T, y: T) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D { x: x, y: y, _unit: PhantomData }
|
||||
}
|
||||
|
||||
/// Constructor taking properly typed Lengths instead of scalar values.
|
||||
#[inline]
|
||||
pub fn from_lengths(x: Length<T, U>, y: Length<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(x.0, y.0)
|
||||
}
|
||||
|
||||
/// Returns self.x as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn x_typed(&self) -> Length<T, U> { Length::new(self.x) }
|
||||
|
||||
/// Returns self.y as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn y_typed(&self) -> Length<T, U> { Length::new(self.y) }
|
||||
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
#[inline]
|
||||
pub fn to_untyped(&self) -> Point2D<T> {
|
||||
TypedPoint2D::new(self.x, self.y)
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
#[inline]
|
||||
pub fn from_untyped(p: &Point2D<T>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(p.x, p.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_array(&self) -> [T; 2] {
|
||||
[self.x, self.y]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedPoint2D<T, U>
|
||||
where T: Copy + Mul<T, Output=T> + Add<T, Output=T> + Sub<T, Output=T> {
|
||||
/// Dot product.
|
||||
#[inline]
|
||||
pub fn dot(self, other: TypedPoint2D<T, U>) -> T {
|
||||
self.x * other.x + self.y * other.y
|
||||
}
|
||||
|
||||
/// Returns the norm of the cross product [self.x, self.y, 0] x [other.x, other.y, 0]..
|
||||
#[inline]
|
||||
pub fn cross(self, other: TypedPoint2D<T, U>) -> T {
|
||||
self.x * other.y - self.y * other.x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn normalize(self) -> Self where T: Float + ApproxEq<T> {
|
||||
let dot = self.dot(self);
|
||||
if dot.approx_eq(&T::zero()) {
|
||||
self
|
||||
} else {
|
||||
self / dot.sqrt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Add<T, Output=T>, U> Add for TypedPoint2D<T, U> {
|
||||
type Output = TypedPoint2D<T, U>;
|
||||
fn add(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x + other.x, self.y + other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Add<T, Output=T>, U> Add<TypedSize2D<T, U>> for TypedPoint2D<T, U> {
|
||||
type Output = TypedPoint2D<T, U>;
|
||||
fn add(self, other: TypedSize2D<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x + other.width, self.y + other.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Add<T, Output=T>, U> TypedPoint2D<T, U> {
|
||||
pub fn add_size(&self, other: &TypedSize2D<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x + other.width, self.y + other.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Sub<T, Output=T>, U> Sub for TypedPoint2D<T, U> {
|
||||
type Output = TypedPoint2D<T, U>;
|
||||
fn sub(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x - other.x, self.y - other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl <T: Copy + Neg<Output=T>, U> Neg for TypedPoint2D<T, U> {
|
||||
type Output = TypedPoint2D<T, U>;
|
||||
#[inline]
|
||||
fn neg(self) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(-self.x, -self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, U> TypedPoint2D<T, U> {
|
||||
pub fn min(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x.min(other.x), self.y.min(other.y))
|
||||
}
|
||||
|
||||
pub fn max(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x.max(other.x), self.y.max(other.y))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedPoint2D<T, U> {
|
||||
type Output = TypedPoint2D<T, U>;
|
||||
#[inline]
|
||||
fn mul(self, scale: T) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x * scale, self.y * scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedPoint2D<T, U> {
|
||||
type Output = TypedPoint2D<T, U>;
|
||||
#[inline]
|
||||
fn div(self, scale: T) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x / scale, self.y / scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U1, U2> Mul<ScaleFactor<T, U1, U2>> for TypedPoint2D<T, U1> {
|
||||
type Output = TypedPoint2D<T, U2>;
|
||||
#[inline]
|
||||
fn mul(self, scale: ScaleFactor<T, U1, U2>) -> TypedPoint2D<T, U2> {
|
||||
TypedPoint2D::new(self.x * scale.get(), self.y * scale.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U1, U2> Div<ScaleFactor<T, U1, U2>> for TypedPoint2D<T, U2> {
|
||||
type Output = TypedPoint2D<T, U1>;
|
||||
#[inline]
|
||||
fn div(self, scale: ScaleFactor<T, U1, U2>) -> TypedPoint2D<T, U1> {
|
||||
TypedPoint2D::new(self.x / scale.get(), self.y / scale.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Round, U> TypedPoint2D<T, U> {
|
||||
/// Rounds each component to the nearest integer value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
/// For example `{ -0.1, -0.8 }.round() == { 0.0, -1.0 }`.
|
||||
pub fn round(&self) -> Self {
|
||||
TypedPoint2D::new(self.x.round(), self.y.round())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ceil, U> TypedPoint2D<T, U> {
|
||||
/// Rounds each component to the smallest integer equal or greater than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
/// For example `{ -0.1, -0.8 }.ceil() == { 0.0, 0.0 }`.
|
||||
pub fn ceil(&self) -> Self {
|
||||
TypedPoint2D::new(self.x.ceil(), self.y.ceil())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Floor, U> TypedPoint2D<T, U> {
|
||||
/// Rounds each component to the biggest integer equal or lower than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
/// For example `{ -0.1, -0.8 }.floor() == { -1.0, -1.0 }`.
|
||||
pub fn floor(&self) -> Self {
|
||||
TypedPoint2D::new(self.x.floor(), self.y.floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast + Copy, U> TypedPoint2D<T, U> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
///
|
||||
/// When casting from floating point to integer coordinates, the decimals are truncated
|
||||
/// as one would expect from a simple cast, but this behavior does not always make sense
|
||||
/// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
|
||||
pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedPoint2D<NewT, U>> {
|
||||
match (NumCast::from(self.x), NumCast::from(self.y)) {
|
||||
(Some(x), Some(y)) => Some(TypedPoint2D::new(x, y)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions for common casts
|
||||
|
||||
/// Cast into an `f32` point.
|
||||
pub fn to_f32(&self) -> TypedPoint2D<f32, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `usize` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedPoint2D<usize, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an i32 point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i32(&self) -> TypedPoint2D<i32, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an i64 point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i64(&self) -> TypedPoint2D<i64, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy+ApproxEq<T>, U> ApproxEq<TypedPoint2D<T, U>> for TypedPoint2D<T, U> {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> Self {
|
||||
TypedPoint2D::new(T::approx_epsilon(), T::approx_epsilon())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &Self) -> bool {
|
||||
self.x.approx_eq(&other.x) && self.y.approx_eq(&other.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
|
||||
self.x.approx_eq_eps(&other.x, &eps.x) && self.y.approx_eq_eps(&other.y, &eps.y)
|
||||
}
|
||||
}
|
||||
|
||||
define_matrix! {
|
||||
/// A 3d Point tagged with a unit.
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct TypedPoint3D<T, U> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default 3d point type with no unit.
|
||||
///
|
||||
/// `Point3D` provides the same methods as `TypedPoint3D`.
|
||||
pub type Point3D<T> = TypedPoint3D<T, UnknownUnit>;
|
||||
|
||||
impl<T: Copy + Zero, U> TypedPoint3D<T, U> {
|
||||
/// Constructor, setting all copmonents to zero.
|
||||
#[inline]
|
||||
pub fn zero() -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(Zero::zero(), Zero::zero(), Zero::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, U> fmt::Debug for TypedPoint3D<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({:?},{:?},{:?})", self.x, self.y, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display, U> fmt::Display for TypedPoint3D<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({},{},{})", self.x, self.y, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, U> TypedPoint3D<T, U> {
|
||||
/// Constructor taking scalar values directly.
|
||||
#[inline]
|
||||
pub fn new(x: T, y: T, z: T) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D { x: x, y: y, z: z, _unit: PhantomData }
|
||||
}
|
||||
|
||||
/// Constructor taking properly typed Lengths instead of scalar values.
|
||||
#[inline]
|
||||
pub fn from_lengths(x: Length<T, U>, y: Length<T, U>, z: Length<T, U>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(x.0, y.0, z.0)
|
||||
}
|
||||
|
||||
/// Returns self.x as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn x_typed(&self) -> Length<T, U> { Length::new(self.x) }
|
||||
|
||||
/// Returns self.y as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn y_typed(&self) -> Length<T, U> { Length::new(self.y) }
|
||||
|
||||
/// Returns self.z as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn z_typed(&self) -> Length<T, U> { Length::new(self.z) }
|
||||
|
||||
#[inline]
|
||||
pub fn to_array(&self) -> [T; 3] { [self.x, self.y, self.z] }
|
||||
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
#[inline]
|
||||
pub fn to_untyped(&self) -> Point3D<T> {
|
||||
TypedPoint3D::new(self.x, self.y, self.z)
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
#[inline]
|
||||
pub fn from_untyped(p: &Point3D<T>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(p.x, p.y, p.z)
|
||||
}
|
||||
|
||||
/// Convert into a 2d point.
|
||||
#[inline]
|
||||
pub fn to_2d(&self) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Mul<T, Output=T> +
|
||||
Add<T, Output=T> +
|
||||
Sub<T, Output=T> +
|
||||
Copy, U> TypedPoint3D<T, U> {
|
||||
|
||||
// Dot product.
|
||||
#[inline]
|
||||
pub fn dot(self, other: TypedPoint3D<T, U>) -> T {
|
||||
self.x * other.x +
|
||||
self.y * other.y +
|
||||
self.z * other.z
|
||||
}
|
||||
|
||||
// Cross product.
|
||||
#[inline]
|
||||
pub fn cross(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.y * other.z - self.z * other.y,
|
||||
self.z * other.x - self.x * other.z,
|
||||
self.x * other.y - self.y * other.x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn normalize(self) -> Self where T: Float + ApproxEq<T> {
|
||||
let dot = self.dot(self);
|
||||
if dot.approx_eq(&T::zero()) {
|
||||
self
|
||||
} else {
|
||||
self / dot.sqrt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Add<T, Output=T>, U> Add for TypedPoint3D<T, U> {
|
||||
type Output = TypedPoint3D<T, U>;
|
||||
fn add(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.x + other.x,
|
||||
self.y + other.y,
|
||||
self.z + other.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Sub<T, Output=T>, U> Sub for TypedPoint3D<T, U> {
|
||||
type Output = TypedPoint3D<T, U>;
|
||||
fn sub(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.x - other.x,
|
||||
self.y - other.y,
|
||||
self.z - other.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl <T: Copy + Neg<Output=T>, U> Neg for TypedPoint3D<T, U> {
|
||||
type Output = TypedPoint3D<T, U>;
|
||||
#[inline]
|
||||
fn neg(self) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(-self.x, -self.y, -self.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedPoint3D<T, U> {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn mul(self, scale: T) -> Self {
|
||||
Self::new(self.x * scale, self.y * scale, self.z * scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedPoint3D<T, U> {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn div(self, scale: T) -> Self {
|
||||
Self::new(self.x / scale, self.y / scale, self.z / scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, U> TypedPoint3D<T, U> {
|
||||
pub fn min(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.x.min(other.x),
|
||||
self.y.min(other.y),
|
||||
self.z.min(other.z))
|
||||
}
|
||||
|
||||
pub fn max(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.x.max(other.x), self.y.max(other.y),
|
||||
self.z.max(other.z))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Round, U> TypedPoint3D<T, U> {
|
||||
/// Rounds each component to the nearest integer value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn round(&self) -> Self {
|
||||
TypedPoint3D::new(self.x.round(), self.y.round(), self.z.round())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ceil, U> TypedPoint3D<T, U> {
|
||||
/// Rounds each component to the smallest integer equal or greater than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn ceil(&self) -> Self {
|
||||
TypedPoint3D::new(self.x.ceil(), self.y.ceil(), self.z.ceil())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Floor, U> TypedPoint3D<T, U> {
|
||||
/// Rounds each component to the biggest integer equal or lower than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn floor(&self) -> Self {
|
||||
TypedPoint3D::new(self.x.floor(), self.y.floor(), self.z.floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast + Copy, U> TypedPoint3D<T, U> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
///
|
||||
/// When casting from floating point to integer coordinates, the decimals are truncated
|
||||
/// as one would expect from a simple cast, but this behavior does not always make sense
|
||||
/// geometrically. Consider using round(), ceil or floor() before casting.
|
||||
pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedPoint3D<NewT, U>> {
|
||||
match (NumCast::from(self.x),
|
||||
NumCast::from(self.y),
|
||||
NumCast::from(self.z)) {
|
||||
(Some(x), Some(y), Some(z)) => Some(TypedPoint3D::new(x, y, z)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions for common casts
|
||||
|
||||
/// Cast into an `f32` point.
|
||||
pub fn to_f32(&self) -> TypedPoint3D<f32, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `usize` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedPoint3D<usize, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i32` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i32(&self) -> TypedPoint3D<i32, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i64` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i64(&self) -> TypedPoint3D<i64, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy+ApproxEq<T>, U> ApproxEq<TypedPoint3D<T, U>> for TypedPoint3D<T, U> {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> Self {
|
||||
TypedPoint3D::new(T::approx_epsilon(), T::approx_epsilon(), T::approx_epsilon())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &Self) -> bool {
|
||||
self.x.approx_eq(&other.x)
|
||||
&& self.y.approx_eq(&other.y)
|
||||
&& self.z.approx_eq(&other.z)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
|
||||
self.x.approx_eq_eps(&other.x, &eps.x)
|
||||
&& self.y.approx_eq_eps(&other.y, &eps.y)
|
||||
&& self.z.approx_eq_eps(&other.z, &eps.z)
|
||||
}
|
||||
}
|
||||
|
||||
define_matrix! {
|
||||
/// A 4d Point tagged with a unit.
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct TypedPoint4D<T, U> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
pub w: T,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default 4d point with no unit.
|
||||
///
|
||||
/// `Point4D` provides the same methods as `TypedPoint4D`.
|
||||
pub type Point4D<T> = TypedPoint4D<T, UnknownUnit>;
|
||||
|
||||
impl<T: Copy + Zero, U> TypedPoint4D<T, U> {
|
||||
/// Constructor, setting all copmonents to zero.
|
||||
#[inline]
|
||||
pub fn zero() -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, U> fmt::Debug for TypedPoint4D<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({:?},{:?},{:?},{:?})", self.x, self.y, self.z, self.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display, U> fmt::Display for TypedPoint4D<T, U> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "({},{},{},{})", self.x, self.y, self.z, self.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, U> TypedPoint4D<T, U> {
|
||||
/// Constructor taking scalar values directly.
|
||||
#[inline]
|
||||
pub fn new(x: T, y: T, z: T, w: T) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D { x: x, y: y, z: z, w: w, _unit: PhantomData }
|
||||
}
|
||||
|
||||
/// Constructor taking properly typed Lengths instead of scalar values.
|
||||
#[inline]
|
||||
pub fn from_lengths(x: Length<T, U>,
|
||||
y: Length<T, U>,
|
||||
z: Length<T, U>,
|
||||
w: Length<T, U>) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(x.0, y.0, z.0, w.0)
|
||||
}
|
||||
|
||||
/// Returns self.x as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn x_typed(&self) -> Length<T, U> { Length::new(self.x) }
|
||||
|
||||
/// Returns self.y as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn y_typed(&self) -> Length<T, U> { Length::new(self.y) }
|
||||
|
||||
/// Returns self.z as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn z_typed(&self) -> Length<T, U> { Length::new(self.z) }
|
||||
|
||||
/// Returns self.w as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn w_typed(&self) -> Length<T, U> { Length::new(self.w) }
|
||||
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
#[inline]
|
||||
pub fn to_untyped(&self) -> Point4D<T> {
|
||||
TypedPoint4D::new(self.x, self.y, self.z, self.w)
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
#[inline]
|
||||
pub fn from_untyped(p: &Point4D<T>) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(p.x, p.y, p.z, p.w)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_array(&self) -> [T; 4] {
|
||||
[self.x, self.y, self.z, self.w]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U> TypedPoint4D<T, U> {
|
||||
/// Convert into a 2d point.
|
||||
#[inline]
|
||||
pub fn to_2d(self) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.x / self.w, self.y / self.w)
|
||||
}
|
||||
|
||||
/// Convert into a 3d point.
|
||||
#[inline]
|
||||
pub fn to_3d(self) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(self.x / self.w, self.y / self.w, self.z / self.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Add<T, Output=T>, U> Add for TypedPoint4D<T, U> {
|
||||
type Output = TypedPoint4D<T, U>;
|
||||
fn add(self, other: TypedPoint4D<T, U>) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(self.x + other.x,
|
||||
self.y + other.y,
|
||||
self.z + other.z,
|
||||
self.w + other.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Sub<T, Output=T>, U> Sub for TypedPoint4D<T, U> {
|
||||
type Output = TypedPoint4D<T, U>;
|
||||
fn sub(self, other: TypedPoint4D<T, U>) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(self.x - other.x,
|
||||
self.y - other.y,
|
||||
self.z - other.z,
|
||||
self.w - other.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl <T: Copy + Neg<Output=T>, U> Neg for TypedPoint4D<T, U> {
|
||||
type Output = TypedPoint4D<T, U>;
|
||||
#[inline]
|
||||
fn neg(self) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(-self.x, -self.y, -self.z, -self.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float, U> TypedPoint4D<T, U> {
|
||||
pub fn min(self, other: TypedPoint4D<T, U>) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(self.x.min(other.x), self.y.min(other.y),
|
||||
self.z.min(other.z), self.w.min(other.w))
|
||||
}
|
||||
|
||||
pub fn max(self, other: TypedPoint4D<T, U>) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(self.x.max(other.x), self.y.max(other.y),
|
||||
self.z.max(other.z), self.w.max(other.w))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Round, U> TypedPoint4D<T, U> {
|
||||
/// Rounds each component to the nearest integer value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn round(&self) -> Self {
|
||||
TypedPoint4D::new(self.x.round(), self.y.round(), self.z.round(), self.w.round())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ceil, U> TypedPoint4D<T, U> {
|
||||
/// Rounds each component to the smallest integer equal or greater than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn ceil(&self) -> Self {
|
||||
TypedPoint4D::new(self.x.ceil(), self.y.ceil(), self.z.ceil(), self.w.ceil())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Floor, U> TypedPoint4D<T, U> {
|
||||
/// Rounds each component to the biggest integer equal or lower than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn floor(&self) -> Self {
|
||||
TypedPoint4D::new(self.x.floor(), self.y.floor(), self.z.floor(), self.w.floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast + Copy, U> TypedPoint4D<T, U> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
///
|
||||
/// When casting from floating point to integer coordinates, the decimals are truncated
|
||||
/// as one would expect from a simple cast, but this behavior does not always make sense
|
||||
/// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
|
||||
pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedPoint4D<NewT, U>> {
|
||||
match (NumCast::from(self.x),
|
||||
NumCast::from(self.y),
|
||||
NumCast::from(self.z),
|
||||
NumCast::from(self.w)) {
|
||||
(Some(x), Some(y), Some(z), Some(w)) => Some(TypedPoint4D::new(x, y, z, w)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions for common casts
|
||||
|
||||
/// Cast into an `f32` point.
|
||||
pub fn to_f32(&self) -> TypedPoint4D<f32, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `usize` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedPoint4D<usize, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i32` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i32(&self) -> TypedPoint4D<i32, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i64` point, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i64(&self) -> TypedPoint4D<i64, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ApproxEq<T>, U> ApproxEq<T> for TypedPoint4D<T, U> {
|
||||
fn approx_epsilon() -> T {
|
||||
T::approx_epsilon()
|
||||
}
|
||||
|
||||
fn approx_eq_eps(&self, other: &Self, approx_epsilon: &T) -> bool {
|
||||
self.x.approx_eq_eps(&other.x, approx_epsilon)
|
||||
&& self.y.approx_eq_eps(&other.y, approx_epsilon)
|
||||
&& self.z.approx_eq_eps(&other.z, approx_epsilon)
|
||||
&& self.w.approx_eq_eps(&other.w, approx_epsilon)
|
||||
}
|
||||
|
||||
fn approx_eq(&self, other: &Self) -> bool {
|
||||
self.approx_eq_eps(&other, &Self::approx_epsilon())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn point2<T: Copy, U>(x: T, y: T) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(x, y)
|
||||
}
|
||||
|
||||
pub fn point3<T: Copy, U>(x: T, y: T, z: T) -> TypedPoint3D<T, U> {
|
||||
TypedPoint3D::new(x, y, z)
|
||||
}
|
||||
|
||||
pub fn point4<T: Copy, U>(x: T, y: T, z: T, w: T) -> TypedPoint4D<T, U> {
|
||||
TypedPoint4D::new(x, y, z, w)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod point2d {
|
||||
use super::Point2D;
|
||||
|
||||
#[test]
|
||||
pub fn test_scalar_mul() {
|
||||
let p1: Point2D<f32> = Point2D::new(3.0, 5.0);
|
||||
|
||||
let result = p1 * 5.0;
|
||||
|
||||
assert_eq!(result, Point2D::new(15.0, 25.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_dot() {
|
||||
let p1: Point2D<f32> = Point2D::new(2.0, 7.0);
|
||||
let p2: Point2D<f32> = Point2D::new(13.0, 11.0);
|
||||
assert_eq!(p1.dot(p2), 103.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_cross() {
|
||||
let p1: Point2D<f32> = Point2D::new(4.0, 7.0);
|
||||
let p2: Point2D<f32> = Point2D::new(13.0, 8.0);
|
||||
let r = p1.cross(p2);
|
||||
assert_eq!(r, -59.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_normalize() {
|
||||
let p0: Point2D<f32> = Point2D::zero();
|
||||
let p1: Point2D<f32> = Point2D::new(4.0, 0.0);
|
||||
let p2: Point2D<f32> = Point2D::new(3.0, -4.0);
|
||||
assert_eq!(p0.normalize(), p0);
|
||||
assert_eq!(p1.normalize(), Point2D::new(1.0, 0.0));
|
||||
assert_eq!(p2.normalize(), Point2D::new(0.6, -0.8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_min() {
|
||||
let p1 = Point2D::new(1.0, 3.0);
|
||||
let p2 = Point2D::new(2.0, 2.0);
|
||||
|
||||
let result = p1.min(p2);
|
||||
|
||||
assert_eq!(result, Point2D::new(1.0, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_max() {
|
||||
let p1 = Point2D::new(1.0, 3.0);
|
||||
let p2 = Point2D::new(2.0, 2.0);
|
||||
|
||||
let result = p1.max(p2);
|
||||
|
||||
assert_eq!(result, Point2D::new(2.0, 3.0));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod typedpoint2d {
|
||||
use super::TypedPoint2D;
|
||||
use scale_factor::ScaleFactor;
|
||||
|
||||
pub enum Mm {}
|
||||
pub enum Cm {}
|
||||
|
||||
pub type Point2DMm<T> = TypedPoint2D<T, Mm>;
|
||||
pub type Point2DCm<T> = TypedPoint2D<T, Cm>;
|
||||
|
||||
#[test]
|
||||
pub fn test_add() {
|
||||
let p1 = Point2DMm::new(1.0, 2.0);
|
||||
let p2 = Point2DMm::new(3.0, 4.0);
|
||||
|
||||
let result = p1 + p2;
|
||||
|
||||
assert_eq!(result, Point2DMm::new(4.0, 6.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_scalar_mul() {
|
||||
let p1 = Point2DMm::new(1.0, 2.0);
|
||||
let cm_per_mm: ScaleFactor<f32, Mm, Cm> = ScaleFactor::new(0.1);
|
||||
|
||||
let result = p1 * cm_per_mm;
|
||||
|
||||
assert_eq!(result, Point2DCm::new(0.1, 0.2));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod point3d {
|
||||
use super::Point3D;
|
||||
|
||||
#[test]
|
||||
pub fn test_dot() {
|
||||
let p1 = Point3D::new(7.0, 21.0, 32.0);
|
||||
let p2 = Point3D::new(43.0, 5.0, 16.0);
|
||||
assert_eq!(p1.dot(p2), 918.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_cross() {
|
||||
let p1 = Point3D::new(4.0, 7.0, 9.0);
|
||||
let p2 = Point3D::new(13.0, 8.0, 3.0);
|
||||
let p3 = p1.cross(p2);
|
||||
assert_eq!(p3, Point3D::new(-51.0, 105.0, -59.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_normalize() {
|
||||
let p0: Point3D<f32> = Point3D::zero();
|
||||
let p1: Point3D<f32> = Point3D::new(0.0, -6.0, 0.0);
|
||||
let p2: Point3D<f32> = Point3D::new(1.0, 2.0, -2.0);
|
||||
assert_eq!(p0.normalize(), p0);
|
||||
assert_eq!(p1.normalize(), Point3D::new(0.0, -1.0, 0.0));
|
||||
assert_eq!(p2.normalize(), Point3D::new(1.0/3.0, 2.0/3.0, -2.0/3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_min() {
|
||||
let p1 = Point3D::new(1.0, 3.0, 5.0);
|
||||
let p2 = Point3D::new(2.0, 2.0, -1.0);
|
||||
|
||||
let result = p1.min(p2);
|
||||
|
||||
assert_eq!(result, Point3D::new(1.0, 2.0, -1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_max() {
|
||||
let p1 = Point3D::new(1.0, 3.0, 5.0);
|
||||
let p2 = Point3D::new(2.0, 2.0, -1.0);
|
||||
|
||||
let result = p1.max(p2);
|
||||
|
||||
assert_eq!(result, Point3D::new(2.0, 3.0, 5.0));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod point4d {
|
||||
use super::Point4D;
|
||||
|
||||
#[test]
|
||||
pub fn test_add() {
|
||||
let p1 = Point4D::new(7.0, 21.0, 32.0, 1.0);
|
||||
let p2 = Point4D::new(43.0, 5.0, 16.0, 2.0);
|
||||
|
||||
let result = p1 + p2;
|
||||
|
||||
assert_eq!(result, Point4D::new(50.0, 26.0, 48.0, 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_sub() {
|
||||
let p1 = Point4D::new(7.0, 21.0, 32.0, 1.0);
|
||||
let p2 = Point4D::new(43.0, 5.0, 16.0, 2.0);
|
||||
|
||||
let result = p1 - p2;
|
||||
|
||||
assert_eq!(result, Point4D::new(-36.0, 16.0, 16.0, -1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_min() {
|
||||
let p1 = Point4D::new(1.0, 3.0, 5.0, 7.0);
|
||||
let p2 = Point4D::new(2.0, 2.0, -1.0, 10.0);
|
||||
|
||||
let result = p1.min(p2);
|
||||
|
||||
assert_eq!(result, Point4D::new(1.0, 2.0, -1.0, 7.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_max() {
|
||||
let p1 = Point4D::new(1.0, 3.0, 5.0, 7.0);
|
||||
let p2 = Point4D::new(2.0, 2.0, -1.0, 10.0);
|
||||
|
||||
let result = p1.max(p2);
|
||||
|
||||
assert_eq!(result, Point4D::new(2.0, 3.0, 5.0, 10.0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,671 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::UnknownUnit;
|
||||
use length::Length;
|
||||
use scale_factor::ScaleFactor;
|
||||
use num::*;
|
||||
use point::TypedPoint2D;
|
||||
use size::TypedSize2D;
|
||||
|
||||
use heapsize::HeapSizeOf;
|
||||
use num_traits::NumCast;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::cmp::PartialOrd;
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Sub, Mul, Div};
|
||||
|
||||
/// A 2d Rectangle optionally tagged with a unit.
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct TypedRect<T, U = UnknownUnit> {
|
||||
pub origin: TypedPoint2D<T, U>,
|
||||
pub size: TypedSize2D<T, U>,
|
||||
}
|
||||
|
||||
/// The default rectangle type with no unit.
|
||||
pub type Rect<T> = TypedRect<T, UnknownUnit>;
|
||||
|
||||
impl<T: HeapSizeOf, U> HeapSizeOf for TypedRect<T, U> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.origin.heap_size_of_children() + self.size.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Deserialize, U> Deserialize for TypedRect<T, U> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer
|
||||
{
|
||||
let (origin, size) = try!(Deserialize::deserialize(deserializer));
|
||||
Ok(TypedRect::new(origin, size))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize, U> Serialize for TypedRect<T, U> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
(&self.origin, &self.size).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, U> Copy for TypedRect<T, U> {}
|
||||
|
||||
impl<T: Copy, U> Clone for TypedRect<T, U> {
|
||||
fn clone(&self) -> TypedRect<T, U> { *self }
|
||||
}
|
||||
|
||||
impl<T: PartialEq, U> PartialEq<TypedRect<T, U>> for TypedRect<T, U> {
|
||||
fn eq(&self, other: &TypedRect<T, U>) -> bool {
|
||||
self.origin.eq(&other.origin) && self.size.eq(&other.size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq, U> Eq for TypedRect<T, U> {}
|
||||
|
||||
impl<T: fmt::Debug, U> fmt::Debug for TypedRect<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TypedRect({:?} at {:?})", self.size, self.origin)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display, U> fmt::Display for TypedRect<T, U> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "Rect({} at {})", self.size, self.origin)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedRect<T, U> {
|
||||
/// Constructor.
|
||||
pub fn new(origin: TypedPoint2D<T, U>, size: TypedSize2D<T, U>) -> TypedRect<T, U> {
|
||||
TypedRect {
|
||||
origin: origin,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedRect<T, U>
|
||||
where T: Copy + Clone + Zero + PartialOrd + PartialEq + Add<T, Output=T> + Sub<T, Output=T> {
|
||||
#[inline]
|
||||
pub fn intersects(&self, other: &TypedRect<T, U>) -> bool {
|
||||
self.origin.x < other.origin.x + other.size.width &&
|
||||
other.origin.x < self.origin.x + self.size.width &&
|
||||
self.origin.y < other.origin.y + other.size.height &&
|
||||
other.origin.y < self.origin.y + self.size.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_x(&self) -> T {
|
||||
self.origin.x + self.size.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x(&self) -> T {
|
||||
self.origin.x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_y(&self) -> T {
|
||||
self.origin.y + self.size.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_y(&self) -> T {
|
||||
self.origin.y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_x_typed(&self) -> Length<T, U> {
|
||||
Length::new(self.max_x())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x_typed(&self) -> Length<T, U> {
|
||||
Length::new(self.min_x())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_y_typed(&self) -> Length<T, U> {
|
||||
Length::new(self.max_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_y_typed(&self) -> Length<T, U> {
|
||||
Length::new(self.min_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn intersection(&self, other: &TypedRect<T, U>) -> Option<TypedRect<T, U>> {
|
||||
if !self.intersects(other) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let upper_left = TypedPoint2D::new(max(self.min_x(), other.min_x()),
|
||||
max(self.min_y(), other.min_y()));
|
||||
let lower_right_x = min(self.max_x(), other.max_x());
|
||||
let lower_right_y = min(self.max_y(), other.max_y());
|
||||
|
||||
Some(TypedRect::new(upper_left, TypedSize2D::new(lower_right_x - upper_left.x,
|
||||
lower_right_y - upper_left.y)))
|
||||
}
|
||||
|
||||
/// Translates the rect by a vector.
|
||||
#[inline]
|
||||
pub fn translate(&self, other: &TypedPoint2D<T, U>) -> TypedRect<T, U> {
|
||||
TypedRect::new(
|
||||
TypedPoint2D::new(self.origin.x + other.x, self.origin.y + other.y),
|
||||
self.size
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if this rectangle contains the point. Points are considered
|
||||
/// in the rectangle if they are on the left or top edge, but outside if they
|
||||
/// are on the right or bottom edge.
|
||||
#[inline]
|
||||
pub fn contains(&self, other: &TypedPoint2D<T, U>) -> bool {
|
||||
self.origin.x <= other.x && other.x < self.origin.x + self.size.width &&
|
||||
self.origin.y <= other.y && other.y < self.origin.y + self.size.height
|
||||
}
|
||||
|
||||
/// Returns true if this rectangle contains the interior of rect. Always
|
||||
/// returns true if rect is empty, and always returns false if rect is
|
||||
/// nonempty but this rectangle is empty.
|
||||
#[inline]
|
||||
pub fn contains_rect(&self, rect: &TypedRect<T, U>) -> bool {
|
||||
rect.is_empty() ||
|
||||
(self.min_x() <= rect.min_x() && rect.max_x() <= self.max_x() &&
|
||||
self.min_y() <= rect.min_y() && rect.max_y() <= self.max_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inflate(&self, width: T, height: T) -> TypedRect<T, U> {
|
||||
TypedRect::new(
|
||||
TypedPoint2D::new(self.origin.x - width, self.origin.y - height),
|
||||
TypedSize2D::new(self.size.width + width + width, self.size.height + height + height),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inflate_typed(&self, width: Length<T, U>, height: Length<T, U>) -> TypedRect<T, U> {
|
||||
self.inflate(width.get(), height.get())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn top_right(&self) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.max_x(), self.origin.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bottom_left(&self) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.origin.x, self.max_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bottom_right(&self) -> TypedPoint2D<T, U> {
|
||||
TypedPoint2D::new(self.max_x(), self.max_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn translate_by_size(&self, size: &TypedSize2D<T, U>) -> TypedRect<T, U> {
|
||||
self.translate(&TypedPoint2D::new(size.width, size.height))
|
||||
}
|
||||
|
||||
/// Returns the smallest rectangle containing the four points.
|
||||
pub fn from_points(points: &[TypedPoint2D<T, U>]) -> Self {
|
||||
if points.len() == 0 {
|
||||
return TypedRect::zero();
|
||||
}
|
||||
let (mut min_x, mut min_y) = (points[0].x, points[0].y);
|
||||
let (mut max_x, mut max_y) = (min_x, min_y);
|
||||
for point in &points[1..] {
|
||||
if point.x < min_x {
|
||||
min_x = point.x
|
||||
}
|
||||
if point.x > max_x {
|
||||
max_x = point.x
|
||||
}
|
||||
if point.y < min_y {
|
||||
min_y = point.y
|
||||
}
|
||||
if point.y > max_y {
|
||||
max_y = point.y
|
||||
}
|
||||
}
|
||||
TypedRect::new(TypedPoint2D::new(min_x, min_y),
|
||||
TypedSize2D::new(max_x - min_x, max_y - min_y))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedRect<T, U>
|
||||
where T: Copy + Clone + PartialOrd + Add<T, Output=T> + Sub<T, Output=T> + Zero {
|
||||
#[inline]
|
||||
pub fn union(&self, other: &TypedRect<T, U>) -> TypedRect<T, U> {
|
||||
if self.size == Zero::zero() {
|
||||
return *other;
|
||||
}
|
||||
if other.size == Zero::zero() {
|
||||
return *self;
|
||||
}
|
||||
|
||||
let upper_left = TypedPoint2D::new(min(self.min_x(), other.min_x()),
|
||||
min(self.min_y(), other.min_y()));
|
||||
|
||||
let lower_right_x = max(self.max_x(), other.max_x());
|
||||
let lower_right_y = max(self.max_y(), other.max_y());
|
||||
|
||||
TypedRect::new(
|
||||
upper_left,
|
||||
TypedSize2D::new(lower_right_x - upper_left.x, lower_right_y - upper_left.y)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedRect<T, U> {
|
||||
#[inline]
|
||||
pub fn scale<Scale: Copy>(&self, x: Scale, y: Scale) -> TypedRect<T, U>
|
||||
where T: Copy + Clone + Mul<Scale, Output=T> {
|
||||
TypedRect::new(
|
||||
TypedPoint2D::new(self.origin.x * x, self.origin.y * y),
|
||||
TypedSize2D::new(self.size.width * x, self.size.height * y)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + PartialEq + Zero, U> TypedRect<T, U> {
|
||||
/// Constructor, setting all sides to zero.
|
||||
pub fn zero() -> TypedRect<T, U> {
|
||||
TypedRect::new(
|
||||
TypedPoint2D::zero(),
|
||||
TypedSize2D::zero(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the size is zero, regardless of the origin's value.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.size.width == Zero::zero() || self.size.height == Zero::zero()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn min<T: Clone + PartialOrd>(x: T, y: T) -> T {
|
||||
if x <= y { x } else { y }
|
||||
}
|
||||
|
||||
pub fn max<T: Clone + PartialOrd>(x: T, y: T) -> T {
|
||||
if x >= y { x } else { y }
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedRect<T, U> {
|
||||
type Output = TypedRect<T, U>;
|
||||
#[inline]
|
||||
fn mul(self, scale: T) -> TypedRect<T, U> {
|
||||
TypedRect::new(self.origin * scale, self.size * scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedRect<T, U> {
|
||||
type Output = TypedRect<T, U>;
|
||||
#[inline]
|
||||
fn div(self, scale: T) -> TypedRect<T, U> {
|
||||
TypedRect::new(self.origin / scale, self.size / scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U1, U2> Mul<ScaleFactor<T, U1, U2>> for TypedRect<T, U1> {
|
||||
type Output = TypedRect<T, U2>;
|
||||
#[inline]
|
||||
fn mul(self, scale: ScaleFactor<T, U1, U2>) -> TypedRect<T, U2> {
|
||||
TypedRect::new(self.origin * scale, self.size * scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U1, U2> Div<ScaleFactor<T, U1, U2>> for TypedRect<T, U2> {
|
||||
type Output = TypedRect<T, U1>;
|
||||
#[inline]
|
||||
fn div(self, scale: ScaleFactor<T, U1, U2>) -> TypedRect<T, U1> {
|
||||
TypedRect::new(self.origin / scale, self.size / scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, Unit> TypedRect<T, Unit> {
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
pub fn to_untyped(&self) -> Rect<T> {
|
||||
TypedRect::new(self.origin.to_untyped(), self.size.to_untyped())
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
pub fn from_untyped(r: &Rect<T>) -> TypedRect<T, Unit> {
|
||||
TypedRect::new(TypedPoint2D::from_untyped(&r.origin), TypedSize2D::from_untyped(&r.size))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T0: NumCast + Copy, Unit> TypedRect<T0, Unit> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
///
|
||||
/// When casting from floating point to integer coordinates, the decimals are truncated
|
||||
/// as one would expect from a simple cast, but this behavior does not always make sense
|
||||
/// geometrically. Consider using round(), round_in or round_out() before casting.
|
||||
pub fn cast<T1: NumCast + Copy>(&self) -> Option<TypedRect<T1, Unit>> {
|
||||
match (self.origin.cast(), self.size.cast()) {
|
||||
(Some(origin), Some(size)) => Some(TypedRect::new(origin, size)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Floor + Ceil + Round + Add<T, Output=T> + Sub<T, Output=T>, U> TypedRect<T, U> {
|
||||
/// Return a rectangle with edges rounded to integer coordinates, such that
|
||||
/// the returned rectangle has the same set of pixel centers as the original
|
||||
/// one.
|
||||
/// Edges at offset 0.5 round up.
|
||||
/// Suitable for most places where integral device coordinates
|
||||
/// are needed, but note that any translation should be applied first to
|
||||
/// avoid pixel rounding errors.
|
||||
/// Note that this is *not* rounding to nearest integer if the values are negative.
|
||||
/// They are always rounding as floor(n + 0.5).
|
||||
pub fn round(&self) -> Self {
|
||||
let origin = self.origin.round();
|
||||
let size = self.origin.add_size(&self.size).round() - origin;
|
||||
TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
|
||||
}
|
||||
|
||||
/// Return a rectangle with edges rounded to integer coordinates, such that
|
||||
/// the original rectangle contains the resulting rectangle.
|
||||
pub fn round_in(&self) -> Self {
|
||||
let origin = self.origin.ceil();
|
||||
let size = self.origin.add_size(&self.size).floor() - origin;
|
||||
TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
|
||||
}
|
||||
|
||||
/// Return a rectangle with edges rounded to integer coordinates, such that
|
||||
/// the original rectangle is contained in the resulting rectangle.
|
||||
pub fn round_out(&self) -> Self {
|
||||
let origin = self.origin.floor();
|
||||
let size = self.origin.add_size(&self.size).ceil() - origin;
|
||||
TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions for common casts
|
||||
impl<T: NumCast + Copy, Unit> TypedRect<T, Unit> {
|
||||
/// Cast into an `f32` rectangle.
|
||||
pub fn to_f32(&self) -> TypedRect<f32, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `usize` rectangle, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point rectangles, it is worth considering whether
|
||||
/// to `round()`, `round_in()` or `round_out()` before the cast in order to
|
||||
/// obtain the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedRect<usize, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i32` rectangle, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point rectangles, it is worth considering whether
|
||||
/// to `round()`, `round_in()` or `round_out()` before the cast in order to
|
||||
/// obtain the desired conversion behavior.
|
||||
pub fn to_i32(&self) -> TypedRect<i32, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i64` rectangle, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point rectangles, it is worth considering whether
|
||||
/// to `round()`, `round_in()` or `round_out()` before the cast in order to
|
||||
/// obtain the desired conversion behavior.
|
||||
pub fn to_i64(&self) -> TypedRect<i64, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for `TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))`.
|
||||
pub fn rect<T: Copy, U>(x: T, y: T, w: T, h: T) -> TypedRect<T, U> {
|
||||
TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use point::Point2D;
|
||||
use size::Size2D;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_min_max() {
|
||||
assert!(min(0u32, 1u32) == 0u32);
|
||||
assert!(min(-1.0f32, 0.0f32) == -1.0f32);
|
||||
|
||||
assert!(max(0u32, 1u32) == 1u32);
|
||||
assert!(max(-1.0f32, 0.0f32) == 0.0f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_translate() {
|
||||
let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
|
||||
let pp = p.translate(&Point2D::new(10,15));
|
||||
|
||||
assert!(pp.size.width == 50);
|
||||
assert!(pp.size.height == 40);
|
||||
assert!(pp.origin.x == 10);
|
||||
assert!(pp.origin.y == 15);
|
||||
|
||||
|
||||
let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
|
||||
let rr = r.translate(&Point2D::new(0,-10));
|
||||
|
||||
assert!(rr.size.width == 50);
|
||||
assert!(rr.size.height == 40);
|
||||
assert!(rr.origin.x == -10);
|
||||
assert!(rr.origin.y == -15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_translate_by_size() {
|
||||
let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
|
||||
let pp = p.translate_by_size(&Size2D::new(10,15));
|
||||
|
||||
assert!(pp.size.width == 50);
|
||||
assert!(pp.size.height == 40);
|
||||
assert!(pp.origin.x == 10);
|
||||
assert!(pp.origin.y == 15);
|
||||
|
||||
|
||||
let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
|
||||
let rr = r.translate_by_size(&Size2D::new(0,-10));
|
||||
|
||||
assert!(rr.size.width == 50);
|
||||
assert!(rr.size.height == 40);
|
||||
assert!(rr.origin.x == -10);
|
||||
assert!(rr.origin.y == -15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_union() {
|
||||
let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40));
|
||||
let q = Rect::new(Point2D::new(20,20), Size2D::new(5, 5));
|
||||
let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15));
|
||||
let s = Rect::new(Point2D::new(20, -15), Size2D::new(250, 200));
|
||||
|
||||
let pq = p.union(&q);
|
||||
assert!(pq.origin == Point2D::new(0, 0));
|
||||
assert!(pq.size == Size2D::new(50, 40));
|
||||
|
||||
let pr = p.union(&r);
|
||||
assert!(pr.origin == Point2D::new(-15, -30));
|
||||
assert!(pr.size == Size2D::new(200, 70));
|
||||
|
||||
let ps = p.union(&s);
|
||||
assert!(ps.origin == Point2D::new(0, -15));
|
||||
assert!(ps.size == Size2D::new(270, 200));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intersection() {
|
||||
let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
|
||||
let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10));
|
||||
let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8));
|
||||
|
||||
let pq = p.intersection(&q);
|
||||
assert!(pq.is_some());
|
||||
let pq = pq.unwrap();
|
||||
assert!(pq.origin == Point2D::new(5, 15));
|
||||
assert!(pq.size == Size2D::new(5, 5));
|
||||
|
||||
let pr = p.intersection(&r);
|
||||
assert!(pr.is_some());
|
||||
let pr = pr.unwrap();
|
||||
assert!(pr.origin == Point2D::new(0, 0));
|
||||
assert!(pr.size == Size2D::new(3, 3));
|
||||
|
||||
let qr = q.intersection(&r);
|
||||
assert!(qr.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200));
|
||||
|
||||
assert!(r.contains(&Point2D::new(0, 50)));
|
||||
assert!(r.contains(&Point2D::new(-10, 200)));
|
||||
|
||||
// The `contains` method is inclusive of the top/left edges, but not the
|
||||
// bottom/right edges.
|
||||
assert!(r.contains(&Point2D::new(-20, 15)));
|
||||
assert!(!r.contains(&Point2D::new(80, 15)));
|
||||
assert!(!r.contains(&Point2D::new(80, 215)));
|
||||
assert!(!r.contains(&Point2D::new(-20, 215)));
|
||||
|
||||
// Points beyond the top-left corner.
|
||||
assert!(!r.contains(&Point2D::new(-25, 15)));
|
||||
assert!(!r.contains(&Point2D::new(-15, 10)));
|
||||
|
||||
// Points beyond the top-right corner.
|
||||
assert!(!r.contains(&Point2D::new(85, 20)));
|
||||
assert!(!r.contains(&Point2D::new(75, 10)));
|
||||
|
||||
// Points beyond the bottom-right corner.
|
||||
assert!(!r.contains(&Point2D::new(85, 210)));
|
||||
assert!(!r.contains(&Point2D::new(75, 220)));
|
||||
|
||||
// Points beyond the bottom-left corner.
|
||||
assert!(!r.contains(&Point2D::new(-25, 210)));
|
||||
assert!(!r.contains(&Point2D::new(-15, 220)));
|
||||
|
||||
let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0));
|
||||
assert!(r.contains_rect(&r));
|
||||
assert!(!r.contains_rect(&r.translate(&Point2D::new( 0.1, 0.0))));
|
||||
assert!(!r.contains_rect(&r.translate(&Point2D::new(-0.1, 0.0))));
|
||||
assert!(!r.contains_rect(&r.translate(&Point2D::new( 0.0, 0.1))));
|
||||
assert!(!r.contains_rect(&r.translate(&Point2D::new( 0.0, -0.1))));
|
||||
// Empty rectangles are always considered as contained in other rectangles,
|
||||
// even if their origin is not.
|
||||
let p = Point2D::new(1.0, 1.0);
|
||||
assert!(!r.contains(&p));
|
||||
assert!(r.contains_rect(&Rect::new(p, Size2D::zero())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scale() {
|
||||
let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
|
||||
let pp = p.scale(10, 15);
|
||||
|
||||
assert!(pp.size.width == 500);
|
||||
assert!(pp.size.height == 600);
|
||||
assert!(pp.origin.x == 0);
|
||||
assert!(pp.origin.y == 0);
|
||||
|
||||
let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
|
||||
let rr = r.scale(1, 20);
|
||||
|
||||
assert!(rr.size.width == 50);
|
||||
assert!(rr.size.height == 800);
|
||||
assert!(rr.origin.x == -10);
|
||||
assert!(rr.origin.y == -100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inflate() {
|
||||
let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 10));
|
||||
let pp = p.inflate(10, 20);
|
||||
|
||||
assert!(pp.size.width == 30);
|
||||
assert!(pp.size.height == 50);
|
||||
assert!(pp.origin.x == -10);
|
||||
assert!(pp.origin.y == -20);
|
||||
|
||||
let r = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
|
||||
let rr = r.inflate(-2, -5);
|
||||
|
||||
assert!(rr.size.width == 6);
|
||||
assert!(rr.size.height == 10);
|
||||
assert!(rr.origin.x == 2);
|
||||
assert!(rr.origin.y == 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_min_max_x_y() {
|
||||
let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
|
||||
assert!(p.max_y() == 40);
|
||||
assert!(p.min_y() == 0);
|
||||
assert!(p.max_x() == 50);
|
||||
assert!(p.min_x() == 0);
|
||||
|
||||
let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
|
||||
assert!(r.max_y() == 35);
|
||||
assert!(r.min_y() == -5);
|
||||
assert!(r.max_x() == 40);
|
||||
assert!(r.min_x() == -10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_empty() {
|
||||
assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 0u32)).is_empty());
|
||||
assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(10u32, 0u32)).is_empty());
|
||||
assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 10u32)).is_empty());
|
||||
assert!(!Rect::new(Point2D::new(0u32, 0u32), Size2D::new(1u32, 1u32)).is_empty());
|
||||
assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 0u32)).is_empty());
|
||||
assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(10u32, 0u32)).is_empty());
|
||||
assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 10u32)).is_empty());
|
||||
assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_round() {
|
||||
let mut x = -2.0;
|
||||
let mut y = -2.0;
|
||||
let mut w = -2.0;
|
||||
let mut h = -2.0;
|
||||
while x < 2.0 {
|
||||
while y < 2.0 {
|
||||
while w < 2.0 {
|
||||
while h < 2.0 {
|
||||
let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h));
|
||||
|
||||
assert!(rect.contains_rect(&rect.round_in()));
|
||||
assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect));
|
||||
|
||||
assert!(rect.round_out().contains_rect(&rect));
|
||||
assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out()));
|
||||
|
||||
assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round()));
|
||||
assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect));
|
||||
|
||||
h += 0.1;
|
||||
}
|
||||
w += 0.1;
|
||||
}
|
||||
y += 0.1;
|
||||
}
|
||||
x += 0.1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
//! A type-checked scaling factor between units.
|
||||
|
||||
use num::One;
|
||||
|
||||
use heapsize::HeapSizeOf;
|
||||
use num_traits::NumCast;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Mul, Sub, Div};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A scaling factor between two different units of measurement.
|
||||
///
|
||||
/// This is effectively a type-safe float, intended to be used in combination with other types like
|
||||
/// `length::Length` to enforce conversion between systems of measurement at compile time.
|
||||
///
|
||||
/// `Src` and `Dst` represent the units before and after multiplying a value by a `ScaleFactor`. They
|
||||
/// may be types without values, such as empty enums. For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// use euclid::scale_factor::ScaleFactor;
|
||||
/// use euclid::length::Length;
|
||||
/// enum Mm {};
|
||||
/// enum Inch {};
|
||||
///
|
||||
/// let mm_per_inch: ScaleFactor<f32, Inch, Mm> = ScaleFactor::new(25.4);
|
||||
///
|
||||
/// let one_foot: Length<f32, Inch> = Length::new(12.0);
|
||||
/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch;
|
||||
/// ```
|
||||
#[repr(C)]
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct ScaleFactor<T, Src, Dst>(pub T, PhantomData<(Src, Dst)>);
|
||||
|
||||
impl<T: HeapSizeOf, Src, Dst> HeapSizeOf for ScaleFactor<T, Src, Dst> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.0.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Src, Dst> Deserialize for ScaleFactor<T, Src, Dst> where T: Deserialize {
|
||||
fn deserialize<D>(deserializer: D) -> Result<ScaleFactor<T, Src, Dst>, D::Error>
|
||||
where D: Deserializer {
|
||||
Ok(ScaleFactor(try!(Deserialize::deserialize(deserializer)), PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Src, Dst> Serialize for ScaleFactor<T, Src, Dst> where T: Serialize {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Src, Dst> ScaleFactor<T, Src, Dst> {
|
||||
pub fn new(x: T) -> ScaleFactor<T, Src, Dst> {
|
||||
ScaleFactor(x, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, Src, Dst> ScaleFactor<T, Src, Dst> {
|
||||
pub fn get(&self) -> T {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + One + Div<T, Output=T>, Src, Dst> ScaleFactor<T, Src, Dst> {
|
||||
/// The inverse ScaleFactor (1.0 / self).
|
||||
pub fn inv(&self) -> ScaleFactor<T, Dst, Src> {
|
||||
let one: T = One::one();
|
||||
ScaleFactor::new(one / self.get())
|
||||
}
|
||||
}
|
||||
|
||||
// scale0 * scale1
|
||||
impl<T: Clone + Mul<T, Output=T>, A, B, C>
|
||||
Mul<ScaleFactor<T, B, C>> for ScaleFactor<T, A, B> {
|
||||
type Output = ScaleFactor<T, A, C>;
|
||||
#[inline]
|
||||
fn mul(self, other: ScaleFactor<T, B, C>) -> ScaleFactor<T, A, C> {
|
||||
ScaleFactor::new(self.get() * other.get())
|
||||
}
|
||||
}
|
||||
|
||||
// scale0 + scale1
|
||||
impl<T: Clone + Add<T, Output=T>, Src, Dst> Add for ScaleFactor<T, Src, Dst> {
|
||||
type Output = ScaleFactor<T, Src, Dst>;
|
||||
#[inline]
|
||||
fn add(self, other: ScaleFactor<T, Src, Dst>) -> ScaleFactor<T, Src, Dst> {
|
||||
ScaleFactor::new(self.get() + other.get())
|
||||
}
|
||||
}
|
||||
|
||||
// scale0 - scale1
|
||||
impl<T: Clone + Sub<T, Output=T>, Src, Dst> Sub for ScaleFactor<T, Src, Dst> {
|
||||
type Output = ScaleFactor<T, Src, Dst>;
|
||||
#[inline]
|
||||
fn sub(self, other: ScaleFactor<T, Src, Dst>) -> ScaleFactor<T, Src, Dst> {
|
||||
ScaleFactor::new(self.get() - other.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast + Clone, Src, Dst0> ScaleFactor<T, Src, Dst0> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
pub fn cast<T1: NumCast + Clone>(&self) -> Option<ScaleFactor<T1, Src, Dst0>> {
|
||||
NumCast::from(self.get()).map(ScaleFactor::new)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed:
|
||||
// https://github.com/mozilla/rust/issues/7671
|
||||
|
||||
impl<T: PartialEq, Src, Dst> PartialEq for ScaleFactor<T, Src, Dst> {
|
||||
fn eq(&self, other: &ScaleFactor<T, Src, Dst>) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, Src, Dst> Clone for ScaleFactor<T, Src, Dst> {
|
||||
fn clone(&self) -> ScaleFactor<T, Src, Dst> {
|
||||
ScaleFactor::new(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, Src, Dst> Copy for ScaleFactor<T, Src, Dst> {}
|
||||
|
||||
impl<T: fmt::Debug, Src, Dst> fmt::Debug for ScaleFactor<T, Src, Dst> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display, Src, Dst> fmt::Display for ScaleFactor<T, Src, Dst> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ScaleFactor;
|
||||
|
||||
enum Inch {}
|
||||
enum Cm {}
|
||||
enum Mm {}
|
||||
|
||||
#[test]
|
||||
fn test_scale_factor() {
|
||||
let mm_per_inch: ScaleFactor<f32, Inch, Mm> = ScaleFactor::new(25.4);
|
||||
let cm_per_mm: ScaleFactor<f32, Mm, Cm> = ScaleFactor::new(0.1);
|
||||
|
||||
let mm_per_cm: ScaleFactor<f32, Cm, Mm> = cm_per_mm.inv();
|
||||
assert_eq!(mm_per_cm.get(), 10.0);
|
||||
|
||||
let cm_per_inch: ScaleFactor<f32, Inch, Cm> = mm_per_inch * cm_per_mm;
|
||||
assert_eq!(cm_per_inch, ScaleFactor::new(2.54));
|
||||
|
||||
let a: ScaleFactor<isize, Inch, Inch> = ScaleFactor::new(2);
|
||||
let b: ScaleFactor<isize, Inch, Inch> = ScaleFactor::new(3);
|
||||
assert!(a != b);
|
||||
assert_eq!(a, a.clone());
|
||||
assert_eq!(a.clone() + b.clone(), ScaleFactor::new(5));
|
||||
assert_eq!(a - b, ScaleFactor::new(-1));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! A group of side offsets, which correspond to top/left/bottom/right for borders, padding,
|
||||
//! and margins in CSS.
|
||||
|
||||
use super::UnknownUnit;
|
||||
use length::Length;
|
||||
use num::Zero;
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
use heapsize::HeapSizeOf;
|
||||
|
||||
/// A group of side offsets, which correspond to top/left/bottom/right for borders, padding,
|
||||
/// and margins in CSS, optionally tagged with a unit.
|
||||
define_matrix! {
|
||||
pub struct TypedSideOffsets2D<T, U> {
|
||||
pub top: T,
|
||||
pub right: T,
|
||||
pub bottom: T,
|
||||
pub left: T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, U> fmt::Debug for TypedSideOffsets2D<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({:?},{:?},{:?},{:?})",
|
||||
self.top, self.right, self.bottom, self.left)
|
||||
}
|
||||
}
|
||||
|
||||
/// The default side offset type with no unit.
|
||||
pub type SideOffsets2D<T> = TypedSideOffsets2D<T, UnknownUnit>;
|
||||
|
||||
impl<T: Copy, U> TypedSideOffsets2D<T, U> {
|
||||
/// Constructor taking a scalar for each side.
|
||||
pub fn new(top: T, right: T, bottom: T, left: T) -> TypedSideOffsets2D<T, U> {
|
||||
TypedSideOffsets2D {
|
||||
top: top,
|
||||
right: right,
|
||||
bottom: bottom,
|
||||
left: left,
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructor taking a typed Length for each side.
|
||||
pub fn from_lengths(top: Length<T, U>,
|
||||
right: Length<T, U>,
|
||||
bottom: Length<T, U>,
|
||||
left: Length<T, U>) -> TypedSideOffsets2D<T, U> {
|
||||
TypedSideOffsets2D::new(top.0, right.0, bottom.0, left.0)
|
||||
}
|
||||
|
||||
/// Access self.top as a typed Length instead of a scalar value.
|
||||
pub fn top_typed(&self) -> Length<T, U> { Length::new(self.top) }
|
||||
|
||||
/// Access self.right as a typed Length instead of a scalar value.
|
||||
pub fn right_typed(&self) -> Length<T, U> { Length::new(self.right) }
|
||||
|
||||
/// Access self.bottom as a typed Length instead of a scalar value.
|
||||
pub fn bottom_typed(&self) -> Length<T, U> { Length::new(self.bottom) }
|
||||
|
||||
/// Access self.left as a typed Length instead of a scalar value.
|
||||
pub fn left_typed(&self) -> Length<T, U> { Length::new(self.left) }
|
||||
|
||||
/// Constructor setting the same value to all sides, taking a scalar value directly.
|
||||
pub fn new_all_same(all: T) -> TypedSideOffsets2D<T, U> {
|
||||
TypedSideOffsets2D::new(all, all, all, all)
|
||||
}
|
||||
|
||||
/// Constructor setting the same value to all sides, taking a typed Length.
|
||||
pub fn from_length_all_same(all: Length<T, U>) -> TypedSideOffsets2D<T, U> {
|
||||
TypedSideOffsets2D::new_all_same(all.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedSideOffsets2D<T, U> where T: Add<T, Output=T> + Copy {
|
||||
pub fn horizontal(&self) -> T {
|
||||
self.left + self.right
|
||||
}
|
||||
|
||||
pub fn vertical(&self) -> T {
|
||||
self.top + self.bottom
|
||||
}
|
||||
|
||||
pub fn horizontal_typed(&self) -> Length<T, U> {
|
||||
Length::new(self.horizontal())
|
||||
}
|
||||
|
||||
pub fn vertical_typed(&self) -> Length<T, U> {
|
||||
Length::new(self.vertical())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Add for TypedSideOffsets2D<T, U> where T : Copy + Add<T, Output=T> {
|
||||
type Output = TypedSideOffsets2D<T, U>;
|
||||
fn add(self, other: TypedSideOffsets2D<T, U>) -> TypedSideOffsets2D<T, U> {
|
||||
TypedSideOffsets2D::new(
|
||||
self.top + other.top,
|
||||
self.right + other.right,
|
||||
self.bottom + other.bottom,
|
||||
self.left + other.left,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Zero, U> TypedSideOffsets2D<T, U> {
|
||||
/// Constructor, setting all sides to zero.
|
||||
pub fn zero() -> TypedSideOffsets2D<T, U> {
|
||||
TypedSideOffsets2D::new(
|
||||
Zero::zero(),
|
||||
Zero::zero(),
|
||||
Zero::zero(),
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A SIMD enabled version of TypedSideOffsets2D specialized for i32.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[repr(simd)]
|
||||
pub struct SideOffsets2DSimdI32 {
|
||||
pub top: i32,
|
||||
pub bottom: i32,
|
||||
pub right: i32,
|
||||
pub left: i32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
impl HeapSizeOf for SideOffsets2DSimdI32 {
|
||||
fn heap_size_of_children(&self) -> usize { 0 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
impl SideOffsets2DSimdI32 {
|
||||
#[inline]
|
||||
pub fn new(top: i32, right: i32, bottom: i32, left: i32) -> SideOffsets2DSimdI32 {
|
||||
SideOffsets2DSimdI32 {
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
right: right,
|
||||
left: left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
impl SideOffsets2DSimdI32 {
|
||||
#[inline]
|
||||
pub fn new_all_same(all: i32) -> SideOffsets2DSimdI32 {
|
||||
SideOffsets2DSimdI32::new(all.clone(), all.clone(), all.clone(), all.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
impl SideOffsets2DSimdI32 {
|
||||
#[inline]
|
||||
pub fn horizontal(&self) -> i32 {
|
||||
self.left + self.right
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn vertical(&self) -> i32 {
|
||||
self.top + self.bottom
|
||||
}
|
||||
}
|
||||
|
||||
/*impl Add for SideOffsets2DSimdI32 {
|
||||
type Output = SideOffsets2DSimdI32;
|
||||
#[inline]
|
||||
fn add(self, other: SideOffsets2DSimdI32) -> SideOffsets2DSimdI32 {
|
||||
self + other // Use SIMD addition
|
||||
}
|
||||
}*/
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
impl SideOffsets2DSimdI32 {
|
||||
#[inline]
|
||||
pub fn zero() -> SideOffsets2DSimdI32 {
|
||||
SideOffsets2DSimdI32 {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
#[inline]
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.top == 0 && self.right == 0 && self.bottom == 0 && self.left == 0
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[inline]
|
||||
pub fn is_zero(&self) -> bool {
|
||||
let is_zero: bool;
|
||||
unsafe {
|
||||
asm! {
|
||||
"ptest $1, $1
|
||||
setz $0"
|
||||
: "=r"(is_zero)
|
||||
: "x"(*self)
|
||||
:
|
||||
: "intel"
|
||||
};
|
||||
}
|
||||
is_zero
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SideOffsets2DSimdI32;
|
||||
|
||||
#[test]
|
||||
fn test_is_zero() {
|
||||
assert!(SideOffsets2DSimdI32::new_all_same(0).is_zero());
|
||||
assert!(!SideOffsets2DSimdI32::new_all_same(1).is_zero());
|
||||
assert!(!SideOffsets2DSimdI32::new(1, 0, 0, 0).is_zero());
|
||||
assert!(!SideOffsets2DSimdI32::new(0, 1, 0, 0).is_zero());
|
||||
assert!(!SideOffsets2DSimdI32::new(0, 0, 1, 0).is_zero());
|
||||
assert!(!SideOffsets2DSimdI32::new(0, 0, 0, 1).is_zero());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
#[cfg(bench)]
|
||||
mod bench {
|
||||
use test::BenchHarness;
|
||||
use std::num::Zero;
|
||||
use rand::{XorShiftRng, Rng};
|
||||
use super::SideOffsets2DSimdI32;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[bench]
|
||||
fn bench_naive_is_zero(bh: &mut BenchHarness) {
|
||||
fn is_zero(x: &SideOffsets2DSimdI32) -> bool {
|
||||
x.top.is_zero() && x.right.is_zero() && x.bottom.is_zero() && x.left.is_zero()
|
||||
}
|
||||
let mut rng = XorShiftRng::new().unwrap();
|
||||
bh.iter(|| is_zero(&rng.gen::<SideOffsets2DSimdI32>()))
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_is_zero(bh: &mut BenchHarness) {
|
||||
let mut rng = XorShiftRng::new().unwrap();
|
||||
bh.iter(|| rng.gen::<SideOffsets2DSimdI32>().is_zero())
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_naive_add(bh: &mut BenchHarness) {
|
||||
fn add(x: &SideOffsets2DSimdI32, y: &SideOffsets2DSimdI32) -> SideOffsets2DSimdI32 {
|
||||
SideOffsets2DSimdI32 {
|
||||
top: x.top + y.top,
|
||||
right: x.right + y.right,
|
||||
bottom: x.bottom + y.bottom,
|
||||
left: x.left + y.left,
|
||||
}
|
||||
}
|
||||
let mut rng = XorShiftRng::new().unwrap();
|
||||
bh.iter(|| add(&rng.gen::<SideOffsets2DSimdI32>(), &rng.gen::<SideOffsets2DSimdI32>()))
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_add(bh: &mut BenchHarness) {
|
||||
let mut rng = XorShiftRng::new().unwrap();
|
||||
bh.iter(|| rng.gen::<SideOffsets2DSimdI32>() + rng.gen::<SideOffsets2DSimdI32>())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::UnknownUnit;
|
||||
use length::Length;
|
||||
use scale_factor::ScaleFactor;
|
||||
use num::*;
|
||||
|
||||
use num_traits::NumCast;
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A 2d size tagged with a unit.
|
||||
define_matrix! {
|
||||
#[derive(RustcDecodable, RustcEncodable)]
|
||||
pub struct TypedSize2D<T, U> {
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default 2d size type with no unit.
|
||||
///
|
||||
/// `Size2D` provides the same methods as `TypedSize2D`.
|
||||
pub type Size2D<T> = TypedSize2D<T, UnknownUnit>;
|
||||
|
||||
impl<T: fmt::Debug, U> fmt::Debug for TypedSize2D<T, U> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}×{:?}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display, U> fmt::Display for TypedSize2D<T, U> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "({}x{})", self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> TypedSize2D<T, U> {
|
||||
/// Constructor taking scalar values.
|
||||
pub fn new(width: T, height: T) -> TypedSize2D<T, U> {
|
||||
TypedSize2D {
|
||||
width: width,
|
||||
height: height,
|
||||
_unit: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, U> TypedSize2D<T, U> {
|
||||
/// Constructor taking scalar strongly typed lengths.
|
||||
pub fn from_lengths(width: Length<T, U>, height: Length<T, U>) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(width.get(), height.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Round, U> TypedSize2D<T, U> {
|
||||
/// Rounds each component to the nearest integer value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn round(&self) -> Self {
|
||||
TypedSize2D::new(self.width.round(), self.height.round())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ceil, U> TypedSize2D<T, U> {
|
||||
/// Rounds each component to the smallest integer equal or greater than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn ceil(&self) -> Self {
|
||||
TypedSize2D::new(self.width.ceil(), self.height.ceil())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Floor, U> TypedSize2D<T, U> {
|
||||
/// Rounds each component to the biggest integer equal or lower than the original value.
|
||||
///
|
||||
/// This behavior is preserved for negative values (unlike the basic cast).
|
||||
pub fn floor(&self) -> Self {
|
||||
TypedSize2D::new(self.width.floor(), self.height.floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Add<T, Output=T>, U> Add for TypedSize2D<T, U> {
|
||||
type Output = TypedSize2D<T, U>;
|
||||
fn add(self, other: TypedSize2D<T, U>) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(self.width + other.width, self.height + other.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Sub<T, Output=T>, U> Sub for TypedSize2D<T, U> {
|
||||
type Output = TypedSize2D<T, U>;
|
||||
fn sub(self, other: TypedSize2D<T, U>) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(self.width - other.width, self.height - other.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Clone + Mul<T, Output=U>, U> TypedSize2D<T, U> {
|
||||
pub fn area(&self) -> U { self.width * self.height }
|
||||
}
|
||||
|
||||
impl<T: Zero, U> TypedSize2D<T, U> {
|
||||
pub fn zero() -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(
|
||||
Zero::zero(),
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zero, U> Zero for TypedSize2D<T, U> {
|
||||
fn zero() -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(
|
||||
Zero::zero(),
|
||||
Zero::zero(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedSize2D<T, U> {
|
||||
type Output = TypedSize2D<T, U>;
|
||||
#[inline]
|
||||
fn mul(self, scale: T) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(self.width * scale, self.height * scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedSize2D<T, U> {
|
||||
type Output = TypedSize2D<T, U>;
|
||||
#[inline]
|
||||
fn div(self, scale: T) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(self.width / scale, self.height / scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Mul<T, Output=T>, U1, U2> Mul<ScaleFactor<T, U1, U2>> for TypedSize2D<T, U1> {
|
||||
type Output = TypedSize2D<T, U2>;
|
||||
#[inline]
|
||||
fn mul(self, scale: ScaleFactor<T, U1, U2>) -> TypedSize2D<T, U2> {
|
||||
TypedSize2D::new(self.width * scale.get(), self.height * scale.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Div<T, Output=T>, U1, U2> Div<ScaleFactor<T, U1, U2>> for TypedSize2D<T, U2> {
|
||||
type Output = TypedSize2D<T, U1>;
|
||||
#[inline]
|
||||
fn div(self, scale: ScaleFactor<T, U1, U2>) -> TypedSize2D<T, U1> {
|
||||
TypedSize2D::new(self.width / scale.get(), self.height / scale.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy, U> TypedSize2D<T, U> {
|
||||
/// Returns self.width as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn width_typed(&self) -> Length<T, U> { Length::new(self.width) }
|
||||
|
||||
/// Returns self.height as a Length carrying the unit.
|
||||
#[inline]
|
||||
pub fn height_typed(&self) -> Length<T, U> { Length::new(self.height) }
|
||||
|
||||
#[inline]
|
||||
pub fn to_array(&self) -> [T; 2] { [self.width, self.height] }
|
||||
|
||||
/// Drop the units, preserving only the numeric value.
|
||||
pub fn to_untyped(&self) -> Size2D<T> {
|
||||
TypedSize2D::new(self.width, self.height)
|
||||
}
|
||||
|
||||
/// Tag a unitless value with units.
|
||||
pub fn from_untyped(p: &Size2D<T>) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(p.width, p.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast + Copy, Unit> TypedSize2D<T, Unit> {
|
||||
/// Cast from one numeric representation to another, preserving the units.
|
||||
///
|
||||
/// When casting from floating point to integer coordinates, the decimals are truncated
|
||||
/// as one would expect from a simple cast, but this behavior does not always make sense
|
||||
/// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
|
||||
pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedSize2D<NewT, Unit>> {
|
||||
match (NumCast::from(self.width), NumCast::from(self.height)) {
|
||||
(Some(w), Some(h)) => Some(TypedSize2D::new(w, h)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions for common casts
|
||||
|
||||
/// Cast into an `f32` size.
|
||||
pub fn to_f32(&self) -> TypedSize2D<f32, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `uint` size, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point sizes, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedSize2D<usize, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i32` size, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point sizes, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i32(&self) -> TypedSize2D<i32, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
/// Cast into an `i64` size, truncating decimals if any.
|
||||
///
|
||||
/// When casting from floating point sizes, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_i64(&self) -> TypedSize2D<i64, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for `TypedSize2D::new(w, h)`.
|
||||
pub fn size2<T, U>(w: T, h: T) -> TypedSize2D<T, U> {
|
||||
TypedSize2D::new(w, h)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod size2d {
|
||||
use super::Size2D;
|
||||
|
||||
#[test]
|
||||
pub fn test_add() {
|
||||
let p1 = Size2D::new(1.0, 2.0);
|
||||
let p2 = Size2D::new(3.0, 4.0);
|
||||
assert_eq!(p1 + p2, Size2D::new(4.0, 6.0));
|
||||
|
||||
let p1 = Size2D::new(1.0, 2.0);
|
||||
let p2 = Size2D::new(0.0, 0.0);
|
||||
assert_eq!(p1 + p2, Size2D::new(1.0, 2.0));
|
||||
|
||||
let p1 = Size2D::new(1.0, 2.0);
|
||||
let p2 = Size2D::new(-3.0, -4.0);
|
||||
assert_eq!(p1 + p2, Size2D::new(-2.0, -2.0));
|
||||
|
||||
let p1 = Size2D::new(0.0, 0.0);
|
||||
let p2 = Size2D::new(0.0, 0.0);
|
||||
assert_eq!(p1 + p2, Size2D::new(0.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_sub() {
|
||||
let p1 = Size2D::new(1.0, 2.0);
|
||||
let p2 = Size2D::new(3.0, 4.0);
|
||||
assert_eq!(p1 - p2, Size2D::new(-2.0, -2.0));
|
||||
|
||||
let p1 = Size2D::new(1.0, 2.0);
|
||||
let p2 = Size2D::new(0.0, 0.0);
|
||||
assert_eq!(p1 - p2, Size2D::new(1.0, 2.0));
|
||||
|
||||
let p1 = Size2D::new(1.0, 2.0);
|
||||
let p2 = Size2D::new(-3.0, -4.0);
|
||||
assert_eq!(p1 - p2, Size2D::new(4.0, 6.0));
|
||||
|
||||
let p1 = Size2D::new(0.0, 0.0);
|
||||
let p2 = Size2D::new(0.0, 0.0);
|
||||
assert_eq!(p1 - p2, Size2D::new(0.0, 0.0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
|
||||
/// Trait for basic trigonometry functions, so they can be used on generic numeric types
|
||||
pub trait Trig {
|
||||
fn sin(self) -> Self;
|
||||
fn cos(self) -> Self;
|
||||
fn tan(self) -> Self;
|
||||
}
|
||||
|
||||
impl Trig for f32 {
|
||||
#[inline]
|
||||
fn sin(self) -> f32 {
|
||||
self.sin()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cos(self) -> f32 {
|
||||
self.cos()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tan(self) -> f32 {
|
||||
self.tan()
|
||||
}
|
||||
}
|
||||
|
||||
impl Trig for f64 {
|
||||
#[inline]
|
||||
fn sin(self) -> f64 {
|
||||
self.sin()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cos(self) -> f64 {
|
||||
self.cos()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tan(self) -> f64 {
|
||||
self.tan()
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"118514fd9c4958df0d25584cda4917186c46011569f55ef350530c1ad3fbdb48",".travis.yml":"13d3e5a7bf83b04c8e8cfa14f0297bd8366d68391d977dd547f64707dffc275a","COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"10cfe5580ee83ae883a60d96f504dda8ae7885ae5fd3a3faf95c2a2b8b38fad0","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"52f974f01c1e15182413e4321c8817d5e66fe4d92c5ec223c857dd0440f5c229","src/approxeq.rs":"2987e046c90d948b6c7d7ddba52d10c8b7520d71dc0a50dbe7665de128d7410e","src/length.rs":"d7c6369f2fe2a17c845b57749bd48c471159f0571a7314d3bf90737d53f697d3","src/lib.rs":"e2e621f05304278d020429d0349acf7a4e7c7a9a72bd23fc0e55680267472ee9","src/macros.rs":"b63dabdb52df84ea170dc1dab5fe8d7a78c054562d1566bab416124708d2d7af","src/matrix2d.rs":"2361338f59813adf4eebaab76e4dd82be0fbfb9ff2461da8dd9ac9d43583b322","src/matrix4d.rs":"b8547bed6108b037192021c97169c00ad456120b849e9b7ac7bec40363edaec1","src/num.rs":"62286aa642ce3afa7ebd950f50bf2197d8722907f2e23a2e2ea6690484d8b250","src/point.rs":"53f3c9018c822e0a6dc5018005e153775479f41fe55c082d0be10f331fda773f","src/rect.rs":"db62b3af8939529509ae21b3bf6ae498d73a95b4ff3a6eba4db614be08e95f8b","src/scale_factor.rs":"df6dbd1f0f9f63210b92809f84a383dad982a74f09789cf22c7d8f9b62199d39","src/side_offsets.rs":"f85526a421ffda63ff01a3478d4162c8717eef68e942acfa2fd9a1adee02ebb2","src/size.rs":"19d1c08f678d793c6eff49a44f69e5b7179e574aa9b81fb4e73210733af38718","src/trig.rs":"6b207980052d13c625272f2a70a22f7741b59513c2a4882385926f497c763a63"},"package":"f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"}
|
||||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"118514fd9c4958df0d25584cda4917186c46011569f55ef350530c1ad3fbdb48",".travis.yml":"13d3e5a7bf83b04c8e8cfa14f0297bd8366d68391d977dd547f64707dffc275a","COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"c91c98dc9510ef29a7ce0d7c78294f15cb139c9afecca38e5fda56b0a6984954","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"625bec69c76ce5423fdd05cfe46922b2680ec517f97c5854ce34798d1d8a9541","src/approxeq.rs":"6cf810ad389c73a27141a7a67454ed12d4b01c3c16605b9a7414b389bc0615dd","src/length.rs":"d7c6369f2fe2a17c845b57749bd48c471159f0571a7314d3bf90737d53f697d3","src/lib.rs":"e2e621f05304278d020429d0349acf7a4e7c7a9a72bd23fc0e55680267472ee9","src/macros.rs":"b63dabdb52df84ea170dc1dab5fe8d7a78c054562d1566bab416124708d2d7af","src/matrix2d.rs":"2361338f59813adf4eebaab76e4dd82be0fbfb9ff2461da8dd9ac9d43583b322","src/matrix4d.rs":"b8547bed6108b037192021c97169c00ad456120b849e9b7ac7bec40363edaec1","src/num.rs":"749b201289fc6663199160a2f9204e17925fd3053f8ab7779e7bfb377ad06227","src/point.rs":"dbf12a3ad35dc2502b7f2637856d8ee40f5a96e37ed00f3ee3272bf5752c060c","src/rect.rs":"0a255046dd11a6093d9a77e327e1df31802808536b4d87e4e3b80ff6b208de0f","src/scale_factor.rs":"df6dbd1f0f9f63210b92809f84a383dad982a74f09789cf22c7d8f9b62199d39","src/side_offsets.rs":"f85526a421ffda63ff01a3478d4162c8717eef68e942acfa2fd9a1adee02ebb2","src/size.rs":"ae1b647e300600b50a21dba8c1d915801782ebae82baeb5e49017e6d68a49b28","src/trig.rs":"ef290927af252ca90a29ba9f17158b591ed591604e66cb9df045dd47b9cfdca5"},"package":"6083f113c422ff9cd855a1cf6cc8ec0903606c0eb43a0c6a0ced3bdc9731e4c1"}
|
|
@ -1,17 +1,19 @@
|
|||
[package]
|
||||
name = "euclid"
|
||||
version = "0.11.3"
|
||||
version = "0.13.0"
|
||||
authors = ["The Servo Project Developers"]
|
||||
description = "Geometry primitives"
|
||||
documentation = "https://docs.rs/euclid/"
|
||||
repository = "https://github.com/servo/euclid"
|
||||
keywords = ["matrix", "vector", "linear-algebra", "geometry"]
|
||||
categories = ["science"]
|
||||
license = "MIT / Apache-2.0"
|
||||
|
||||
[features]
|
||||
unstable = []
|
||||
|
||||
[dependencies]
|
||||
heapsize = "0.3"
|
||||
heapsize = "0.4"
|
||||
rustc-serialize = "0.3.2"
|
||||
num-traits = {version = "0.1.32", default-features = false}
|
||||
log = "0.3.1"
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# euclid
|
||||
|
||||
This is a small library for geometric types.
|
||||
This is a small library for geometric types with a focus on 2d graphics and
|
||||
layout.
|
||||
|
||||
[Documentation](https://docs.rs/euclid/)
|
||||
* [Documentation](https://docs.rs/euclid/)
|
||||
* [Release notes](https://github.com/servo/euclid/releases)
|
||||
* [crates.io](https://crates.io/crates/euclid)
|
||||
|
|
|
@ -15,33 +15,22 @@ pub trait ApproxEq<Eps> {
|
|||
fn approx_eq_eps(&self, other: &Self, approx_epsilon: &Eps) -> bool;
|
||||
}
|
||||
|
||||
impl ApproxEq<f32> for f32 {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> f32 { 1.0e-6 }
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &f32) -> bool {
|
||||
self.approx_eq_eps(other, &1.0e-6)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &f32, approx_epsilon: &f32) -> bool {
|
||||
(*self - *other).abs() < *approx_epsilon
|
||||
}
|
||||
macro_rules! approx_eq {
|
||||
($ty:ty, $eps:expr) => (
|
||||
impl ApproxEq<$ty> for $ty {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> $ty { $eps }
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &$ty) -> bool {
|
||||
self.approx_eq_eps(other, &$eps)
|
||||
}
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &$ty, approx_epsilon: &$ty) -> bool {
|
||||
(*self - *other).abs() < *approx_epsilon
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
impl ApproxEq<f64> for f64 {
|
||||
#[inline]
|
||||
fn approx_epsilon() -> f64 { 1.0e-6 }
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &f64) -> bool {
|
||||
self.approx_eq_eps(other, &1.0e-6)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &f64, approx_epsilon: &f64) -> bool {
|
||||
(*self - *other).abs() < *approx_epsilon
|
||||
}
|
||||
}
|
||||
approx_eq!(f32, 1.0e-6);
|
||||
approx_eq!(f64, 1.0e-6);
|
||||
|
|
|
@ -27,40 +27,51 @@ impl<T: num_traits::One> One for T {
|
|||
fn one() -> T { num_traits::One::one() }
|
||||
}
|
||||
|
||||
|
||||
pub trait Round : Copy { fn round(self) -> Self; }
|
||||
pub trait Floor : Copy { fn floor(self) -> Self; }
|
||||
pub trait Ceil : Copy { fn ceil(self) -> Self; }
|
||||
|
||||
impl Round for f32 { fn round(self) -> Self { self.round() } }
|
||||
impl Round for f64 { fn round(self) -> Self { self.round() } }
|
||||
impl Round for i16 { fn round(self) -> Self { self } }
|
||||
impl Round for u16 { fn round(self) -> Self { self } }
|
||||
impl Round for i32 { fn round(self) -> Self { self } }
|
||||
impl Round for i64 { fn round(self) -> Self { self } }
|
||||
impl Round for u32 { fn round(self) -> Self { self } }
|
||||
impl Round for u64 { fn round(self) -> Self { self } }
|
||||
impl Round for usize { fn round(self) -> Self { self } }
|
||||
impl Round for isize { fn round(self) -> Self { self } }
|
||||
|
||||
impl Floor for f32 { fn floor(self) -> Self { self.floor() } }
|
||||
impl Floor for f64 { fn floor(self) -> Self { self.floor() } }
|
||||
impl Floor for i16 { fn floor(self) -> Self { self } }
|
||||
impl Floor for u16 { fn floor(self) -> Self { self } }
|
||||
impl Floor for i32 { fn floor(self) -> Self { self } }
|
||||
impl Floor for i64 { fn floor(self) -> Self { self } }
|
||||
impl Floor for u32 { fn floor(self) -> Self { self } }
|
||||
impl Floor for u64 { fn floor(self) -> Self { self } }
|
||||
impl Floor for usize { fn floor(self) -> Self { self } }
|
||||
impl Floor for isize { fn floor(self) -> Self { self } }
|
||||
|
||||
impl Ceil for f32 { fn ceil(self) -> Self { self.ceil() } }
|
||||
impl Ceil for f64 { fn ceil(self) -> Self { self.ceil() } }
|
||||
impl Ceil for i16 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for u16 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for i32 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for i64 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for u32 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for u64 { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for usize { fn ceil(self) -> Self { self } }
|
||||
impl Ceil for isize { fn ceil(self) -> Self { self } }
|
||||
macro_rules! num_int {
|
||||
($ty:ty) => (
|
||||
impl Round for $ty {
|
||||
#[inline]
|
||||
fn round(self) -> $ty { self }
|
||||
}
|
||||
impl Floor for $ty {
|
||||
#[inline]
|
||||
fn floor(self) -> $ty { self }
|
||||
}
|
||||
impl Ceil for $ty {
|
||||
#[inline]
|
||||
fn ceil(self) -> $ty { self }
|
||||
}
|
||||
)
|
||||
}
|
||||
macro_rules! num_float {
|
||||
($ty:ty) => (
|
||||
impl Round for $ty {
|
||||
#[inline]
|
||||
fn round(self) -> $ty { self.round() }
|
||||
}
|
||||
impl Floor for $ty {
|
||||
#[inline]
|
||||
fn floor(self) -> $ty { self.floor() }
|
||||
}
|
||||
impl Ceil for $ty {
|
||||
#[inline]
|
||||
fn ceil(self) -> $ty { self.ceil() }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
num_int!(i16);
|
||||
num_int!(u16);
|
||||
num_int!(i32);
|
||||
num_int!(u32);
|
||||
num_int!(i64);
|
||||
num_int!(u64);
|
||||
num_int!(isize);
|
||||
num_int!(usize);
|
||||
num_float!(f32);
|
||||
num_float!(f64);
|
||||
|
|
|
@ -254,7 +254,7 @@ impl<T: NumCast + Copy, U> TypedPoint2D<T, U> {
|
|||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedPoint2D<usize, U> {
|
||||
pub fn to_usize(&self) -> TypedPoint2D<usize, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
|
@ -517,7 +517,7 @@ impl<T: NumCast + Copy, U> TypedPoint3D<T, U> {
|
|||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedPoint3D<usize, U> {
|
||||
pub fn to_usize(&self) -> TypedPoint3D<usize, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
|
@ -756,7 +756,7 @@ impl<T: NumCast + Copy, U> TypedPoint4D<T, U> {
|
|||
/// When casting from floating point points, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedPoint4D<usize, U> {
|
||||
pub fn to_usize(&self) -> TypedPoint4D<usize, U> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ impl<T: NumCast + Copy, Unit> TypedRect<T, Unit> {
|
|||
/// When casting from floating point rectangles, it is worth considering whether
|
||||
/// to `round()`, `round_in()` or `round_out()` before the cast in order to
|
||||
/// obtain the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedRect<usize, Unit> {
|
||||
pub fn to_usize(&self) -> TypedRect<usize, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ impl<T: NumCast + Copy, Unit> TypedSize2D<T, Unit> {
|
|||
/// When casting from floating point sizes, it is worth considering whether
|
||||
/// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
|
||||
/// the desired conversion behavior.
|
||||
pub fn to_uint(&self) -> TypedSize2D<usize, Unit> {
|
||||
pub fn to_usize(&self) -> TypedSize2D<usize, Unit> {
|
||||
self.cast().unwrap()
|
||||
}
|
||||
|
||||
|
|
|
@ -15,36 +15,18 @@ pub trait Trig {
|
|||
fn tan(self) -> Self;
|
||||
}
|
||||
|
||||
impl Trig for f32 {
|
||||
#[inline]
|
||||
fn sin(self) -> f32 {
|
||||
self.sin()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cos(self) -> f32 {
|
||||
self.cos()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tan(self) -> f32 {
|
||||
self.tan()
|
||||
}
|
||||
macro_rules! trig {
|
||||
($ty:ty) => (
|
||||
impl Trig for $ty {
|
||||
#[inline]
|
||||
fn sin(self) -> $ty { self.sin() }
|
||||
#[inline]
|
||||
fn cos(self) -> $ty { self.cos() }
|
||||
#[inline]
|
||||
fn tan(self) -> $ty { self.tan() }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl Trig for f64 {
|
||||
#[inline]
|
||||
fn sin(self) -> f64 {
|
||||
self.sin()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cos(self) -> f64 {
|
||||
self.cos()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tan(self) -> f64 {
|
||||
self.tan()
|
||||
}
|
||||
}
|
||||
trig!(f32);
|
||||
trig!(f64);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"97503c8cf1fc53fd41e237662402477c0ab257225d25fe21470494a0b1bbec3c",".travis.yml":"1108708721703f4562646e1e7c6f6c924fa997835714bcc6a3ff8a58382134f1","Cargo.toml":"723e5918946fdb518ed1ad3e03ae9104b980cbe85bbee1989dc4197570ba6d73","README.md":"9a38b16bccde5db28c34d79134f02d2cdcbbab224b9a68ace93c5b85b5ef38f2","appveyor.yml":"130e820ab60abf8d08f3a91d4b0158e6a581c180385e12850113adb362eb158c","build.rs":"e13e88ed285a829256d3c6987563a663c37e335457d090125a3e19b1a97fec8e","src/lib.rs":"ab4e0a2e6d0ac700df5dbb7a2c83542cb82c94d4e46c632a4114fec93d6aba0a","tests/tests.rs":"f642da7b54b6cde55cf25fe84b2e6b27356d26b351d42a38e944b93e0c1fa24f"},"package":"5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"}
|
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
Cargo.lock
|
||||
*.swp
|
|
@ -0,0 +1,19 @@
|
|||
language: rust
|
||||
rust:
|
||||
- 1.8.0
|
||||
- nightly
|
||||
- beta
|
||||
- stable
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
notifications:
|
||||
webhooks: http://build.servo.org:54856/travis
|
||||
|
||||
script:
|
||||
- cargo test
|
||||
- "[ $TRAVIS_RUST_VERSION != nightly ] || cargo test --features unstable"
|
||||
- "[ $TRAVIS_RUST_VERSION != nightly ] || cargo test --manifest-path derive/Cargo.toml"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "heapsize"
|
||||
version = "0.3.8"
|
||||
authors = [ "The Servo Project Developers" ]
|
||||
description = "Infrastructure for measuring the total runtime size of an object on the heap"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/servo/heapsize"
|
||||
build = "build.rs"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
kernel32-sys = "0.2.1"
|
||||
|
||||
[features]
|
||||
unstable = []
|
|
@ -0,0 +1,5 @@
|
|||
# heapsize
|
||||
|
||||
In support of measuring heap allocations in Rust programs.
|
||||
|
||||
[API Documentation](https://doc.servo.org/heapsize/)
|
|
@ -0,0 +1,23 @@
|
|||
environment:
|
||||
matrix:
|
||||
- FEATURES: ""
|
||||
- FEATURES: "unstable"
|
||||
|
||||
platform:
|
||||
- i686-pc-windows-gnu
|
||||
- i686-pc-windows-msvc
|
||||
- x86_64-pc-windows-gnu
|
||||
- x86_64-pc-windows-msvc
|
||||
|
||||
install:
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:PLATFORM}.exe"
|
||||
- rust-nightly-%PLATFORM%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build_script:
|
||||
- cargo build --verbose --features "%FEATURES%"
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose --features "%FEATURES%"
|
|
@ -0,0 +1,37 @@
|
|||
use std::env::var;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
fn main() {
|
||||
let verbose = Command::new(var("RUSTC").unwrap_or("rustc".into()))
|
||||
.arg("--version")
|
||||
.arg("--verbose")
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout;
|
||||
let verbose = str::from_utf8(&verbose).unwrap();
|
||||
let mut commit_date = None;
|
||||
let mut release = None;
|
||||
for line in verbose.lines() {
|
||||
let mut parts = line.split(':');
|
||||
match parts.next().unwrap().trim() {
|
||||
"commit-date" => commit_date = Some(parts.next().unwrap().trim()),
|
||||
"release" => release = Some(parts.next().unwrap().trim()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let version = release.unwrap().split('-').next().unwrap();;
|
||||
let mut version_components = version.split('.').map(|s| s.parse::<u32>().unwrap());
|
||||
let version = (
|
||||
version_components.next().unwrap(),
|
||||
version_components.next().unwrap(),
|
||||
version_components.next().unwrap(),
|
||||
// "unknown" sorts after "2016-02-14", which is what we want to defaut to unprefixed
|
||||
// https://github.com/servo/heapsize/pull/44#issuecomment-187935883
|
||||
commit_date.unwrap()
|
||||
);
|
||||
assert_eq!(version_components.next(), None);
|
||||
if version < (1, 8, 0, "2016-02-14") {
|
||||
println!("cargo:rustc-cfg=prefixed_jemalloc");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Data structure measurement.
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate kernel32;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use kernel32::{GetProcessHeap, HeapSize, HeapValidate};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{BTreeMap, HashSet, HashMap, LinkedList, VecDeque};
|
||||
use std::hash::BuildHasher;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Get the size of a heap block.
|
||||
///
|
||||
/// Ideally Rust would expose a function like this in std::rt::heap.
|
||||
///
|
||||
/// `unsafe` because the caller must ensure that the pointer is from jemalloc.
|
||||
/// FIXME: This probably interacts badly with custom allocators:
|
||||
/// https://doc.rust-lang.org/book/custom-allocators.html
|
||||
pub unsafe fn heap_size_of(ptr: *const c_void) -> usize {
|
||||
if ptr == 0x01 as *const c_void {
|
||||
0
|
||||
} else {
|
||||
heap_size_of_impl(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
unsafe fn heap_size_of_impl(ptr: *const c_void) -> usize {
|
||||
// The C prototype is `je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr)`. On some
|
||||
// platforms `JEMALLOC_USABLE_SIZE_CONST` is `const` and on some it is empty. But in practice
|
||||
// this function doesn't modify the contents of the block that `ptr` points to, so we use
|
||||
// `*const c_void` here.
|
||||
extern "C" {
|
||||
#[cfg_attr(any(prefixed_jemalloc, target_os = "macos", target_os = "android"), link_name = "je_malloc_usable_size")]
|
||||
fn malloc_usable_size(ptr: *const c_void) -> usize;
|
||||
}
|
||||
malloc_usable_size(ptr)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub unsafe fn heap_size_of_impl(mut ptr: *const c_void) -> usize {
|
||||
let heap = GetProcessHeap();
|
||||
|
||||
if HeapValidate(heap, 0, ptr) == 0 {
|
||||
ptr = *(ptr as *const *const c_void).offset(-1);
|
||||
}
|
||||
|
||||
HeapSize(heap, 0, ptr) as usize
|
||||
}
|
||||
|
||||
// The simplest trait for measuring the size of heap data structures. More complex traits that
|
||||
// return multiple measurements -- e.g. measure text separately from images -- are also possible,
|
||||
// and should be used when appropriate.
|
||||
//
|
||||
pub trait HeapSizeOf {
|
||||
/// Measure the size of any heap-allocated structures that hang off this value, but not the
|
||||
/// space taken up by the value itself (i.e. what size_of::<T> measures, more or less); that
|
||||
/// space is handled by the implementation of HeapSizeOf for Box<T> below.
|
||||
fn heap_size_of_children(&self) -> usize;
|
||||
}
|
||||
|
||||
// There are two possible ways to measure the size of `self` when it's on the heap: compute it
|
||||
// (with `::std::rt::heap::usable_size(::std::mem::size_of::<T>(), 0)`) or measure it directly
|
||||
// using the heap allocator (with `heap_size_of`). We do the latter, for the following reasons.
|
||||
//
|
||||
// * The heap allocator is the true authority for the sizes of heap blocks; its measurement is
|
||||
// guaranteed to be correct. In comparison, size computations are error-prone. (For example, the
|
||||
// `rt::heap::usable_size` function used in some of Rust's non-default allocator implementations
|
||||
// underestimate the true usable size of heap blocks, which is safe in general but would cause
|
||||
// under-measurement here.)
|
||||
//
|
||||
// * If we measure something that isn't a heap block, we'll get a crash. This keeps us honest,
|
||||
// which is important because unsafe code is involved and this can be gotten wrong.
|
||||
//
|
||||
// However, in the best case, the two approaches should give the same results.
|
||||
//
|
||||
impl<T: HeapSizeOf + ?Sized> HeapSizeOf for Box<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
// Measure size of `self`.
|
||||
unsafe {
|
||||
heap_size_of(&**self as *const T as *const c_void) + (**self).heap_size_of_children()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for [T] {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.iter().fold(0, |size, item| size + item.heap_size_of_children())
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for String {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
unsafe {
|
||||
heap_size_of(self.as_ptr() as *const c_void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> HeapSizeOf for &'a T {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for Option<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match *self {
|
||||
None => 0,
|
||||
Some(ref x) => x.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf, E: HeapSizeOf> HeapSizeOf for Result<T, E> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match *self {
|
||||
Ok(ref x) => x.heap_size_of_children(),
|
||||
Err(ref e) => e.heap_size_of_children(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B: ?Sized + ToOwned> HeapSizeOf for Cow<'a, B> where B::Owned: HeapSizeOf {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match *self {
|
||||
Cow::Borrowed(_) => 0,
|
||||
Cow::Owned(ref b) => b.heap_size_of_children(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for () {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2> HeapSizeOf for (T1, T2)
|
||||
where T1: HeapSizeOf, T2 :HeapSizeOf
|
||||
{
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.0.heap_size_of_children() +
|
||||
self.1.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3> HeapSizeOf for (T1, T2, T3)
|
||||
where T1: HeapSizeOf, T2 :HeapSizeOf, T3: HeapSizeOf
|
||||
{
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.0.heap_size_of_children() +
|
||||
self.1.heap_size_of_children() +
|
||||
self.2.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, T4> HeapSizeOf for (T1, T2, T3, T4)
|
||||
where T1: HeapSizeOf, T2 :HeapSizeOf, T3: HeapSizeOf, T4: HeapSizeOf
|
||||
{
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.0.heap_size_of_children() +
|
||||
self.1.heap_size_of_children() +
|
||||
self.2.heap_size_of_children() +
|
||||
self.3.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, T4, T5> HeapSizeOf for (T1, T2, T3, T4, T5)
|
||||
where T1: HeapSizeOf, T2 :HeapSizeOf, T3: HeapSizeOf, T4: HeapSizeOf, T5: HeapSizeOf
|
||||
{
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.0.heap_size_of_children() +
|
||||
self.1.heap_size_of_children() +
|
||||
self.2.heap_size_of_children() +
|
||||
self.3.heap_size_of_children() +
|
||||
self.4.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for Arc<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
(**self).heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for RefCell<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.borrow().heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf + Copy> HeapSizeOf for Cell<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.get().heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for Vec<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.iter().fold(
|
||||
unsafe { heap_size_of(self.as_ptr() as *const c_void) },
|
||||
|n, elem| n + elem.heap_size_of_children())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for VecDeque<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.iter().fold(
|
||||
// FIXME: get the buffer pointer for heap_size_of(), capacity() is a lower bound:
|
||||
self.capacity() * size_of::<T>(),
|
||||
|n, elem| n + elem.heap_size_of_children())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> HeapSizeOf for Vec<Rc<T>> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
// The fate of measuring Rc<T> is still undecided, but we still want to measure
|
||||
// the space used for storing them.
|
||||
unsafe {
|
||||
heap_size_of(self.as_ptr() as *const c_void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf, S> HeapSizeOf for HashSet<T, S>
|
||||
where T: Eq + Hash, S: BuildHasher {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
//TODO(#6908) measure actual bucket memory usage instead of approximating
|
||||
let size = self.capacity() * (size_of::<T>() + size_of::<usize>());
|
||||
self.iter().fold(size, |n, value| {
|
||||
n + value.heap_size_of_children()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: HeapSizeOf, V: HeapSizeOf, S> HeapSizeOf for HashMap<K, V, S>
|
||||
where K: Eq + Hash, S: BuildHasher {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
//TODO(#6908) measure actual bucket memory usage instead of approximating
|
||||
let size = self.capacity() * (size_of::<V>() + size_of::<K>() + size_of::<usize>());
|
||||
self.iter().fold(size, |n, (key, value)| {
|
||||
n + key.heap_size_of_children() + value.heap_size_of_children()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// PhantomData is always 0.
|
||||
impl<T> HeapSizeOf for PhantomData<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// A linked list has an overhead of two words per item.
|
||||
impl<T: HeapSizeOf> HeapSizeOf for LinkedList<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
let mut size = 0;
|
||||
for item in self {
|
||||
size += 2 * size_of::<usize>() + size_of::<T>() + item.heap_size_of_children();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Overhead for the BTreeMap nodes is not accounted for.
|
||||
impl<K: HeapSizeOf, V: HeapSizeOf> HeapSizeOf for BTreeMap<K, V> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
let mut size = 0;
|
||||
for (key, value) in self.iter() {
|
||||
size += size_of::<(K, V)>() +
|
||||
key.heap_size_of_children() +
|
||||
value.heap_size_of_children();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
/// For use on types defined in external crates
|
||||
/// with known heap sizes.
|
||||
#[macro_export]
|
||||
macro_rules! known_heap_size(
|
||||
($size:expr, $($ty:ty),+) => (
|
||||
$(
|
||||
impl $crate::HeapSizeOf for $ty {
|
||||
#[inline(always)]
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
$size
|
||||
}
|
||||
}
|
||||
)+
|
||||
);
|
||||
($size: expr, $($ty:ident<$($gen:ident),+>),+) => (
|
||||
$(
|
||||
impl<$($gen: $crate::HeapSizeOf),+> $crate::HeapSizeOf for $ty<$($gen),+> {
|
||||
#[inline(always)]
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
$size
|
||||
}
|
||||
}
|
||||
)+
|
||||
);
|
||||
);
|
||||
|
||||
known_heap_size!(0, char, str);
|
||||
known_heap_size!(0, u8, u16, u32, u64, usize);
|
||||
known_heap_size!(0, i8, i16, i32, i64, isize);
|
||||
known_heap_size!(0, bool, f32, f64);
|
||||
known_heap_size!(0, AtomicBool, AtomicIsize, AtomicUsize);
|
||||
known_heap_size!(0, Ipv4Addr, Ipv6Addr);
|
|
@ -0,0 +1,171 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![cfg_attr(feature= "unstable", feature(alloc, heap_api, repr_simd))]
|
||||
|
||||
extern crate heapsize;
|
||||
|
||||
use heapsize::{HeapSizeOf, heap_size_of};
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub const EMPTY: *mut () = 0x1 as *mut ();
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
mod unstable {
|
||||
extern crate alloc;
|
||||
|
||||
use heapsize::{HeapSizeOf, heap_size_of};
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[repr(C, simd)]
|
||||
struct OverAligned(u64, u64, u64, u64);
|
||||
|
||||
#[test]
|
||||
fn check_empty() {
|
||||
assert_eq!(::EMPTY, alloc::heap::EMPTY);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[test]
|
||||
fn test_alloc() {
|
||||
unsafe {
|
||||
// A 64 byte request is allocated exactly.
|
||||
let x = alloc::heap::allocate(64, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 64);
|
||||
alloc::heap::deallocate(x, 64, 0);
|
||||
|
||||
// A 255 byte request is rounded up to 256 bytes.
|
||||
let x = alloc::heap::allocate(255, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 256);
|
||||
alloc::heap::deallocate(x, 255, 0);
|
||||
|
||||
// A 1MiB request is allocated exactly.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 0);
|
||||
|
||||
// An overaligned 1MiB request is allocated exactly.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 32);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 32);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_alloc() {
|
||||
unsafe {
|
||||
// A 64 byte request is allocated exactly.
|
||||
let x = alloc::heap::allocate(64, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 64);
|
||||
alloc::heap::deallocate(x, 64, 0);
|
||||
|
||||
// A 255 byte request is allocated exactly.
|
||||
let x = alloc::heap::allocate(255, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 255);
|
||||
alloc::heap::deallocate(x, 255, 0);
|
||||
|
||||
// A 1MiB request is allocated exactly.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 0);
|
||||
|
||||
// An overaligned 1MiB request is over-allocated.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 32);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024 + 32);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 32);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[test]
|
||||
fn test_simd() {
|
||||
let x = Box::new(OverAligned(0, 0, 0, 0));
|
||||
assert_eq!(unsafe { heap_size_of(&*x as *const _ as *const c_void) }, 32);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_simd() {
|
||||
let x = Box::new(OverAligned(0, 0, 0, 0));
|
||||
assert_eq!(unsafe { heap_size_of(&*x as *const _ as *const c_void) }, 32 + 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_str() {
|
||||
let x = "raclette".to_owned().into_boxed_str();
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_heap_size() {
|
||||
|
||||
// Note: jemalloc often rounds up request sizes. However, it does not round up for request
|
||||
// sizes of 8 and higher that are powers of two. We take advantage of knowledge here to make
|
||||
// the sizes of various heap-allocated blocks predictable.
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Start with basic heap block measurement.
|
||||
|
||||
unsafe {
|
||||
// EMPTY is the special non-null address used to represent zero-size allocations.
|
||||
assert_eq!(heap_size_of(EMPTY as *const c_void), 0);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Test HeapSizeOf implementations for various built-in types.
|
||||
|
||||
// Not on the heap; 0 bytes.
|
||||
let x = 0i64;
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
|
||||
// An i64 is 8 bytes.
|
||||
let x = Box::new(0i64);
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
|
||||
// An ascii string with 16 chars is 16 bytes in UTF-8.
|
||||
let string = String::from("0123456789abcdef");
|
||||
assert_eq!(string.heap_size_of_children(), 16);
|
||||
|
||||
let string_ref: (&String, ()) = (&string, ());
|
||||
assert_eq!(string_ref.heap_size_of_children(), 0);
|
||||
|
||||
let slice: &str = &*string;
|
||||
assert_eq!(slice.heap_size_of_children(), 0);
|
||||
|
||||
// Not on the heap.
|
||||
let x: Option<i32> = None;
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
|
||||
// Not on the heap.
|
||||
let x = Some(0i64);
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
|
||||
// The `Some` is not on the heap, but the Box is.
|
||||
let x = Some(Box::new(0i64));
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
|
||||
// Not on the heap.
|
||||
let x = ::std::sync::Arc::new(0i64);
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
|
||||
// The `Arc` is not on the heap, but the Box is.
|
||||
let x = ::std::sync::Arc::new(Box::new(0i64));
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
|
||||
// Zero elements, no heap storage.
|
||||
let x: Vec<i64> = vec![];
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
|
||||
// Four elements, 8 bytes per element.
|
||||
let x = vec![0i64, 1i64, 2i64, 3i64];
|
||||
assert_eq!(x.heap_size_of_children(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_slice() {
|
||||
let x = vec![1i64, 2i64].into_boxed_slice();
|
||||
assert_eq!(x.heap_size_of_children(), 16)
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"97503c8cf1fc53fd41e237662402477c0ab257225d25fe21470494a0b1bbec3c",".travis.yml":"1108708721703f4562646e1e7c6f6c924fa997835714bcc6a3ff8a58382134f1","Cargo.toml":"723e5918946fdb518ed1ad3e03ae9104b980cbe85bbee1989dc4197570ba6d73","README.md":"9a38b16bccde5db28c34d79134f02d2cdcbbab224b9a68ace93c5b85b5ef38f2","appveyor.yml":"130e820ab60abf8d08f3a91d4b0158e6a581c180385e12850113adb362eb158c","build.rs":"e13e88ed285a829256d3c6987563a663c37e335457d090125a3e19b1a97fec8e","src/lib.rs":"ab4e0a2e6d0ac700df5dbb7a2c83542cb82c94d4e46c632a4114fec93d6aba0a","tests/tests.rs":"f642da7b54b6cde55cf25fe84b2e6b27356d26b351d42a38e944b93e0c1fa24f"},"package":"5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"}
|
||||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"97503c8cf1fc53fd41e237662402477c0ab257225d25fe21470494a0b1bbec3c",".travis.yml":"ff4b4eeea4c3d6636633496f884b85e83e3613ad2bb84358b357f0cb8b8b1618","Cargo.toml":"f3a8db502210ebefe0565223738d41e1f6327bc283545789bea68fc93a599393","README.md":"9a38b16bccde5db28c34d79134f02d2cdcbbab224b9a68ace93c5b85b5ef38f2","appveyor.yml":"130e820ab60abf8d08f3a91d4b0158e6a581c180385e12850113adb362eb158c","build.rs":"e13e88ed285a829256d3c6987563a663c37e335457d090125a3e19b1a97fec8e","src/lib.rs":"024183eb6acfd9ebaa0b4bdc31aecd39dcb8bf92ab22228921f154b450b628a3","tests/tests.rs":"28ec35b89867f04be2b1a43116ee82b6f45e34efa53938e29c6727ad4da46ead"},"package":"4c7593b1522161003928c959c20a2ca421c68e940d63d75573316a009e48a6d4"}
|
|
@ -15,5 +15,5 @@ notifications:
|
|||
script:
|
||||
- cargo test
|
||||
- "[ $TRAVIS_RUST_VERSION != nightly ] || cargo test --features unstable"
|
||||
- "[ $TRAVIS_RUST_VERSION != nightly ] || cargo test --manifest-path derive/Cargo.toml"
|
||||
- "[[ $TRAVIS_RUST_VERSION != nightly && $TRAVIS_RUST_VERSION != beta ]] || cargo test --manifest-path derive/Cargo.toml"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "heapsize"
|
||||
version = "0.3.8"
|
||||
version = "0.4.0"
|
||||
authors = [ "The Servo Project Developers" ]
|
||||
description = "Infrastructure for measuring the total runtime size of an object on the heap"
|
||||
license = "MPL-2.0"
|
||||
|
@ -12,3 +12,6 @@ kernel32-sys = "0.2.1"
|
|||
|
||||
[features]
|
||||
unstable = []
|
||||
|
||||
# https://github.com/servo/heapsize/issues/74
|
||||
flexible-tests = []
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::collections::{BTreeMap, HashSet, HashMap, LinkedList, VecDeque};
|
|||
use std::hash::BuildHasher;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::mem::{size_of, align_of};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::Arc;
|
||||
|
@ -29,11 +29,11 @@ use std::rc::Rc;
|
|||
/// `unsafe` because the caller must ensure that the pointer is from jemalloc.
|
||||
/// FIXME: This probably interacts badly with custom allocators:
|
||||
/// https://doc.rust-lang.org/book/custom-allocators.html
|
||||
pub unsafe fn heap_size_of(ptr: *const c_void) -> usize {
|
||||
if ptr == 0x01 as *const c_void {
|
||||
pub unsafe fn heap_size_of<T>(ptr: *const T) -> usize {
|
||||
if ptr as usize <= align_of::<T>() {
|
||||
0
|
||||
} else {
|
||||
heap_size_of_impl(ptr)
|
||||
heap_size_of_impl(ptr as *const c_void)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ unsafe fn heap_size_of_impl(ptr: *const c_void) -> usize {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub unsafe fn heap_size_of_impl(mut ptr: *const c_void) -> usize {
|
||||
unsafe fn heap_size_of_impl(mut ptr: *const c_void) -> usize {
|
||||
let heap = GetProcessHeap();
|
||||
|
||||
if HeapValidate(heap, 0, ptr) == 0 {
|
||||
|
@ -105,7 +105,7 @@ impl<T: HeapSizeOf> HeapSizeOf for [T] {
|
|||
impl HeapSizeOf for String {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
unsafe {
|
||||
heap_size_of(self.as_ptr() as *const c_void)
|
||||
heap_size_of(self.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,25 @@ impl<'a, T: ?Sized> HeapSizeOf for &'a T {
|
|||
}
|
||||
}
|
||||
|
||||
// The implementations for *mut T and *const T are designed for use cases like LinkedHashMap where
|
||||
// you have a data structure which internally maintains an e.g. HashMap parameterized with raw
|
||||
// pointers. We want to be able to rely on the standard HeapSizeOf implementation for `HashMap`,
|
||||
// and can handle the contribution of the raw pointers manually.
|
||||
//
|
||||
// These have to return 0 since we don't know if the pointer is pointing to a heap allocation or
|
||||
// even valid memory.
|
||||
impl<T: ?Sized> HeapSizeOf for *mut T {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> HeapSizeOf for *const T {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HeapSizeOf> HeapSizeOf for Option<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
match *self {
|
||||
|
@ -212,7 +231,7 @@ impl<T: HeapSizeOf + Copy> HeapSizeOf for Cell<T> {
|
|||
impl<T: HeapSizeOf> HeapSizeOf for Vec<T> {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.iter().fold(
|
||||
unsafe { heap_size_of(self.as_ptr() as *const c_void) },
|
||||
unsafe { heap_size_of(self.as_ptr()) },
|
||||
|n, elem| n + elem.heap_size_of_children())
|
||||
}
|
||||
}
|
||||
|
@ -231,7 +250,7 @@ impl<T> HeapSizeOf for Vec<Rc<T>> {
|
|||
// The fate of measuring Rc<T> is still undecided, but we still want to measure
|
||||
// the space used for storing them.
|
||||
unsafe {
|
||||
heap_size_of(self.as_ptr() as *const c_void)
|
||||
heap_size_of(self.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,32 @@ extern crate heapsize;
|
|||
use heapsize::{HeapSizeOf, heap_size_of};
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub const EMPTY: *mut () = 0x1 as *mut ();
|
||||
const EMPTY: *mut () = 0x1 as *mut ();
|
||||
|
||||
/// https://github.com/servo/heapsize/issues/74
|
||||
#[cfg(feature = "flexible-tests")]
|
||||
macro_rules! assert_size {
|
||||
($actual: expr, $expected: expr) => {
|
||||
{
|
||||
let actual = $actual;
|
||||
let expected = $expected;
|
||||
assert!(actual >= expected, "expected {:?} >= {:?}", actual, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flexible-tests"))]
|
||||
macro_rules! assert_size {
|
||||
($actual: expr, $expected: expr) => {
|
||||
assert_eq!($actual, $expected)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
mod unstable {
|
||||
extern crate alloc;
|
||||
|
||||
use heapsize::{HeapSizeOf, heap_size_of};
|
||||
use heapsize::heap_size_of;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[repr(C, simd)]
|
||||
|
@ -32,22 +51,22 @@ mod unstable {
|
|||
unsafe {
|
||||
// A 64 byte request is allocated exactly.
|
||||
let x = alloc::heap::allocate(64, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 64);
|
||||
assert_size!(heap_size_of(x as *const c_void), 64);
|
||||
alloc::heap::deallocate(x, 64, 0);
|
||||
|
||||
// A 255 byte request is rounded up to 256 bytes.
|
||||
let x = alloc::heap::allocate(255, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 256);
|
||||
assert_size!(heap_size_of(x as *const c_void), 256);
|
||||
alloc::heap::deallocate(x, 255, 0);
|
||||
|
||||
// A 1MiB request is allocated exactly.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
assert_size!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 0);
|
||||
|
||||
// An overaligned 1MiB request is allocated exactly.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 32);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
assert_size!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 32);
|
||||
}
|
||||
}
|
||||
|
@ -58,22 +77,22 @@ mod unstable {
|
|||
unsafe {
|
||||
// A 64 byte request is allocated exactly.
|
||||
let x = alloc::heap::allocate(64, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 64);
|
||||
assert_size!(heap_size_of(x as *const c_void), 64);
|
||||
alloc::heap::deallocate(x, 64, 0);
|
||||
|
||||
// A 255 byte request is allocated exactly.
|
||||
let x = alloc::heap::allocate(255, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 255);
|
||||
assert_size!(heap_size_of(x as *const c_void), 255);
|
||||
alloc::heap::deallocate(x, 255, 0);
|
||||
|
||||
// A 1MiB request is allocated exactly.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 0);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
assert_size!(heap_size_of(x as *const c_void), 1024 * 1024);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 0);
|
||||
|
||||
// An overaligned 1MiB request is over-allocated.
|
||||
let x = alloc::heap::allocate(1024 * 1024, 32);
|
||||
assert_eq!(heap_size_of(x as *const c_void), 1024 * 1024 + 32);
|
||||
assert_size!(heap_size_of(x as *const c_void), 1024 * 1024 + 32);
|
||||
alloc::heap::deallocate(x, 1024 * 1024, 32);
|
||||
}
|
||||
}
|
||||
|
@ -82,21 +101,21 @@ mod unstable {
|
|||
#[test]
|
||||
fn test_simd() {
|
||||
let x = Box::new(OverAligned(0, 0, 0, 0));
|
||||
assert_eq!(unsafe { heap_size_of(&*x as *const _ as *const c_void) }, 32);
|
||||
assert_size!(unsafe { heap_size_of(&*x as *const _ as *const c_void) }, 32);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_simd() {
|
||||
let x = Box::new(OverAligned(0, 0, 0, 0));
|
||||
assert_eq!(unsafe { heap_size_of(&*x as *const _ as *const c_void) }, 32 + 32);
|
||||
assert_size!(unsafe { heap_size_of(&*x as *const _ as *const c_void) }, 32 + 32);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_str() {
|
||||
let x = "raclette".to_owned().into_boxed_str();
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
}
|
||||
#[test]
|
||||
fn test_boxed_str() {
|
||||
let x = "raclette".to_owned().into_boxed_str();
|
||||
assert_size!(x.heap_size_of_children(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -111,7 +130,7 @@ fn test_heap_size() {
|
|||
|
||||
unsafe {
|
||||
// EMPTY is the special non-null address used to represent zero-size allocations.
|
||||
assert_eq!(heap_size_of(EMPTY as *const c_void), 0);
|
||||
assert_size!(heap_size_of(EMPTY as *const c_void), 0);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
@ -119,53 +138,53 @@ fn test_heap_size() {
|
|||
|
||||
// Not on the heap; 0 bytes.
|
||||
let x = 0i64;
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
assert_size!(x.heap_size_of_children(), 0);
|
||||
|
||||
// An i64 is 8 bytes.
|
||||
let x = Box::new(0i64);
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
assert_size!(x.heap_size_of_children(), 8);
|
||||
|
||||
// An ascii string with 16 chars is 16 bytes in UTF-8.
|
||||
let string = String::from("0123456789abcdef");
|
||||
assert_eq!(string.heap_size_of_children(), 16);
|
||||
assert_size!(string.heap_size_of_children(), 16);
|
||||
|
||||
let string_ref: (&String, ()) = (&string, ());
|
||||
assert_eq!(string_ref.heap_size_of_children(), 0);
|
||||
assert_size!(string_ref.heap_size_of_children(), 0);
|
||||
|
||||
let slice: &str = &*string;
|
||||
assert_eq!(slice.heap_size_of_children(), 0);
|
||||
assert_size!(slice.heap_size_of_children(), 0);
|
||||
|
||||
// Not on the heap.
|
||||
let x: Option<i32> = None;
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
assert_size!(x.heap_size_of_children(), 0);
|
||||
|
||||
// Not on the heap.
|
||||
let x = Some(0i64);
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
assert_size!(x.heap_size_of_children(), 0);
|
||||
|
||||
// The `Some` is not on the heap, but the Box is.
|
||||
let x = Some(Box::new(0i64));
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
assert_size!(x.heap_size_of_children(), 8);
|
||||
|
||||
// Not on the heap.
|
||||
let x = ::std::sync::Arc::new(0i64);
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
assert_size!(x.heap_size_of_children(), 0);
|
||||
|
||||
// The `Arc` is not on the heap, but the Box is.
|
||||
let x = ::std::sync::Arc::new(Box::new(0i64));
|
||||
assert_eq!(x.heap_size_of_children(), 8);
|
||||
assert_size!(x.heap_size_of_children(), 8);
|
||||
|
||||
// Zero elements, no heap storage.
|
||||
let x: Vec<i64> = vec![];
|
||||
assert_eq!(x.heap_size_of_children(), 0);
|
||||
assert_size!(x.heap_size_of_children(), 0);
|
||||
|
||||
// Four elements, 8 bytes per element.
|
||||
let x = vec![0i64, 1i64, 2i64, 3i64];
|
||||
assert_eq!(x.heap_size_of_children(), 32);
|
||||
assert_size!(x.heap_size_of_children(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_slice() {
|
||||
let x = vec![1i64, 2i64].into_boxed_slice();
|
||||
assert_eq!(x.heap_size_of_children(), 16)
|
||||
assert_size!(x.heap_size_of_children(), 16)
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"f9b1ca6ae27d1c18215265024629a8960c31379f206d9ed20f64e0b2dcf79805",".travis.yml":"b76d49f66f842c652d40825c67791352364a6b6bbb7d8d1009f2ac79eb413e66","Cargo.toml":"3100d538a6cd25f84ae7d502f37ec09603f4a7fb902e46583df5bdbb50b73538","LICENSE":"b946744aeda89b467929585fe8eeb5461847695220c1b168fb375d8abd4ea3d0","README.md":"62f99334c17b451342fcea70eb1cc27b26612616b7c1a58fab50dd493f766f32","benches/split.rs":"49befe22321f34280106fdea53d93644b7757873407376247f86f9d55d09b4ab","src/bsp.rs":"83f1a84b4dda5668727eb27070f22676d1f77ab9eaff0c777b32651f75ecf5b6","src/lib.rs":"c6bdf6ac6db519b79b3dbf0d4805bbba3e761e0d5cf19b4ae5388a5b93ff7454","src/naive.rs":"c7e50de094d24b609f03e3dc9599bb040a6baef84bce93ffab7af7f049fb805b","tests/main.rs":"915d915c5ca82befef82f1604cc974b072238a8d69043341589d8dd569d412d3","tests/split.rs":"a4681a788f9a9a515d4084d97ba33406a54bc0725711ade9fc955348d1703368"},"package":"8b3624c9e5e728dcc6347bde5762406b0f0707bea527d585e8f7b6ac44fdd33a"}
|
||||
{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"f9b1ca6ae27d1c18215265024629a8960c31379f206d9ed20f64e0b2dcf79805",".travis.yml":"b76d49f66f842c652d40825c67791352364a6b6bbb7d8d1009f2ac79eb413e66","Cargo.toml":"6a8c18281f4854b2f184e335d2efb7702ed920f3e66adbe84ce2013215215068","LICENSE":"b946744aeda89b467929585fe8eeb5461847695220c1b168fb375d8abd4ea3d0","README.md":"62f99334c17b451342fcea70eb1cc27b26612616b7c1a58fab50dd493f766f32","benches/split.rs":"49befe22321f34280106fdea53d93644b7757873407376247f86f9d55d09b4ab","src/bsp.rs":"1bc961e97b47f6d918384858310c60a20f9490e11404a89f379a2ad6c5705071","src/lib.rs":"c7f52a46d9ebdb9c1346b39312110aaba75821297e5f446c81a8a25706d850f5","src/naive.rs":"c7e50de094d24b609f03e3dc9599bb040a6baef84bce93ffab7af7f049fb805b","tests/main.rs":"915d915c5ca82befef82f1604cc974b072238a8d69043341589d8dd569d412d3","tests/split.rs":"a4681a788f9a9a515d4084d97ba33406a54bc0725711ade9fc955348d1703368"},"package":"f00d5b0bef85e7e218329cde2f9b75784967c62c0cc9b7faa491d81c2d35eb2a"}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "plane-split"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
description = "Plane splitting"
|
||||
authors = ["Dzmitry Malyshau <kvark@mozilla.com>"]
|
||||
license = "MPL-2.0"
|
||||
|
@ -10,6 +10,6 @@ documentation = "https://docs.rs/plane-split"
|
|||
|
||||
[dependencies]
|
||||
binary-space-partition = "0.1.2"
|
||||
euclid = "0.11.2"
|
||||
euclid = "0.13"
|
||||
log = "0.3"
|
||||
num-traits = {version = "0.1.37", default-features = false}
|
||||
|
|
|
@ -19,11 +19,11 @@ impl<T, U> Plane for Polygon<T, U> where
|
|||
|
||||
match self.intersect(&plane) {
|
||||
Intersection::Coplanar if dist.approx_eq(&T::zero()) => {
|
||||
debug!("\t\tcoplanar and matching");
|
||||
debug!("\t\tCoplanar and matching");
|
||||
PlaneCut::Sibling(plane)
|
||||
}
|
||||
Intersection::Coplanar | Intersection::Outside => {
|
||||
debug!("\t\tcoplanar at {:?}", dist);
|
||||
debug!("\t\tCoplanar at {:?}", dist);
|
||||
if dist > T::zero() {
|
||||
PlaneCut::Cut {
|
||||
front: vec![plane],
|
||||
|
@ -48,7 +48,7 @@ impl<T, U> Plane for Polygon<T, U> where
|
|||
back.push(sub)
|
||||
}
|
||||
}
|
||||
debug!("\t\tcut across {:?} by {} in front and {} in back",
|
||||
debug!("\t\tCut across {:?} by {} in front and {} in back",
|
||||
line, front.len(), back.len());
|
||||
|
||||
PlaneCut::Cut {
|
||||
|
|
|
@ -298,20 +298,20 @@ impl<T, U> Polygon<T, U> where
|
|||
pub fn intersect(&self, other: &Self) -> Intersection<Line<T, U>> {
|
||||
if self.are_outside(&other.points) || other.are_outside(&self.points) {
|
||||
// one is completely outside the other
|
||||
debug!("\t\toutside");
|
||||
debug!("\t\tOutside");
|
||||
return Intersection::Outside
|
||||
}
|
||||
let cross_dir = self.normal.cross(other.normal);
|
||||
if cross_dir.dot(cross_dir) < T::approx_epsilon() {
|
||||
// polygons are co-planar
|
||||
debug!("\t\tcoplanar");
|
||||
debug!("\t\tCoplanar");
|
||||
return Intersection::Coplanar
|
||||
}
|
||||
let self_proj = self.project_on(&cross_dir);
|
||||
let other_proj = other.project_on(&cross_dir);
|
||||
if !self_proj.intersect(&other_proj) {
|
||||
// projections on the line don't intersect
|
||||
debug!("\t\tprojection outside");
|
||||
debug!("\t\tProjection outside");
|
||||
return Intersection::Outside
|
||||
}
|
||||
// compute any point on the intersection between planes
|
||||
|
|
|
@ -280,6 +280,18 @@ dependencies = [
|
|||
"serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "euclid"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.5"
|
||||
|
@ -376,6 +388,14 @@ dependencies = [
|
|||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapsize"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.1.0"
|
||||
|
@ -613,11 +633,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "plane-split"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -1086,7 +1106,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webrender"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
dependencies = [
|
||||
"app_units 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1096,7 +1116,7 @@ dependencies = [
|
|||
"core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gamma-lut 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1104,11 +1124,11 @@ dependencies = [
|
|||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plane-split 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plane-split 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender_traits 0.39.0",
|
||||
"webrender_traits 0.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1116,15 +1136,15 @@ name = "webrender_bindings"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"app_units 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender 0.39.0",
|
||||
"webrender_traits 0.39.0",
|
||||
"webrender 0.40.0",
|
||||
"webrender_traits 0.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webrender_traits"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
dependencies = [
|
||||
"app_units 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1132,7 +1152,7 @@ dependencies = [
|
|||
"core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1188,6 +1208,7 @@ dependencies = [
|
|||
"checksum dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74114b6b49d6731835da7a28a3642651451e315f7f9b9d04e907e65a45681796"
|
||||
"checksum env_logger 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ed39959122ea027670b704fb70539f4286ddf4a49eefede23bf0b4b2a069ec03"
|
||||
"checksum euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"
|
||||
"checksum euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6083f113c422ff9cd855a1cf6cc8ec0903606c0eb43a0c6a0ced3bdc9731e4c1"
|
||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
||||
"checksum freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fde23272c687e4570aefec06cb71174ec0f5284b725deac4e77ba2665d635faf"
|
||||
"checksum futures 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "55f0008e13fc853f79ea8fc86e931486860d4c4c156cdffb59fa5f7fa833660a"
|
||||
|
@ -1197,6 +1218,7 @@ dependencies = [
|
|||
"checksum gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86944a6a4d7f54507f8ee930192d971f18a7b1da526ff529b7a0d4043935380"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
|
||||
"checksum heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c7593b1522161003928c959c20a2ca421c68e940d63d75573316a009e48a6d4"
|
||||
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
|
||||
"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
|
@ -1224,7 +1246,7 @@ dependencies = [
|
|||
"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
|
||||
"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
|
||||
"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
|
||||
"checksum plane-split 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b3624c9e5e728dcc6347bde5762406b0f0707bea527d585e8f7b6ac44fdd33a"
|
||||
"checksum plane-split 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f00d5b0bef85e7e218329cde2f9b75784967c62c0cc9b7faa491d81c2d35eb2a"
|
||||
"checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
|
||||
"checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
|
||||
"checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3"
|
||||
|
|
|
@ -278,6 +278,18 @@ dependencies = [
|
|||
"serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "euclid"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.5"
|
||||
|
@ -374,6 +386,14 @@ dependencies = [
|
|||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapsize"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.1.0"
|
||||
|
@ -600,11 +620,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "plane-split"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -1073,7 +1093,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webrender"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
dependencies = [
|
||||
"app_units 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1083,7 +1103,7 @@ dependencies = [
|
|||
"core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gamma-lut 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1091,11 +1111,11 @@ dependencies = [
|
|||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plane-split 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plane-split 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender_traits 0.39.0",
|
||||
"webrender_traits 0.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1103,15 +1123,15 @@ name = "webrender_bindings"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"app_units 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webrender 0.39.0",
|
||||
"webrender_traits 0.39.0",
|
||||
"webrender 0.40.0",
|
||||
"webrender_traits 0.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webrender_traits"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
dependencies = [
|
||||
"app_units 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1119,7 +1139,7 @@ dependencies = [
|
|||
"core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1175,6 +1195,7 @@ dependencies = [
|
|||
"checksum dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74114b6b49d6731835da7a28a3642651451e315f7f9b9d04e907e65a45681796"
|
||||
"checksum env_logger 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ed39959122ea027670b704fb70539f4286ddf4a49eefede23bf0b4b2a069ec03"
|
||||
"checksum euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"
|
||||
"checksum euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6083f113c422ff9cd855a1cf6cc8ec0903606c0eb43a0c6a0ced3bdc9731e4c1"
|
||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
||||
"checksum freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fde23272c687e4570aefec06cb71174ec0f5284b725deac4e77ba2665d635faf"
|
||||
"checksum futures 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "55f0008e13fc853f79ea8fc86e931486860d4c4c156cdffb59fa5f7fa833660a"
|
||||
|
@ -1184,6 +1205,7 @@ dependencies = [
|
|||
"checksum gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86944a6a4d7f54507f8ee930192d971f18a7b1da526ff529b7a0d4043935380"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
|
||||
"checksum heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c7593b1522161003928c959c20a2ca421c68e940d63d75573316a009e48a6d4"
|
||||
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
|
||||
"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
|
@ -1211,7 +1233,7 @@ dependencies = [
|
|||
"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
|
||||
"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
|
||||
"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
|
||||
"checksum plane-split 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b3624c9e5e728dcc6347bde5762406b0f0707bea527d585e8f7b6ac44fdd33a"
|
||||
"checksum plane-split 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f00d5b0bef85e7e218329cde2f9b75784967c62c0cc9b7faa491d81c2d35eb2a"
|
||||
"checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
|
||||
"checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
|
||||
"checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3"
|
||||
|
|
Загрузка…
Ссылка в новой задаче