Merge branch 'pf3'

This commit is contained in:
Patrick Walton 2019-04-29 14:23:07 -07:00
commit deecdb12de
328 changed files with 73399 additions and 56007 deletions

20
.gitignore vendored
View File

@ -1,19 +1,10 @@
/font-renderer/target
/partitioner/target
/path-utils/target
/utils/frontend/target
/utils/gamma-lut/target
/demo/client/target
/demo/client/*.html
/demo/client/*.js
/demo/client/src/*.js
/demo/client/src/*.js.map
/demo/client/node_modules
/demo/client/package-lock.json
/demo/server/target
/demo/server/Rocket.toml
.DS_Store
target
.cache
/site/package-lock.json
/site/dist
node_modules
# Editors
*.swp
@ -25,3 +16,6 @@ target
# VSCode
.vscode
*.code-workspace
# Emacs
*~

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
language: rust
addons:
apt:
packages:
- libegl1-mesa-dev
- libgtk-3-dev
- libsdl2-dev
script:
- cd demo/native
- cargo build
- cd ../magicleap
- cargo build

2118
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,17 @@
[workspace]
members = [
"gfx-utils",
"partitioner",
"path-utils",
"demo/server",
"demo/android/rust",
"demo/common",
"demo/magicleap",
"demo/native",
"geometry",
"gl",
"gpu",
"renderer",
"simd",
"svg",
"ui",
"utils/area-lut",
"utils/frontend",
"utils/gamma-lut",
"utils/svg-to-skia"
]
[patch.crates-io]
ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" }

View File

@ -1,69 +1,61 @@
# Pathfinder 2
# Pathfinder 3
Pathfinder 2 is a fast, practical, work in progress GPU-based rasterizer for fonts and vector
graphics using OpenGL and OpenGL ES 2.0+.
Pathfinder 3 is a fast, practical, GPU-based rasterizer for fonts and vector graphics using OpenGL
and OpenGL ES 3.0+.
Please note that Pathfinder is under heavy development and is incomplete in various areas.
The project features:
* Low setup time. Typical glyph outlines can be prepared for GPU rendering in about 5 microseconds
each (typically O(n log n) in the number of vertices), making Pathfinder suitable for dynamic
environments. The setup process is lossless and fully resolution independent; paths need only be
prepared once and can thereafter be rendered at any zoom level without any loss in quality.
Pathfinder can also render outlines without any mesh at all, reducing the setup time to nearly
zero, at the cost of some runtime performance. For static paths such as game assets, the
resulting meshes can be saved to disk to avoid having to generate them at runtime.
* High quality antialiasing. Pathfinder can compute exact fractional trapezoidal area coverage on a
per-pixel basis for the highest-quality antialiasing, provided that either OpenGL 3.0+ or a few
common extensions are available. Supersampling is available as an alternative for 3D scenes
and/or lower-end hardware.
per-pixel basis for the highest-quality antialiasing possible (effectively 256xAA).
* Fast rendering, even at small pixel sizes. Even on lower-end GPUs, Pathfinder typically matches
or exceeds the performance of the best CPU rasterizers. The difference is particularly pronouced
at large sizes, where Pathfinder regularly achieves multi-factor speedups. All shaders have no
loops and minimal branching.
* Fast CPU setup, making full use of parallelism. Pathfinder 3 uses the Rayon library to quickly
perform a CPU tiling prepass to prepare vector scenes for the GPU. This prepass can be pipelined
with the GPU to hide its latency.
* Fast GPU rendering, even at small pixel sizes. Even on lower-end GPUs, Pathfinder typically
matches or exceeds the performance of the best CPU rasterizers. The difference is particularly
pronouced at large sizes, where Pathfinder regularly achieves multi-factor speedups. All shaders
have no loops and minimal branching.
* Advanced font rendering. Pathfinder can render fonts with slight hinting and can perform subpixel
antialiasing on LCD screens. It can do stem darkening/font dilation like macOS and FreeType in
order to make text easier to read at small sizes. The library also has support for gamma
correction.
* Support for full vector scenes. Pathfinder 2 is designed to efficiently handle workloads that
consist of many overlapping vector paths, such as those commonly found in SVG and PDF files. It
makes heavy use of the hardware Z buffer to perform occlusion culling, which often results in
dramatic performance wins over typical software renderers that use the painter's algorithm.
* Support for SVG. Pathfinder 3 is designed to efficiently handle workloads that consist of many
overlapping vector paths, such as those commonly found in SVG and PDF files. It can perform
occlusion culling, which often results in dramatic performance wins over typical software
renderers that use the painter's algorithm. A simple loader that leverages the `resvg` library
to render a subset of SVG is included, so it's easy to get started.
* 3D capability. Pathfinder 2 can render fonts and vector paths in 3D environments. Vector meshes
are rendered just like any other mesh, with a simple shader applied.
* 3D capability. Pathfinder can render fonts and vector paths in 3D environments without any loss
in quality. This is intended to be useful for vector-graphics-based user interfaces in VR, for
example.
* Lightweight. Unlike large vector graphics packages that mix and match many different algorithms,
Pathfinder 3 uses a single, simple technique. It consists of a set of modular crates, so
applications can pick and choose only the components that are necessary to minimize dependencies.
* Portability to most GPUs manufactured in the last decade, including integrated and mobile GPUs.
Geometry, tessellation, and compute shader functionality is not required.
## Building
Pathfinder 2 is a set of modular packages, allowing you to choose which parts of the library you
need. A WebGL demo is included, so you can try Pathfinder right in your browser. (Please note that,
like the rest of Pathfinder, it's under heavy development and has known bugs.)
Pathfinder 3 is a set of modular packages, allowing you to choose which parts of the library you
need. An SVG rendering demo, written in Rust, is included, so you can try Pathfinder out right
away. It also provides an example of how to use the library. (Note that, like the rest of
Pathfinder, the demo is under heavy development and has known bugs.)
To run the demo, make sure [Node.js](https://nodejs.org/en/), [nightly Rust](https://doc.rust-lang.org/1.5.0/book/nightly-rust.html) and [CMake](https://cmake.org/install/) are installed, then run the following commands:
Running the demo is as simple as:
$ cd demo/client
$ npm install
$ npm run build
$ cd ../server
$ cargo run --release
$ cd demo/native
$ RUSTFLAGS="-C target-cpu=native" cargo run --release
Then navigate to http://localhost:8000/.
## Testing
Pathfinder contains reference tests that compare its output to that of other rendering libraries,
such as Cairo. To run them, install Cairo and FreeType if necessary, then perform the above steps,
substituting the last line with:
$ cargo run --release --features reftests
On macOS, it is recommended that you force the use of the integrated GPU, as issues with Apple's
OpenGL drivers may limit performance on discrete GPUs. You can use
[gfxCardStatus.app](https://gfx.io/) for this.
## Authors
@ -72,7 +64,7 @@ community.
The logo was designed by Jay Vining.
Pathfinder abides by the same Code of Conduct as Rust itself.
Contributors to Pathfinder are expected to abide by the same Code of Conduct as Rust itself.
## License

2
demo/.gitignore vendored
View File

@ -1,2 +0,0 @@
node_modules

10
demo/android/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild

1
demo/android/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,34 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "graphics.pathfinder.pathfinderdemo"
minSdkVersion 24
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "arm64-v8a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
compile 'com.google.vr:sdk-base:1.160.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:support-v4:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

21
demo/android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="graphics.pathfinder.pathfinderdemo">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.software.vr.mode"
android:required="false" />
<uses-feature
android:name="android.hardware.vr.high_performance"
android:required="false" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".PathfinderDemoActivity"
android:configChanges="density|navigation|orientation|keyboardHidden|screenSize|uiMode"
android:label="@string/app_name"
android:resizeableActivity="false"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!--
This marks the Activity as a Daydream Activity and allows it
to be launched from the Daydream Home.
-->
<category android:name="com.google.intent.category.DAYDREAM" />
<!--
This marks the Activity as a Cardboard Activity and allows it
to be launched from the Cardboard app.
-->
<category android:name="com.google.intent.category.CARDBOARD" />
<!--
This allows this Activity to be launched from the traditional
Android 2D launcher as well. Remove it if you do not want
this Activity to be launched directly from the 2D launcher.
-->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".PathfinderDemoVRListenerService"
android:label="@string/service_name"
android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.vr.VrListenerService" />
</intent-filter>
</service>
<activity
android:name=".PathfinderDemoFileBrowserActivity"
android:label="@string/title_activity_pathfinder_demo_file_browser"></activity>
</application>
</manifest>

View File

@ -0,0 +1 @@
../../../../../../resources

View File

@ -0,0 +1,178 @@
package graphics.pathfinder.pathfinderdemo;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class PathfinderDemoActivity extends Activity {
private PathfinderDemoRenderer mRenderer;
/**
* Some older devices needs a small delay between UI widget updates
* and a change of the status and navigation bar.
*/
private PathfinderDemoSurfaceView mContentView;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
ComponentName mVRListenerComponentName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
String[] perms = new String[1];
perms[0] = Manifest.permission.READ_EXTERNAL_STORAGE;
ActivityCompat.requestPermissions(this, perms,
1);
} else {
init();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (permissions[0] == Manifest.permission.READ_EXTERNAL_STORAGE)
init();
}
@RequiresApi(api = Build.VERSION_CODES.N)
void setVRMode(boolean enabled) {
mContentView.setStereoModeEnabled(enabled);
mContentView.setDistortionCorrectionEnabled(false);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressLint("ClickableViewAccessibility")
private void init() {
mVRListenerComponentName = new ComponentName("graphics.pathfinder.pathfinderdemo",
"graphics.pathfinder.pathfinderdemo.PathfinderDemoVRListenerService");
setContentView(R.layout.activity_pathfinder);
mContentView = findViewById(R.id.fullscreen_content);
setVRMode(false);
mContentView.setEGLContextClientVersion(3);
mRenderer = new PathfinderDemoRenderer(this);
mContentView.setRenderer(mRenderer);
GestureDetector.SimpleOnGestureListener gestureListener =
new GestureDetector.SimpleOnGestureListener() {
public boolean onScroll(final MotionEvent from,
final MotionEvent to,
final float deltaX,
final float deltaY) {
final int x = Math.round(to.getX());
final int y = Math.round(to.getY());
PathfinderDemoRenderer.pushMouseDraggedEvent(x, y);
return true;
}
public boolean onDown(final MotionEvent event) {
final int x = Math.round(event.getX());
final int y = Math.round(event.getY());
PathfinderDemoRenderer.pushMouseDownEvent(x, y);
return true;
}
};
mGestureDetector = new GestureDetector(getApplicationContext(), gestureListener);
ScaleGestureDetector.SimpleOnScaleGestureListener scaleGestureListener =
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
public boolean onScale(final ScaleGestureDetector detector) {
int focusX = Math.round(detector.getFocusX());
int focusY = Math.round(detector.getFocusY());
float factor = (detector.getScaleFactor() - 1.0f) * 0.5f;
PathfinderDemoRenderer.pushZoomEvent(factor, focusX, focusY);
return true;
}
};
mScaleGestureDetector = new ScaleGestureDetector(getApplicationContext(),
scaleGestureListener);
mContentView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(final View view, final MotionEvent event) {
boolean result = mScaleGestureDetector.onTouchEvent(event);
if (!mScaleGestureDetector.isInProgress())
result = mGestureDetector.onTouchEvent(event) || result;
return result;
}
});
final SensorManager sensorManager = (SensorManager)
getSystemService(Context.SENSOR_SERVICE);
final Sensor rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
sensorManager.registerListener(new SensorEventListener() {
private boolean mInitialized;
private float mPitch;
private float mYaw;
@Override
public void onSensorChanged(SensorEvent event) {
// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_Angles_Conversion
final float[] q = event.values;
final float pitch = (float)Math.asin(2.0 * (q[0] * q[2] - q[3] * q[1]));
final float yaw = (float)Math.atan2(2.0 * (q[0] * q[3] + q[1] * q[2]),
1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3]));
final float deltaPitch = pitch - mPitch;
final float deltaYaw = yaw - mYaw;
mPitch = pitch;
mYaw = yaw;
if (!mInitialized) {
mInitialized = true;
return;
}
PathfinderDemoRenderer.pushLookEvent(-deltaPitch, deltaYaw);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}, rotationSensor, 5000);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
}
public void presentOpenSVGDialog() {
final Intent intent = new Intent(this, PathfinderDemoFileBrowserActivity.class);
startActivity(intent);
}
}

View File

@ -0,0 +1,47 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.io.IOException;
public class PathfinderDemoFileBrowserActivity extends Activity {
private ListView mBrowserView;
private static String SVG_RESOURCE_PATH = "svg/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pathfinder_demo_file_browser);
mBrowserView = findViewById(R.id.fileBrowserBrowser);
try {
final AssetManager assetManager = getAssets();
final String[] svgFilenames = assetManager.list("resources/" + SVG_RESOURCE_PATH);
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this,
R.layout.layout_pathfinder_demo_file_browser_list_item,
svgFilenames);
mBrowserView.setAdapter(adapter);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
mBrowserView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textView = (TextView)view;
PathfinderDemoRenderer.pushOpenSVGEvent(SVG_RESOURCE_PATH + textView.getText());
finish();
}
});
}
}

View File

@ -0,0 +1,96 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
import com.google.vr.sdk.base.Eye;
import com.google.vr.sdk.base.GvrView;
import com.google.vr.sdk.base.HeadTransform;
import com.google.vr.sdk.base.Viewport;
import javax.microedition.khronos.egl.EGLConfig;
public class PathfinderDemoRenderer extends Object implements GvrView.Renderer {
private final PathfinderDemoActivity mActivity;
private boolean mInitialized;
private boolean mInVRMode;
private static native void init(PathfinderDemoActivity activity,
PathfinderDemoResourceLoader resourceLoader,
int width,
int height);
private static native int prepareFrame();
private static native void drawScene();
private static native void finishDrawingFrame();
public static native void pushWindowResizedEvent(int width, int height);
public static native void pushMouseDownEvent(int x, int y);
public static native void pushMouseDraggedEvent(int x, int y);
public static native void pushZoomEvent(float scale, int centerX, int centerY);
public static native void pushLookEvent(float pitch, float yaw);
public static native void pushOpenSVGEvent(String path);
static {
System.loadLibrary("pathfinder_android_demo");
}
PathfinderDemoRenderer(PathfinderDemoActivity activity) {
super();
mActivity = activity;
mInitialized = false;
}
@Override
public void onDrawFrame(HeadTransform headTransform, Eye leftEye, Eye rightEye) {
final boolean inVR = prepareFrame() > 1;
if (inVR != mInVRMode) {
mInVRMode = inVR;
try {
mActivity.setVrModeEnabled(mInVRMode, mActivity.mVRListenerComponentName);
mActivity.setVRMode(inVR);
} catch (PackageManager.NameNotFoundException exception) {
throw new RuntimeException(exception);
}
}
drawScene();
finishDrawingFrame();
}
@Override
public void onFinishFrame(Viewport viewport) {
}
@Override
public void onSurfaceChanged(int width, int height) {
if (!mInitialized) {
init(mActivity,
new PathfinderDemoResourceLoader(mActivity.getAssets()),
width,
height);
mInitialized = true;
} else {
pushWindowResizedEvent(width, height);
}
}
@Override
public void onSurfaceCreated(EGLConfig config) {
}
@Override
public void onRendererShutdown() {
}
}

View File

@ -0,0 +1,40 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class PathfinderDemoResourceLoader {
private AssetManager m_assetManager;
PathfinderDemoResourceLoader(AssetManager assetManager) {
m_assetManager = assetManager;
}
ByteBuffer slurp(String path) {
try {
InputStream inputStream = m_assetManager.open("resources/" + path);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
while (true) {
int nRead = inputStream.read(buffer, 0, buffer.length);
if (nRead == -1)
break;
outputStream.write(buffer, 0, nRead);
}
byte[] outputBytes = outputStream.toByteArray();
ByteBuffer resultBuffer = ByteBuffer.allocateDirect(outputStream.size());
resultBuffer.put(outputBytes);
return resultBuffer;
} catch (IOException exception) {
Log.e("Pathfinder", "Resource not found: " + path);
return null;
}
}
}

View File

@ -0,0 +1,15 @@
package graphics.pathfinder.pathfinderdemo;
import android.content.Context;
import android.util.AttributeSet;
import com.google.vr.sdk.base.GvrView;
public class PathfinderDemoSurfaceView extends GvrView {
public PathfinderDemoSurfaceView(Context context) {
super(context);
}
public PathfinderDemoSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

View File

@ -0,0 +1,10 @@
package graphics.pathfinder.pathfinderdemo;
import android.os.Build;
import android.service.vr.VrListenerService;
import android.support.annotation.RequiresApi;
@RequiresApi(api = Build.VERSION_CODES.N)
public class PathfinderDemoVRListenerService extends VrListenerService {
}

View File

@ -0,0 +1 @@
../../../../../../../target/aarch64-linux-android/release/libpathfinder_android_demo.so

View File

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".PathfinderDemoActivity">
<!-- The primary full-screen view. This can be replaced with whatever view
is needed to present your content, e.g. VideoView, SurfaceView,
TextureView, etc. -->
<graphics.pathfinder.pathfinderdemo.PathfinderDemoSurfaceView
android:id="@+id/fullscreen_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true" />
<!-- This FrameLayout insets its children based on system windows using
android:fitsSystemWindows. -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:id="@+id/fullscreen_content_controls"
style="?metaButtonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/black_overlay"
android:orientation="horizontal"
tools:ignore="UselessParent">
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".PathfinderDemoFileBrowserActivity">
<ListView
android:id="@+id/fileBrowserBrowser"
style="@android:style/Widget.Material.ListView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:lineSpacingExtra="14sp"
android:textAppearance="@android:style/TextAppearance"
android:textSize="18sp">
</TextView>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,12 @@
<resources>
<!-- Declare custom theme attributes that allow changing which styles are
used for button bars depending on the API level.
?android:attr/buttonBarStyle is new as of API 11 so this is
necessary to support previous API levels. -->
<declare-styleable name="ButtonBarContainerTheme">
<attr name="metaButtonBarStyle" format="reference" />
<attr name="metaButtonBarButtonStyle" format="reference" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="black_overlay">#66000000</color>
</resources>

View File

@ -0,0 +1,9 @@
<resources>
<string name="app_name">Pathfinder Demo</string>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
<string name="service_name">PathfinderVRListenerService</string>
<string name="title_activity_pathfinder_demo_file_browser">PathfinderDemoFileBrowserActivity
</string>
</resources>

View File

@ -0,0 +1,23 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="FullscreenTheme" parent="AppTheme">
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:windowBackground">@null</item>
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
</style>
<style name="FullscreenActionBarStyle" parent="Widget.AppCompat.ActionBar">
<item name="android:background">@color/black_overlay</item>
</style>
</resources>

27
demo/android/build.gradle Normal file
View File

@ -0,0 +1,27 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,14 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Fri Mar 08 12:48:37 PST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

172
demo/android/gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
demo/android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,26 @@
[package]
name = "pathfinder_android_demo"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[lib]
crate_type = ["cdylib"]
[dependencies]
egl = "0.2"
gl = "0.6"
jni = "0.11"
lazy_static = "1.3"
[dependencies.pathfinder_demo]
path = "../../common"
[dependencies.pathfinder_geometry]
path = "../../../geometry"
[dependencies.pathfinder_gl]
path = "../../../gl"
[dependencies.pathfinder_gpu]
path = "../../../gpu"

View File

@ -0,0 +1,275 @@
// pathfinder/demo/android/rust/src/main.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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_use]
extern crate lazy_static;
use jni::{JNIEnv, JavaVM};
use jni::objects::{GlobalRef, JByteBuffer, JClass, JObject, JString, JValue};
use pathfinder_demo::DemoApp;
use pathfinder_demo::Options;
use pathfinder_demo::window::{Event, SVGPath, View, Window, WindowSize};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader;
use std::cell::RefCell;
use std::io::Error as IOError;
use std::mem;
use std::os::raw::c_void;
use std::path::PathBuf;
use std::sync::Mutex;
lazy_static! {
static ref EVENT_QUEUE: Mutex<Vec<Event>> = Mutex::new(vec![]);
}
thread_local! {
static DEMO_APP: RefCell<Option<DemoApp<WindowImpl>>> = RefCell::new(None);
static JAVA_ACTIVITY: RefCell<Option<JavaActivity>> = RefCell::new(None);
static JAVA_RESOURCE_LOADER: RefCell<Option<JavaResourceLoader>> = RefCell::new(None);
}
static RESOURCE_LOADER: AndroidResourceLoader = AndroidResourceLoader;
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_init(env: JNIEnv,
class: JClass,
activity: JObject,
loader: JObject,
width: i32,
height: i32) {
let logical_size = Point2DI32::new(width, height);
let window_size = WindowSize { logical_size, backing_scale_factor: 1.0 };
let window = WindowImpl { size: logical_size };
let options = Options::default();
JAVA_ACTIVITY.with(|java_activity| {
*java_activity.borrow_mut() = Some(JavaActivity::new(env.clone(), activity));
});
JAVA_RESOURCE_LOADER.with(|java_resource_loader| {
*java_resource_loader.borrow_mut() = Some(JavaResourceLoader::new(env, loader));
});
DEMO_APP.with(|demo_app| {
gl::load_with(|name| egl::get_proc_address(name) as *const c_void);
*demo_app.borrow_mut() = Some(DemoApp::new(window, window_size, options));
});
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_prepareFrame(env: JNIEnv,
class: JClass)
-> i32 {
DEMO_APP.with(|demo_app| {
let mut event_queue = EVENT_QUEUE.lock().unwrap();
match *demo_app.borrow_mut() {
Some(ref mut demo_app) => {
demo_app.prepare_frame(mem::replace(&mut *event_queue, vec![])) as i32
}
None => 0,
}
})
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_drawScene(
env: JNIEnv,
class: JClass) {
DEMO_APP.with(|demo_app| {
if let Some(ref mut demo_app) = *demo_app.borrow_mut() {
demo_app.draw_scene()
}
})
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_finishDrawingFrame(
env: JNIEnv,
class: JClass) {
DEMO_APP.with(|demo_app| {
if let Some(ref mut demo_app) = *demo_app.borrow_mut() {
demo_app.finish_drawing_frame()
}
})
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushWindowResizedEvent(
env: JNIEnv,
class: JClass,
width: i32,
height: i32) {
EVENT_QUEUE.lock().unwrap().push(Event::WindowResized(WindowSize {
logical_size: Point2DI32::new(width, height),
backing_scale_factor: 1.0,
}))
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushMouseDownEvent(
_: JNIEnv,
_: JClass,
x: i32,
y: i32) {
EVENT_QUEUE.lock().unwrap().push(Event::MouseDown(Point2DI32::new(x, y)))
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushMouseDraggedEvent(
_: JNIEnv,
_: JClass,
x: i32,
y: i32) {
EVENT_QUEUE.lock().unwrap().push(Event::MouseDragged(Point2DI32::new(x, y)))
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushZoomEvent(
_: JNIEnv,
_: JClass,
factor: f32,
center_x: i32,
center_y: i32) {
EVENT_QUEUE.lock().unwrap().push(Event::Zoom(factor, Point2DI32::new(center_x, center_y)))
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushLookEvent(
_: JNIEnv,
_: JClass,
pitch: f32,
yaw: f32) {
EVENT_QUEUE.lock().unwrap().push(Event::Look { pitch, yaw })
}
#[no_mangle]
pub unsafe extern "system" fn
Java_graphics_pathfinder_pathfinderdemo_PathfinderDemoRenderer_pushOpenSVGEvent(
env: JNIEnv,
_: JClass,
string: JObject) {
let string: String = env.get_string(JString::from(string)).unwrap().into();
EVENT_QUEUE.lock().unwrap().push(Event::OpenSVG(SVGPath::Resource(string)))
}
struct WindowImpl {
size: Point2DI32,
}
impl Window for WindowImpl {
fn gl_version(&self) -> GLVersion {
GLVersion::GLES3
}
fn viewport(&self, view: View) -> RectI32 {
let mut width = self.size.x();
let mut offset_x = 0;
let height = self.size.y();
if let View::Stereo(index) = view {
width = width / 2;
offset_x = (index as i32) * width;
}
let size = Point2DI32::new(width, height);
let offset = Point2DI32::new(offset_x, 0);
RectI32::new(offset, size)
}
fn make_current(&mut self, _view: View) {}
fn present(&mut self) {}
fn resource_loader(&self) -> &dyn ResourceLoader {
&RESOURCE_LOADER
}
fn create_user_event_id(&self) -> u32 {
0
}
fn push_user_event(message_type: u32, message_data: u32) {
}
fn present_open_svg_dialog(&mut self) {
JAVA_ACTIVITY.with(|java_activity| {
let mut java_activity = java_activity.borrow_mut();
let java_activity = java_activity.as_mut().unwrap();
let env = java_activity.vm.get_env().unwrap();
env.call_method(java_activity.activity.as_obj(),
"presentOpenSVGDialog",
"()V",
&[]).unwrap();
});
}
fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()> {
// TODO(pcwalton)
Err(())
}
}
struct AndroidResourceLoader;
impl ResourceLoader for AndroidResourceLoader {
fn slurp(&self, path: &str) -> Result<Vec<u8>, IOError> {
JAVA_RESOURCE_LOADER.with(|java_resource_loader| {
let java_resource_loader = java_resource_loader.borrow();
let java_resource_loader = java_resource_loader.as_ref().unwrap();
let loader = java_resource_loader.loader.as_obj();
let env = java_resource_loader.vm.get_env().unwrap();
match env.call_method(loader,
"slurp",
"(Ljava/lang/String;)Ljava/nio/ByteBuffer;",
&[JValue::Object(*env.new_string(path).unwrap())]).unwrap() {
JValue::Object(object) => {
let byte_buffer = JByteBuffer::from(object);
Ok(Vec::from(env.get_direct_buffer_address(byte_buffer).unwrap()))
}
_ => panic!("Unexpected return value!"),
}
})
}
}
struct JavaActivity {
activity: GlobalRef,
vm: JavaVM,
}
impl JavaActivity {
fn new(env: JNIEnv, activity: JObject) -> JavaActivity {
JavaActivity {
activity: env.new_global_ref(activity).unwrap(),
vm: env.get_java_vm().unwrap(),
}
}
}
struct JavaResourceLoader {
loader: GlobalRef,
vm: JavaVM,
}
impl JavaResourceLoader {
fn new(env: JNIEnv, loader: JObject) -> JavaResourceLoader {
JavaResourceLoader {
loader: env.new_global_ref(loader).unwrap(),
vm: env.get_java_vm().unwrap(),
}
}
}

View File

@ -0,0 +1 @@
include ':app'

View File

@ -1,46 +0,0 @@
const {spawn} = require('child_process');
const process = require('process');
class RustdocPlugin {
constructor(options) {
this.options = Object.assign({directories: [], flags: {}}, options);
}
apply(compiler) {
let rustdocFlags = [];
for (let key in this.options.flags) {
if (this.options.flags.hasOwnProperty(key))
rustdocFlags.push("--" + key + "=" + this.options.flags[key]);
}
rustdocFlags = rustdocFlags.join(" ");
compiler.plugin('after-compile', (compilation, done) => {
let directoriesLeft = this.options.directories.length;
for (const directory of this.options.directories) {
console.log("Building documentation for `" + directory + "`...");
const cargo = spawn("cargo", ["doc", "--no-deps"], {
cwd: directory,
env: Object.assign({RUSTDOCFLAGS: rustdocFlags}, process.env),
});
cargo.stdout.setEncoding('utf8');
cargo.stderr.setEncoding('utf8');
cargo.stdout.on('data', data => console.log(data));
cargo.stderr.on('data', data => console.log(data));
cargo.on('close', code => {
if (code !== 0) {
const message = "Failed to build documentation for `" + directory + "`!";
console.error(message);
throw new Error(message);
}
console.log("Built documentation for `" + directory + "`.");
directoriesLeft--;
if (directoriesLeft === 0)
done();
});
}
});
}
}
module.exports = RustdocPlugin;

View File

@ -1,11 +0,0 @@
{
"name": "rustdoc-webpack-plugin",
"version": "0.1.0",
"description": "Webpack plugin for building Rust package documentation",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Patrick Walton",
"license": "MIT OR Apache-2.0"
}

View File

@ -1,43 +0,0 @@
body {
padding: 0 !important;
}
h1,
h2,
h3
h4,
.sidebar,
a.source,
.search-input,
.content table :not(code) > a,
.collapse-toggle,
ul.item-list > li > .out-of-band {
font-family: inherit !important;
}
.content {
padding: 0 15px 0 0 !important;
}
nav.navbar {
max-width: inherit !important;
border: none !important;
margin: 0 !important;
}
nav.sidebar {
top: auto !important;
}
img {
max-width: inherit !important;
}
h4 > span.invisible,
.structfield > span.invisible,
.variant > span.invisible {
visibility: visible !important;
}
/*
* Hide `generate_gamma_lut` and `pathfinder_server`.
* FIXME(pcwalton): Is there a better way to do this?
*/
.sidebar-elems > .block.crate > ul > li:first-child,
.sidebar-elems > .block.crate > ul > li:last-child {
display: none;
}

View File

@ -1,406 +0,0 @@
/*
* Fonts
*/
@font-face {
font-family: "Inter UI";
font-weight: 400;
src: url("/woff2/inter-ui/Inter-UI-Regular.woff2") format('woff2');
}
@font-face {
font-family: "Inter UI";
font-weight: 700;
src: url("/woff2/inter-ui/Inter-UI-Bold.woff2") format('woff2');
}
@font-face {
font-family: "Inter UI";
font-weight: 400;
font-style: italic;
src: url("/woff2/inter-ui/Inter-UI-RegularItalic.woff2") format('woff2');
}
@font-face {
font-family: "Inter UI";
font-weight: 700;
font-style: italic;
src: url("/woff2/inter-ui/Inter-UI-BoldItalic.woff2") format('woff2');
}
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url("/woff2/material-icons/MaterialIcons-Regular.woff2") format('woff2');
}
/*
* Core
*/
body {
font-family: "Inter UI", sans-serif;
}
body.pf-unscrollable {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
nav {
-moz-user-select: none;
user-select: none;
}
#pf-load-font-button-label,
#pf-load-svg-button-label {
left: 1em;
margin: 0;
}
#pf-settings-container, #pf-rotate-slider-container {
position: absolute;
right: 1rem;
bottom: 2rem;
pointer-events: none;
text-align: right;
}
#pf-toolbar {
margin-right: 1rem;
}
#pf-settings, #pf-rotate-slider-card {
text-align: initial;
user-select: none;
-moz-user-select: none;
opacity: 1.0;
transition: opacity 300ms, transform 300ms, visibility 300ms;
transform: translateY(0);
}
#pf-settings:not(.pf-invisible), #pf-rotate-slider-card:not(.pf-invisible) {
pointer-events: auto;
}
.pf-toolbar-button {
pointer-events: auto;
}
#pf-test-pane {
pointer-events: auto;
}
.pf-toolbar-button.btn-outline-secondary:not(:hover) {
background: rgba(255, 255, 255, 0.75);
}
#pf-file-select {
position: absolute;
visibility: hidden;
}
#pf-settings.pf-invisible, #pf-rotate-slider-card.pf-invisible {
opacity: 0.0;
transform: translateY(1em);
visibility: hidden;
}
button > svg {
width: 1.25em;
display: block;
}
button > svg path {
fill: currentColor;
}
.pf-pointer-events-none {
pointer-events: none;
}
.pf-pointer-events-auto {
pointer-events: auto;
}
#pf-rendering-options-group {
right: 1em;
}
.pf-maximized-canvas {
display: block;
position: absolute;
touch-action: none;
}
#pf-canvas.pf-draggable {
cursor: grab;
cursor: -webkit-grab;
}
#pf-canvas.pf-draggable.pf-grabbing {
cursor: grabbing;
cursor: -webkit-grabbing;
}
#pf-fps-label {
color: white;
background: rgba(0, 0, 0, 0.75);
width: 17em;
}
#pf-svg, #pf-svg * {
visibility: hidden !important;
}
#pf-outer-container {
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#pf-inner-container {
overflow: scroll;
}
.pf-spinner {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.pf-spinner-hidden {
display: none !important;
}
.pf-display-none {
display: none !important;
}
#pf-benchmark-results-modal .modal-body {
overflow: scroll;
max-height: 75vh;
}
.pf-benchmark-results-global {
padding: 0.75rem;
}
.pf-benchmark-results-label {
font-weight: bold;
}
.pf-material-icons {
font-family: "Material Icons";
display: block;
font-size: 18px;
margin: -3px 0 -3px;
}
#pf-rotate-slider {
margin: 2px 0 0;
}
label.pf-disabled {
color: gray;
}
.btn-group input[type="radio"] {
-moz-appearance: none;
}
/*
* Arrow
*
* http://www.cssarrowplease.com/
*/
#pf-settings > .pf-arrow-box:after, #pf-settings .pf-arrow-box:before {
right: 22px;
}
#pf-rotate-slider-card > .pf-arrow-box:after, #pf-rotate-slider-card .pf-arrow-box:before {
right: 166px;
}
.pf-arrow-box:after, .pf-arrow-box:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.pf-arrow-box:after {
border-color: rgba(0, 0, 0, 0);
border-top-color: white;
border-width: 14px;
margin-right: -14px;
}
.pf-arrow-box:before {
border-color: rgba(0, 0, 0, 0);
border-top-color: rgba(0, 0, 0, 0.125);
border-width: 15px;
margin-right: -15px;
}
#pf-masthead-logo {
width: 30vw;
}
.github-corner {
display: block;
position: absolute;
right: 0;
top: 0;
height: 100%;
text-align: right;
}
.github-corner > svg {
fill: #151513;
color: #fff;
height: 100%;
width: auto;
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave{
0%, 100% {
transform: rotate(0);
}
20%, 60% {
transform: rotate(-25deg);
}
40%, 80% {
transform: rotate(10deg);
}
}
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
/*
* Spinner
*
* http://tobiasahlin.com/spinkit/
*/
.sk-fading-circle {
margin: auto;
width: 40px;
height: 40px;
}
.sk-fading-circle .sk-circle {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.sk-fading-circle .sk-circle:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #333;
border-radius: 100%;
-webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
}
.sk-fading-circle .sk-circle2 {
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}
.sk-fading-circle .sk-circle3 {
-webkit-transform: rotate(60deg);
-ms-transform: rotate(60deg);
transform: rotate(60deg);
}
.sk-fading-circle .sk-circle4 {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.sk-fading-circle .sk-circle5 {
-webkit-transform: rotate(120deg);
-ms-transform: rotate(120deg);
transform: rotate(120deg);
}
.sk-fading-circle .sk-circle6 {
-webkit-transform: rotate(150deg);
-ms-transform: rotate(150deg);
transform: rotate(150deg);
}
.sk-fading-circle .sk-circle7 {
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.sk-fading-circle .sk-circle8 {
-webkit-transform: rotate(210deg);
-ms-transform: rotate(210deg);
transform: rotate(210deg);
}
.sk-fading-circle .sk-circle9 {
-webkit-transform: rotate(240deg);
-ms-transform: rotate(240deg);
transform: rotate(240deg);
}
.sk-fading-circle .sk-circle10 {
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.sk-fading-circle .sk-circle11 {
-webkit-transform: rotate(300deg);
-ms-transform: rotate(300deg);
transform: rotate(300deg);
}
.sk-fading-circle .sk-circle12 {
-webkit-transform: rotate(330deg);
-ms-transform: rotate(330deg);
transform: rotate(330deg);
}
.sk-fading-circle .sk-circle2:before {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.sk-fading-circle .sk-circle3:before {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
.sk-fading-circle .sk-circle4:before {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.sk-fading-circle .sk-circle5:before {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.sk-fading-circle .sk-circle6:before {
-webkit-animation-delay: -0.7s;
animation-delay: -0.7s;
}
.sk-fading-circle .sk-circle7:before {
-webkit-animation-delay: -0.6s;
animation-delay: -0.6s;
}
.sk-fading-circle .sk-circle8:before {
-webkit-animation-delay: -0.5s;
animation-delay: -0.5s;
}
.sk-fading-circle .sk-circle9:before {
-webkit-animation-delay: -0.4s;
animation-delay: -0.4s;
}
.sk-fading-circle .sk-circle10:before {
-webkit-animation-delay: -0.3s;
animation-delay: -0.3s;
}
.sk-fading-circle .sk-circle11:before {
-webkit-animation-delay: -0.2s;
animation-delay: -0.2s;
}
.sk-fading-circle .sk-circle12:before {
-webkit-animation-delay: -0.1s;
animation-delay: -0.1s;
}
@-webkit-keyframes sk-circleFadeDelay {
0%, 39%, 100% { opacity: 0; }
40% { opacity: 1; }
}
@keyframes sk-circleFadeDelay {
0%, 39%, 100% { opacity: 0; }
40% { opacity: 1; }
}

View File

@ -1,58 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>3D Demo &mdash; Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
<script type="text/javascript" src="/js/pathfinder/3d-demo.js"></script>
</head>
<body class="pf-unscrollable">
{{>partials/navbar.html isDemo=true}}
<canvas id="pf-canvas" class="pf-maximized-canvas" width="400" height="300"></canvas>
<div class="fixed-bottom mb-3 d-flex justify-content-between align-items-end pf-pointer-events-none">
<div class="rounded invisible container py-1 px-3 ml-3" id="pf-fps-label"></div>
<div id="pf-toolbar">
<button id="pf-screenshot-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">photo_camera</span>
</button>
<div id="pf-settings-container">
<div id="pf-settings" class="card mb-4 pf-invisible">
<div class="card-body">
<button id="pf-settings-close-button" type="button" class="close"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<form>
<div class="form-group">
<label for="pf-aa-level-select">Antialiasing</label>
<select id="pf-aa-level-select" class="form-control custom-select">
<option value="none">None</option>
<option value="ssaa-2">2&times;SSAA</option>
<option value="ssaa-4" selected>4&times;SSAA</option>
</select>
</div>
</form>
</div>
<div class="pf-arrow-box"></div>
</div>
</div>
<button id="pf-settings-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button"
aria-expanded="false" aria-controls="#pf-settings">
<span class="pf-material-icons">settings</span>
</button>
<button id="pf-vr" type="button"
class="btn btn-outline-secondary pf-toolbar-button"
aria-expanded="false" aria-controls="#pf-vr"
style="display: none">
<!-- https://materialdesignicons.com/icon/virtual-reality -->
<svg style="height: 18px" viewBox="0 0 24 24">
<path fill="#000000" d="M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5M6,9H7.5L8.5,12.43L9.5,9H11L9.25,15H7.75L6,9M13,9H16.5C17.35,9 18,9.65 18,10.5V11.5C18,12.1 17.6,12.65 17.1,12.9L18,15H16.5L15.65,13H14.5V15H13V9M14.5,10.5V11.5H16.5V10.5H14.5Z" />
</svg>
</button>
</div>
</div>
{{>partials/spinner.html}}
</body>
</html>

View File

@ -1,122 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Benchmark &mdash; Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
<script type="text/javascript" src="/js/pathfinder/benchmark.js"></script>
</head>
<body class="pf-unscrollable">
{{>partials/navbar.html isTool=true}}
<canvas id="pf-canvas" class="pf-maximized-canvas" width="400" height="300"></canvas>
<svg id="pf-svg" xmlns="http://www.w3.org/2000/svg" width="400" height="300"></svg>
<div class="modal fade" id="pf-benchmark-modal" tabindex="-1" role="dialog"
aria-labelledby="pf-benchmark-label" aria-hidden="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="pf-benchmark-label">Benchmark</h5>
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="pf-benchmark-text-tab"
data-toggle="tab" href="#pf-benchmark-text" role="tab"
aria-controls="pf-benchmark-text" aria-selected="true">Text</a>
</li>
<li class="nav-item">
<a class="nav-link" id="pf-benchmark-svg-tab" data-toggle="tab"
href="#pf-benchmark-svg" role="tab"
aria-controls="pf-benchmark-svg" aria-selected="false">SVG</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane show active pt-3" id="pf-benchmark-text"
role="tabpanel" aria-labelledby="pf-benchmark-text-tab">
<form class="form" id="pf-benchmark-text-form">
<div class="form-group" id="pf-aa-level-form-group">
<label for="pf-aa-level-select"
class="col-form-label mr-sm-2">Antialiasing</label>
<select id="pf-aa-level-select" autocomplete="off"
class="form-control custom-select mr-sm-3">
<option value="none" selected>None</option>
<option value="ssaa-2">2&times;SSAA</option>
<option value="ssaa-4">4&times;SSAA</option>
<option value="ssaa-8">8&times;SSAA</option>
<option value="ssaa-16">16&times;SSAA</option>
<option value="xcaa">XCAA</option>
</select>
</div>
<div class="form-group">
<label for="pf-subpixel-aa-select">Subpixel AA</label>
<select id="pf-subpixel-aa-select"
class="form-control custom-select">
<option value="none">None</option>
<option value="freetype" selected>
FreeType-style
</option>
<option value="core-graphics">
Core Graphics-style
</option>
</select>
</div>
</form>
</div>
<div class="tab-pane pt-3" id="pf-benchmark-svg"
role="tabpanel" aria-labelledby="pf-benchmark-svg-tab">
<form class="form" id="pf-benchmark-svg-form">
</form>
</div>
</div>
<div class="d-flex justify-content-end">
<button id="pf-run-benchmark-button" type="button"
class="btn btn-primary">
Run
</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="pf-benchmark-results-modal" tabindex="-1" role="dialog"
aria-labelledby="pf-benchmark-results-label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="pf-benchmark-result-label">
Benchmark Results
</h5>
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="pf-benchmark-results-global">
<span class="pf-benchmark-results-label">Partitioning:</span>
&nbsp;
<span id="pf-benchmark-results-partitioning-time">0</span> µs/glyph
</div>
<table class="table table-striped">
<thead class="thead-default" id="pf-benchmark-results-table-header">
<tr><th>Font size (px)</th><th>GPU time per glyph (µs)</th></tr>
</thead>
<tbody id="pf-benchmark-results-table-body"></tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
id="pf-benchmark-results-save-csv-button">Save CSV&hellip;</button>
<button type="button" class="btn btn-primary"
id="pf-benchmark-results-close-button">Close</button>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1 +0,0 @@
{{>partials/navbar.html isDoc=true}}

View File

@ -1,2 +0,0 @@
{{>partials/header.html}}
<link rel="stylesheet" href="/css/pathfinder-doc.css">

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
</head>
<body>
{{>partials/navbar.html}}
<main id="content" class="mt-5 mb-5">
<div class="container d-flex flex-row">
<div><img alt="" src="/svg/demo/logo" id="pf-masthead-logo"></div>
<div class="flex-column d-flex justify-content-center ml-5">
<h1 class="display-4">Pathfinder</h1>
<p class="lead">
A fast, high-quality, open source text and vector graphics renderer for
GPUs.
</p>
</div>
</div>
</main>
</body>
</html>

View File

@ -1,73 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Mesh Debugger &mdash; Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
<script type="text/javascript" src="/js/pathfinder/mesh-debugger.js"></script>
</head>
<body class="pf-unscrollable">
{{>partials/navbar.html isTool=true}}
<canvas id="pf-canvas" class="pf-maximized-canvas" width="400" height="300"></canvas>
<svg id="pf-svg" xmlns="http://www.w3.org/2000/svg" width="400" height="300"></svg>
<div class="fixed-bottom mb-3 d-flex justify-content-end align-items-end pf-pointer-events-none">
<div id="pf-toolbar">
<button id="pf-open-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
Open…
</button>
</div>
</div>
<div class="modal fade" id="pf-open-modal" tabindex="-1" role="dialog"
aria-labelledby="pf-open-label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="pf-open-label">Open…</h5>
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="pf-open-file-label">File</label>
<select id="pf-open-file-select"
class="form-control custom-select">
<option value="font-eb-garamond" selected>
Font: EB Garamond
</option>
<option value="font-open-sans">Font: Open Sans</option>
<option value="font-nimbus-sans">Font: Nimbus Sans</option>
<option value="font-inter-ui">Font: Inter UI</option>
<option value="font-custom">Font: Custom…</option>
<option value="svg-tiger">SVG: Ghostscript Tiger</option>
<option value="svg-logo">SVG: Pathfinder Logo</option>
<option value="svg-custom">SVG: Custom…</option>
</select>
<input id="pf-file-select" type="file">
</div>
<div class="form-group" id="pf-font-path-select-group">
<label for="pf-font-path-label">Path</label>
<select id="pf-font-path-select"
class="form-control custom-select">
<option value="65" selected>A</option>
<option value="66">B</option>
<option value="67">C</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-dismiss="modal">Cancel</button>
<button type="button"
class="btn btn-primary" id="pf-open-ok-button">Open</button>
</div>
</div>
</div>
</div>
{{>partials/spinner.html}}
</body>
</html>

View File

@ -1,6 +0,0 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/css/bootstrap/bootstrap.css">
<link rel="stylesheet" href="/css/pathfinder.css">
<script type="text/javascript" src="/js/jquery/jquery.js"></script>
<script type="text/javascript" src="/js/popper.js/popper.js"></script>
<script type="text/javascript" src="/js/bootstrap/bootstrap.js"></script>

View File

@ -1,42 +0,0 @@
<nav class="navbar sticky-top navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/">
<img id="pf-navbar-logo" alt="" src="/svg/demo/logo-bw" width="30" class="mr-2">
Pathfinder
</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown {{#if isDemo}}active{{/if}}">
<a class="nav-link dropdown-toggle" id="pf-demos-menu" data-toggle="dropdown"
ref="/" aria-haspopup="true" aria-expanded="false">Demos</a>
<div class="dropdown-menu" aria-labelledby="pf-demos-menu">
<a class="dropdown-item" href="/demo/text">Text</a>
<a class="dropdown-item" href="/demo/svg">SVG</a>
<a class="dropdown-item" href="/demo/3d">3D</a>
</div>
</li>
<li class="nav-item {{#if isDoc}}active{{/if}}">
<a class="nav-link" href="/doc/api">Documentation</a>
</li>
<li class="nav-item dropdown {{#if isTool}}active{{/if}}">
<a class="nav-link dropdown-toggle" id="pf-tools-menu" data-toggle="dropdown"
ref="/" aria-haspopup="true" aria-expanded="false">Tools</a>
<div class="dropdown-menu" aria-labelledby="pf-tools-menu">
<a class="dropdown-item" href="/tools/benchmark">Benchmark</a>
<a class="dropdown-item" href="/tools/reference-test">Reference Test</a>
<a class="dropdown-item" href="/tools/mesh-debugger">Mesh Debugger</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/pcwalton/pathfinder">Code</a>
</li>
</ul>
</div>
<a href="https://github.com/pcwalton/pathfinder" class="github-corner"
aria-label="View source on Github">
<svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
</a>
</nav>

View File

@ -1,18 +0,0 @@
<div id="pf-rotate-slider-container">
<div id="pf-rotate-slider-card" class="card mb-4 pf-invisible">
<div class="card-body d-flex flex-row">
<form class="mr-3">
<input id="pf-rotate-slider" type="range" min="-3.14159" max="3.14159"
step="0.006136" value="0.0" autocomplete="off" class="form-control">
</form>
<button id="pf-rotate-close-button" type="button" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="pf-arrow-box"></div>
</div>
</div>
<button id="pf-rotate-button" type="button" title="Rotate"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">rotate_left</span>
</button>

View File

@ -1,15 +0,0 @@
<!-- http://tobiasahlin.com/spinkit/ -->
<div class="sk-fading-circle pf-spinner-hidden pf-spinner">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>

View File

@ -1,9 +0,0 @@
<label for="{{id}}-select-on" class="col-form-label col-auto">{{title}}</label>
<div class="col-auto btn-group" id="{{id}}-buttons" data-toggle="buttons">
<label class="btn btn-outline-secondary active">
<input type="radio" name="{{id}}" id="{{id}}-select-on" autocomplete="off" checked>On
</label>
<label class="btn btn-outline-secondary">
<input type="radio" name="{{id}}" autocomplete="off" id="{{id}}-select-off">Off
</label>
</div>

View File

@ -1,188 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Reference Test &mdash; Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
<script type="text/javascript" src="/js/pathfinder/reference-test.js"></script>
</head>
<body>
<svg id="pf-svg" xmlns="http://www.w3.org/2000/svg" width="400" height="300"></svg>
<div id="pf-outer-container" class="w-100">
<div class="w-100">
{{>partials/navbar.html isTool=true}}
</div>
<div id="pf-inner-container" class="container-fluid">
<div class="row">
<div id="pf-reference-test-sidebar" class="col p-3">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link" id="pf-font-test-suite-tab" data-toggle="tab"
href="#pf-font-test-suite" role="tab"
aria-controls="pf-font-test-suite" aria-selected="false">
Font Test Suite
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="pf-svg-test-suite-tab" data-toggle="tab"
href="#pf-svg-test-suite" role="tab"
aria-controls="pf-svg-test-suite" aria-selected="false">
SVG Test Suite
</a>
</li>
<li class="nav-item">
<a class="nav-link active" id="pf-font-custom-test-tab"
data-toggle="tab" href="#pf-font-custom-test" role="tab"
aria-controls="pf-font-custom-test"
aria-selected="true">
Custom Font Test
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="pf-svg-custom-test-tab"
data-toggle="tab" href="#pf-svg-custom-test" role="tab"
aria-controls="pf-svg-custom-test">
Custom SVG Test
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane pt-3" id="pf-font-test-suite" role="tabpanel"
aria-labelledby="pf-font-test-suite-tab">
<button id="pf-run-font-tests-button" class="btn btn-primary m-2">
Run Tests
</button>
<table id="pf-font-results-table"
class="table table-sm table-striped">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Font</th>
<th scope="col">Char.</th>
<th scope="col">Size</th>
<th scope="col">AA</th>
<th scope="col">Subpixel</th>
<th scope="col">Reference</th>
<th scope="col">Expected SSIM</th>
<th scope="col">Actual SSIM</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="tab-pane pt-3" id="pf-svg-test-suite" role="tabpanel"
aria-labelledby="pf-svg-test-suite-tab">
<button id="pf-run-svg-tests-button" class="btn btn-primary m-2">
Run Tests
</button>
<table id="pf-svg-results-table"
class="table table-sm table-striped">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">SVG</th>
<th scope="col">AA</th>
<th scope="col">Reference</th>
<th scope="col">Expected SSIM</th>
<th scope="col">Actual SSIM</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="tab-pane show active p-3" id="pf-font-custom-test"
role="tabpanel" aria-labelledby="pf-font-custom-test-tab">
<form id="pf-font-custom-form">
<div id="pf-font-select-file-group" class="form-group">
<label for="pf-select-file">Font</label>
<select id="pf-select-file"
class="form-control custom-select">
</select>
</div>
<div class="form-group">
<label for="pf-font-size">Font Size</label>
<div class="input-group">
<input id="pf-font-size" type="number"
class="form-control" value="32">
<div class="input-group-addon">px</div>
</div>
</div>
<div class="form-group">
<label for="pf-character">Character</label>
<input id="pf-character" type="text" maxlength="1" class="form-control"
value="B">
</div>
<div class="form-group">
<label for="pf-font-reference-renderer">
Reference Renderer
</label>
<select id="pf-font-reference-renderer"
class="form-control custom-select">
<option value="freetype">FreeType</option>
<option value="core-graphics">Core Graphics</option>
</select>
</div>
<div class="form-group row justify-content-between">
{{>partials/switch.html id="pf-subpixel-aa"
title="Subpixel AA"}}
</div>
<div id="pf-aa-level-group" class="form-group">
<label for="pf-aa-level-select">Antialiasing</label>
<select id="pf-aa-level-select"
class="form-control custom-select">
<option value="none">None</option>
<option value="ssaa-4">4&times;SSAA</option>
<option value="xcaa" selected>XCAA</option>
</select>
</div>
<div id="pf-font-ssim-group" class="form-group">
<label for="pf-font-ssim-label">SSIM</label>
<div id="pf-font-ssim-label">&mdash;</div>
</div>
</form>
</div>
<div class="tab-pane p-3" id="pf-svg-custom-test"
role="tabpanel" aria-labelledby="pf-svg-custom-test-tab">
<form id="pf-svg-custom-form">
<div id="pf-svg-select-file-group" class="form-group">
<label for="pf-select-file">SVG</label>
</div>
<div class="form-group">
<label for="pf-svg-reference-renderer">
Reference Renderer
</label>
<select id="pf-svg-reference-renderer"
class="form-control custom-select">
<option value="pixman">Cairo/Pixman</option>
</select>
</div>
<div id="pf-svg-ssim-group" class="form-group">
<label for="pf-svg-ssim-label">SSIM</label>
<div id="pf-svg-ssim-label">&mdash;</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-auto d-flex flex-column">
<div class="row">
<canvas id="pf-reference-canvas"
class="border border-top-0 border-right-0"
width="250" height="200"></canvas>
</div>
<div class="row">
<canvas id="pf-canvas"
class="pf-no-autoresize border border-top-0 border-right-0"
width="250" height="200"></canvas>
</div>
<div class="row">
<canvas id="pf-difference-canvas"
class="border border-top-0 border-right-0"
width="250" height="200"></canvas>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,78 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>SVG Demo &mdash; Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
<script type="text/javascript" src="/js/pathfinder/svg-demo.js"></script>
</head>
<body class="pf-unscrollable">
{{>partials/navbar.html isDemo=true}}
<canvas id="pf-canvas" class="pf-maximized-canvas" width="400" height="300"></canvas>
<svg id="pf-svg" xmlns="http://www.w3.org/2000/svg" width="400" height="300"></svg>
<div class="fixed-bottom mb-3 d-flex justify-content-between align-items-end pf-pointer-events-none">
<div class="rounded invisible container py-1 px-3 ml-3" id="pf-fps-label"></div>
<div id="pf-toolbar">
<div class="btn-group" role="group" aria-label="Zoom">
<button id="pf-zoom-out-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">remove</span>
</button>
<button id="pf-zoom-in-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">add</span>
</button>
</div>
{{>partials/rotate.html}}
<button id="pf-zoom-pulse-button" type="button" title="Pulse Zoom"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">play_arrow</span>
</button>
<button id="pf-screenshot-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">photo_camera</span>
</button>
<div id="pf-settings-container">
<div id="pf-settings" class="card mb-4 pf-invisible">
<div class="card-body">
<button id="pf-settings-close-button" type="button" class="close"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<form>
<div class="form-group">
<label for="pf-select-file">SVG document</label>
<select id="pf-select-file" class="form-control custom-select">
<option value="tiger" selected>Ghostscript Tiger</option>
<option value="logo">Pathfinder Logo</option>
<option value="icons">Material Design Icons</option>
<option value="load-custom">Load File&hellip;</option>
</select>
<input id="pf-file-select" type="file">
</div>
<div class="form-group">
<label for="pf-aa-level-select">Antialiasing</label>
<select id="pf-aa-level-select" class="form-control custom-select">
<option value="none">None</option>
<option value="ssaa-2">2&times;SSAA</option>
<option value="ssaa-4">4&times;SSAA</option>
<option value="ssaa-8">8&times;SSAA</option>
<option value="ssaa-16">16&times;SSAA</option>
<option value="xcaa" selected>XCAA</option>
</select>
</div>
</form>
</div>
<div class="pf-arrow-box"></div>
</div>
</div>
<button id="pf-settings-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button" aria-expanded="false"
aria-controls="#pf-settings">
<span class="pf-material-icons">settings</span>
</button>
</div>
</div>
{{>partials/spinner.html}}
</body>
</html>

View File

@ -1,133 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Text Demo &mdash; Pathfinder</title>
<meta charset="utf-8">
{{>partials/header.html}}
<script type="text/javascript" src="/js/pathfinder/text-demo.js"></script>
</head>
<body class="pf-unscrollable">
{{>partials/navbar.html isDemo=true}}
<canvas id="pf-canvas" class="pf-maximized-canvas" width="400" height="300"></canvas>
<div class="fixed-bottom mb-3 d-flex justify-content-between align-items-end pf-pointer-events-none">
<div class="rounded invisible container py-1 px-3 ml-3" id="pf-fps-label"></div>
<div id="pf-toolbar">
<div class="btn-group" role="group" aria-label="Zoom">
<button id="pf-zoom-out-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">remove</span>
</button>
<button id="pf-zoom-in-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">add</span>
</button>
</div>
{{>partials/rotate.html}}
<button id="pf-zoom-pulse-button" type="button" title="Pulse Zoom"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">play_arrow</span>
</button>
<button id="pf-screenshot-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button">
<span class="pf-material-icons">photo_camera</span>
</button>
<div id="pf-settings-container">
<div id="pf-settings" class="card mb-4 pf-invisible">
<div class="card-body">
<button id="pf-settings-close-button" type="button" class="close"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<form>
<div class="form-group">
<label for="pf-select-file">Font</label>
<select id="pf-select-file" class="form-control custom-select">
<option value="eb-garamond" selected>EB Garamond</option>
<option value="open-sans">Open Sans</option>
<option value="nimbus-sans">Nimbus Sans</option>
<option value="inter-ui">Inter UI</option>
<option value="load-custom">Load File&hellip;</option>
</select>
<input id="pf-file-select" type="file">
</div>
<div class="form-group">
<label for="pf-aa-level-select">Antialiasing</label>
<select id="pf-aa-level-select" class="form-control custom-select">
<option value="none">None</option>
<option value="ssaa-2">2&times;SSAA</option>
<option value="ssaa-4">4&times;SSAA</option>
<option value="xcaa" selected>XCAA</option>
</select>
</div>
<div class="form-group">
<label for="pf-hinting-select">Hinting</label>
<select id="pf-hinting-select"
class="form-control custom-select">
<option value="none">None</option>
<option value="slight" selected>Slight</option>
</select>
</div>
<div class="form-group">
<label for="pf-subpixel-aa-select">Subpixel AA</label>
<select id="pf-subpixel-aa-select"
class="form-control custom-select">
<option value="none">None</option>
<option value="freetype" selected>FreeType-style</option>
<option value="core-graphics">Core Graphics-style</option>
</select>
</div>
<div class="form-group row justify-content-between">
{{>partials/switch.html id="pf-gamma-correction"
title="Gamma Correction"}}
</div>
<div class="form-group row justify-content-between">
{{>partials/switch.html id="pf-stem-darkening"
title="Stem Darkening"}}
</div>
<div class="form-group">
<label for="pf-embolden">Faux Bold</label>
<input class="form-control" id="pf-embolden" type="range"
min="-0.015" max="0.015" step="0.0005" value="0.0"
autocomplete="off">
</div>
</form>
</div>
<div class="pf-arrow-box"></div>
</div>
</div>
<button id="pf-settings-button" type="button"
class="btn btn-outline-secondary pf-toolbar-button" aria-expanded="false"
aria-controls="#pf-settings">
<span class="pf-material-icons">settings</span>
</button>
</div>
</div>
<div class="modal fade" id="pf-edit-text-modal" tabindex="-1" role="dialog"
aria-labelledby="pf-edit-text-label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="pf-edit-text-label">Edit Text</h5>
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<textarea id="pf-edit-text-area" class="form-control" rows="16">
</textarea>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-dismiss="modal">Cancel</button>
<button type="button"
class="btn btn-primary" id="pf-edit-text-ok-button">OK</button>
</div>
</div>
</div>
</div>
{{>partials/spinner.html}}
</body>
</html>

View File

@ -1,44 +0,0 @@
{
"name": "pathfinder-demo",
"version": "0.1.0",
"description": "Demo for Pathfinder 2",
"scripts": {
"build": "webpack",
"build-watch": "webpack --watch",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Patrick Walton <pcwalton@mimiga.net>",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@types/base64-js": "^1.2.5",
"@types/gl-matrix": "^2.4.0",
"@types/lodash": "^4.14.104",
"@types/node": "^8.9.4",
"@types/opentype.js": "0.0.0",
"@types/papaparse": "^4.1.33",
"@types/webgl-ext": "0.0.29",
"base64-js": "^1.2.3",
"bootstrap": "^4.0.0",
"gl-matrix": "^2.4.0",
"handlebars": "^4.0.11",
"handlebars-loader": "^1.6.0",
"handlebars-webpack-plugin": "^1.3.2",
"html-loader": "^0.5.5",
"image-ssim": "^0.2.0",
"jquery": "^3.3.1",
"lodash": "^4.17.5",
"opentype.js": "^0.7.3",
"papaparse": "^4.3.7",
"parse-color": "^1.0.0",
"path-data-polyfill.js": "^1.0.2",
"popper.js": "^1.13.0",
"rustdoc-webpack-plugin": "file:./build/rustdoc-webpack-plugin",
"ts-loader": "^2.3.7",
"typescript": "^2.7.2"
},
"devDependencies": {
"tslint": "^5.9.1",
"tslint-loader": "^3.5.3",
"webpack": "^3.11.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,167 +0,0 @@
// pathfinder/client/src/aa-strategy.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import {createFramebuffer, createFramebufferColorTexture, UniformMap} from './gl-utils';
import {createFramebufferDepthTexture} from './gl-utils';
import {Renderer} from './renderer';
import {unwrapNull} from './utils';
import {DemoView} from './view';
const SUBPIXEL_AA_KERNELS: {readonly [kind in SubpixelAAType]: glmatrix.vec4} = {
// These intentionally do not precisely match what Core Graphics does (a Lanczos function),
// because we don't want any ringing artefacts.
'core-graphics': glmatrix.vec4.clone([0.033165660, 0.102074051, 0.221434336, 0.286651906]),
'freetype': glmatrix.vec4.clone([0.0, 0.031372549, 0.301960784, 0.337254902]),
'none': glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]),
};
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';
export type DirectRenderingMode = 'none' | 'conservative' | 'color';
export type SubpixelAAType = 'none' | 'freetype' | 'core-graphics';
export type GammaCorrectionMode = 'off' | 'on';
export type StemDarkeningMode = 'none' | 'dark';
export interface TileInfo {
size: glmatrix.vec2;
position: glmatrix.vec2;
}
export abstract class AntialiasingStrategy {
// The type of direct rendering that should occur, if any.
abstract readonly directRenderingMode: DirectRenderingMode;
// How many rendering passes this AA strategy requires.
abstract readonly passCount: number;
protected subpixelAA: SubpixelAAType;
constructor(subpixelAA: SubpixelAAType) {
this.subpixelAA = subpixelAA;
}
// Prepares any OpenGL data. This is only called on startup and canvas resize.
init(renderer: Renderer): void {
this.setFramebufferSize(renderer);
}
// Uploads any mesh data. This is called whenever a new set of meshes is supplied.
abstract attachMeshes(renderer: Renderer): void;
// This is called whenever the framebuffer has changed.
abstract setFramebufferSize(renderer: Renderer): void;
// Returns the transformation matrix that should be applied when directly rendering.
abstract get transform(): glmatrix.mat4;
// Called before rendering.
//
// Typically, this redirects rendering to a framebuffer of some sort.
abstract prepareForRendering(renderer: Renderer): void;
// Called before directly rendering.
//
// Typically, this redirects rendering to a framebuffer of some sort.
abstract prepareForDirectRendering(renderer: Renderer): void;
// Called before directly rendering a single object.
abstract prepareToRenderObject(renderer: Renderer, objectIndex: number): void;
abstract finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void;
// Called after direct rendering.
//
// This usually performs the actual antialiasing.
abstract antialiasObject(renderer: Renderer, objectIndex: number): void;
// Called after antialiasing each object.
abstract finishAntialiasingObject(renderer: Renderer, objectIndex: number): void;
// Called before rendering each object directly.
abstract resolveAAForObject(renderer: Renderer, objectIndex: number): void;
// Called after antialiasing.
//
// This usually blits to the real framebuffer.
abstract resolve(pass: number, renderer: Renderer): void;
setSubpixelAAKernelUniform(renderer: Renderer, uniforms: UniformMap): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const kernel = SUBPIXEL_AA_KERNELS[this.subpixelAA];
gl.uniform4f(uniforms.uKernel, kernel[0], kernel[1], kernel[2], kernel[3]);
}
worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
return glmatrix.mat4.create();
}
}
export class NoAAStrategy extends AntialiasingStrategy {
framebufferSize: glmatrix.vec2;
get passCount(): number {
return 1;
}
constructor(level: number, subpixelAA: SubpixelAAType) {
super(subpixelAA);
this.framebufferSize = glmatrix.vec2.create();
}
attachMeshes(renderer: Renderer) {}
setFramebufferSize(renderer: Renderer) {
this.framebufferSize = renderer.destAllocatedSize;
}
get transform(): glmatrix.mat4 {
return glmatrix.mat4.create();
}
prepareForRendering(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
renderer.setDrawViewport();
gl.disable(gl.SCISSOR_TEST);
}
prepareForDirectRendering(renderer: Renderer): void {}
prepareToRenderObject(renderer: Renderer, objectIndex: number): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
renderer.setDrawViewport();
gl.disable(gl.SCISSOR_TEST);
}
finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {}
antialiasObject(renderer: Renderer, objectIndex: number): void {}
finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {}
resolveAAForObject(renderer: Renderer): void {}
resolve(pass: number, renderer: Renderer): void {}
get directRenderingMode(): DirectRenderingMode {
return 'color';
}
}

View File

@ -1,398 +0,0 @@
// pathfinder/client/src/app-controller.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import {AntialiasingStrategyName, GammaCorrectionMode, StemDarkeningMode} from "./aa-strategy";
import {SubpixelAAType} from "./aa-strategy";
import {FilePickerView} from "./file-picker";
import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader';
import {expectNotNull, unwrapNull, unwrapUndef} from './utils';
import {DemoView, Timings, TIMINGS} from "./view";
const AREA_LUT_URI: string = "/textures/area-lut.png";
const GAMMA_LUT_URI: string = "/textures/gamma-lut.png";
const SWITCHES: SwitchMap = {
gammaCorrection: {
defaultValue: 'on',
id: 'pf-gamma-correction',
offValue: 'off',
onValue: 'on',
switchInputsName: 'gammaCorrectionSwitchInputs',
},
stemDarkening: {
defaultValue: 'dark',
id: 'pf-stem-darkening',
offValue: 'none',
onValue: 'dark',
switchInputsName: 'stemDarkeningSwitchInputs',
},
};
interface SwitchDescriptor {
id: string;
switchInputsName: keyof Switches;
onValue: string;
offValue: string;
defaultValue: string;
}
interface SwitchMap {
gammaCorrection: SwitchDescriptor;
stemDarkening: SwitchDescriptor;
}
export interface AAOptions {
gammaCorrection: GammaCorrectionMode;
stemDarkening: StemDarkeningMode;
subpixelAA: SubpixelAAType;
}
export interface SwitchInputs {
on: HTMLInputElement;
off: HTMLInputElement;
}
interface Switches {
gammaCorrectionSwitchInputs: SwitchInputs | null;
stemDarkeningSwitchInputs: SwitchInputs | null;
}
export abstract class AppController {
protected canvas!: HTMLCanvasElement;
protected selectFileElement: HTMLSelectElement | null = null;
protected screenshotButton: HTMLButtonElement | null = null;
start(): void {
this.selectFileElement = document.getElementById('pf-select-file') as HTMLSelectElement |
null;
}
protected loadInitialFile(builtinFileURI: string): void {
if (this.selectFileElement != null) {
const selectedOption = this.selectFileElement.selectedOptions[0] as HTMLOptionElement;
this.fetchFile(selectedOption.value, builtinFileURI);
} else {
this.fetchFile(this.defaultFile, builtinFileURI);
}
}
protected fetchFile(file: string, builtinFileURI: string): Promise<void> {
return new Promise(resolve => {
window.fetch(`${builtinFileURI}/${file}`)
.then(response => response.arrayBuffer())
.then(data => {
this.fileLoaded(data, file);
resolve();
});
});
}
protected abstract fileLoaded(data: ArrayBuffer, builtinName: string | null): void;
protected abstract get defaultFile(): string;
}
export abstract class DemoAppController<View extends DemoView> extends AppController
implements Switches {
view!: Promise<View>;
gammaCorrectionSwitchInputs: SwitchInputs | null = null;
stemDarkeningSwitchInputs: SwitchInputs | null = null;
protected abstract readonly builtinFileURI: string;
protected filePickerView: FilePickerView | null = null;
protected aaLevelSelect: HTMLSelectElement | null = null;
protected subpixelAASelect: HTMLSelectElement | null = null;
private fpsLabel: HTMLElement | null = null;
constructor() {
super();
}
start() {
super.start();
this.initPopup('pf-settings', 'pf-settings-button', 'pf-settings-close-button');
this.initPopup('pf-rotate-slider-card', 'pf-rotate-button', 'pf-rotate-close-button');
const screenshotButton = document.getElementById('pf-screenshot-button') as
HTMLButtonElement | null;
if (screenshotButton != null) {
screenshotButton.addEventListener('click', () => {
this.view.then(view => view.queueScreenshot());
}, false);
}
const zoomInButton = document.getElementById('pf-zoom-in-button') as HTMLButtonElement |
null;
if (zoomInButton != null) {
zoomInButton.addEventListener('click', () => {
this.view.then(view => view.zoomIn());
}, false);
}
const zoomOutButton = document.getElementById('pf-zoom-out-button') as HTMLButtonElement |
null;
if (zoomOutButton != null) {
zoomOutButton.addEventListener('click', () => {
this.view.then(view => view.zoomOut());
}, false);
}
const zoomPulseButton = document.getElementById('pf-zoom-pulse-button') as
HTMLButtonElement | null;
if (zoomPulseButton != null) {
zoomPulseButton.addEventListener('click', () => {
this.view.then(view => view.zoomPulse());
}, false);
}
const rotateSlider = document.getElementById('pf-rotate-slider') as HTMLInputElement |
null;
if (rotateSlider != null) {
rotateSlider.addEventListener('input', event => {
this.view.then(view => {
view.rotate((event.target as HTMLInputElement).valueAsNumber);
});
}, false);
}
const vrButton = document.getElementById('pf-vr') as HTMLInputElement |
null;
if (vrButton != null) {
vrButton.addEventListener('click', event => {
this.view.then(view => {
view.enterVR();
});
}, false);
}
this.filePickerView = FilePickerView.create();
if (this.filePickerView != null) {
this.filePickerView.onFileLoaded = fileData => this.fileLoaded(fileData, null);
}
if (this.selectFileElement != null) {
this.selectFileElement
.addEventListener('click', event => this.fileSelectionChanged(event), false);
}
this.fpsLabel = document.getElementById('pf-fps-label');
const shaderLoader = new ShaderLoader;
shaderLoader.load();
const areaLUTPromise = this.loadTexture(AREA_LUT_URI);
const gammaLUTPromise = this.loadTexture(GAMMA_LUT_URI);
const promises: any[] = [
areaLUTPromise,
gammaLUTPromise,
shaderLoader.common,
shaderLoader.shaders,
];
this.view = Promise.all(promises).then(assets => {
return this.createView(assets[0], assets[1], assets[2], assets[3]);
});
this.aaLevelSelect = document.getElementById('pf-aa-level-select') as
(HTMLSelectElement | null);
if (this.aaLevelSelect != null)
this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false);
this.subpixelAASelect = document.getElementById('pf-subpixel-aa-select') as
(HTMLSelectElement | null);
if (this.subpixelAASelect != null)
this.subpixelAASelect.addEventListener('change', () => this.updateAALevel(), false);
// The event listeners here use `window.setTimeout()` because jQuery won't fire the "live"
// click listener that Bootstrap sets up until the event bubbles up to the document. This
// click listener is what toggles the `checked` attribute, so we have to wait until it
// fires before updating the antialiasing settings.
for (const switchName of Object.keys(SWITCHES) as Array<keyof SwitchMap>) {
const switchInputsName = SWITCHES[switchName].switchInputsName;
const switchID = SWITCHES[switchName].id;
const switchOnInput = document.getElementById(`${switchID}-select-on`);
const switchOffInput = document.getElementById(`${switchID}-select-off`);
if (switchOnInput != null && switchOffInput != null) {
this[switchInputsName] = {
off: switchOffInput as HTMLInputElement,
on: switchOnInput as HTMLInputElement,
};
} else {
this[switchInputsName] = null;
}
const buttons = document.getElementById(`${switchID}-buttons`) as HTMLElement | null;
if (buttons == null)
continue;
buttons.addEventListener('click', () => {
window.setTimeout(() => this.updateAALevel(), 0);
}, false);
}
this.updateAALevel();
}
newTimingsReceived(timings: Partial<Timings>) {
if (this.fpsLabel == null)
return;
while (this.fpsLabel.lastChild != null)
this.fpsLabel.removeChild(this.fpsLabel.lastChild);
for (const timing of Object.keys(timings) as Array<keyof Timings>) {
const tr = document.createElement('div');
tr.classList.add('row');
const keyTD = document.createElement('div');
const valueTD = document.createElement('div');
keyTD.classList.add('col');
valueTD.classList.add('col');
keyTD.appendChild(document.createTextNode(TIMINGS[timing]));
valueTD.appendChild(document.createTextNode(timings[timing] + " ms"));
tr.appendChild(keyTD);
tr.appendChild(valueTD);
this.fpsLabel.appendChild(tr);
}
this.fpsLabel.classList.remove('invisible');
}
protected updateAALevel(): Promise<void> {
let aaType: AntialiasingStrategyName, aaLevel: number;
if (this.aaLevelSelect != null) {
const selectedOption = this.aaLevelSelect.selectedOptions[0];
const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
aaType = aaValues[1] as AntialiasingStrategyName;
aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2], 10);
} else {
aaType = 'none';
aaLevel = 0;
}
this.updateUIForAALevelChange(aaType, aaLevel);
const aaOptions: Partial<AAOptions> = {};
for (const switchName of Object.keys(SWITCHES) as Array<keyof SwitchMap>) {
const switchDescriptor = SWITCHES[switchName];
const switchInputsName = switchDescriptor.switchInputsName;
const switchInputs = this[switchInputsName];
if (switchInputs == null)
aaOptions[switchName] = switchDescriptor.defaultValue as any;
else if (switchInputs.on.checked && !switchInputs.on.disabled)
aaOptions[switchName] = switchDescriptor.onValue as any;
else
aaOptions[switchName] = switchDescriptor.offValue as any;
}
if (this.subpixelAASelect != null) {
const selectedOption = this.subpixelAASelect.selectedOptions[0];
aaOptions.subpixelAA = selectedOption.value as SubpixelAAType;
} else {
aaOptions.subpixelAA = 'none';
}
return this.view.then(view => {
view.setAntialiasingOptions(aaType, aaLevel, aaOptions as AAOptions);
});
}
protected updateUIForAALevelChange(aaType: AntialiasingStrategyName, aaLevel: number): void {
// Overridden by subclasses.
}
protected abstract createView(areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>):
View;
private initPopup(cardID: string, popupButtonID: string, closeButtonID: string): void {
const card = document.getElementById(cardID) as HTMLElement | null;
const button = document.getElementById(popupButtonID) as HTMLButtonElement | null;
const closeButton = document.getElementById(closeButtonID) as HTMLButtonElement | null;
if (button != null) {
button.addEventListener('click', event => {
event.stopPropagation();
unwrapNull(card).classList.toggle('pf-invisible');
}, false);
}
if (closeButton != null) {
closeButton.addEventListener('click', () => {
unwrapNull(card).classList.add('pf-invisible');
}, false);
}
if (card == null)
return;
document.body.addEventListener('click', event => {
let element = event.target as Element | null;
while (element != null) {
if (element === card)
return;
element = element.parentElement;
}
card.classList.add('pf-invisible');
}, false);
}
private loadTexture(uri: string): Promise<HTMLImageElement> {
return window.fetch(uri)
.then(response => response.blob())
.then(blob => {
const imgElement = document.createElement('img');
imgElement.src = URL.createObjectURL(blob);
const promise: Promise<HTMLImageElement> = new Promise(resolve => {
imgElement.addEventListener('load', () => {
resolve(imgElement);
}, false);
});
return promise;
});
}
private fileSelectionChanged(event: Event): void {
const selectFileElement = event.currentTarget as HTMLSelectElement;
const selectedOption = selectFileElement.selectedOptions[0] as HTMLOptionElement;
if (selectedOption.value === 'load-custom' && this.filePickerView != null) {
this.filePickerView.open();
const oldSelectedIndex = selectFileElement.selectedIndex;
const newOption = document.createElement('option');
newOption.id = 'pf-custom-option-placeholder';
newOption.appendChild(document.createTextNode("Custom"));
selectFileElement.insertBefore(newOption, selectedOption);
selectFileElement.selectedIndex = oldSelectedIndex;
return;
}
// Remove the "Custom…" placeholder if it exists.
const placeholder = document.getElementById('pf-custom-option-placeholder');
if (placeholder != null)
selectFileElement.removeChild(placeholder);
// Fetch the file.
this.fetchFile(selectedOption.value, this.builtinFileURI);
}
}
export function setSwitchInputsValue(switchInputs: SwitchInputs, on: boolean): void {
switchInputs.on.checked = on;
switchInputs.off.checked = !on;
}

View File

@ -1,160 +0,0 @@
// pathfinder/client/src/atlas.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {setTextureParameters} from './gl-utils';
import {calculatePixelDescent, calculatePixelRectForGlyph, calculatePixelXMin, Hint} from './text';
import {PathfinderFont, UnitMetrics} from './text';
import {unwrapNull} from './utils';
import {RenderContext} from './view';
export const SUBPIXEL_GRANULARITY: number = 4;
export const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(2048, 4096);
export class Atlas {
private _texture: WebGLTexture | null;
private _usedSize: glmatrix.vec2;
constructor() {
this._texture = null;
this._usedSize = glmatrix.vec2.create();
}
layoutGlyphs(glyphs: AtlasGlyph[],
font: PathfinderFont,
pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
emboldenAmount: glmatrix.vec2):
void {
let nextOrigin = glmatrix.vec2.fromValues(1.0, 1.0);
let shelfBottom = 2.0;
for (const glyph of glyphs) {
// Place the glyph, and advance the origin.
const metrics = font.metricsForGlyph(glyph.glyphKey.id);
if (metrics == null)
continue;
const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount);
glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit);
let pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics,
pixelOrigin,
pixelsPerUnit,
hint)[2] + 1.0;
// If the glyph overflowed the shelf, make a new one and reposition the glyph.
if (nextOrigin[0] > ATLAS_SIZE[0]) {
nextOrigin = glmatrix.vec2.clone([1.0, shelfBottom + 1.0]);
glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit);
pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics,
pixelOrigin,
pixelsPerUnit,
hint)[2] + 1.0;
}
// Grow the shelf as necessary.
const glyphBottom = calculatePixelRectForGlyph(unitMetrics,
pixelOrigin,
pixelsPerUnit,
hint)[3];
shelfBottom = Math.max(shelfBottom, glyphBottom + 1.0);
}
// FIXME(pcwalton): Could be more precise if we don't have a full row.
this._usedSize = glmatrix.vec2.clone([ATLAS_SIZE[0], shelfBottom]);
}
ensureTexture(renderContext: RenderContext): WebGLTexture {
if (this._texture != null)
return this._texture;
const gl = renderContext.gl;
const texture = unwrapNull(gl.createTexture());
this._texture = texture;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
ATLAS_SIZE[0],
ATLAS_SIZE[1],
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null);
setTextureParameters(gl, gl.NEAREST);
return texture;
}
get usedSize(): glmatrix.vec2 {
return this._usedSize;
}
}
export class AtlasGlyph {
readonly glyphStoreIndex: number;
readonly glyphKey: GlyphKey;
readonly origin: glmatrix.vec2;
constructor(glyphStoreIndex: number, glyphKey: GlyphKey) {
this.glyphStoreIndex = glyphStoreIndex;
this.glyphKey = glyphKey;
this.origin = glmatrix.vec2.create();
}
calculateSubpixelOrigin(pixelsPerUnit: number): glmatrix.vec2 {
const pixelOrigin = glmatrix.vec2.create();
glmatrix.vec2.scale(pixelOrigin, this.origin, pixelsPerUnit);
glmatrix.vec2.round(pixelOrigin, pixelOrigin);
if (this.glyphKey.subpixel != null)
pixelOrigin[0] += this.glyphKey.subpixel / SUBPIXEL_GRANULARITY;
return pixelOrigin;
}
setPixelLowerLeft(pixelLowerLeft: glmatrix.vec2, metrics: UnitMetrics, pixelsPerUnit: number):
void {
const pixelXMin = calculatePixelXMin(metrics, pixelsPerUnit);
const pixelDescent = calculatePixelDescent(metrics, pixelsPerUnit);
const pixelOrigin = glmatrix.vec2.clone([pixelLowerLeft[0] - pixelXMin,
pixelLowerLeft[1] - pixelDescent]);
this.setPixelOrigin(pixelOrigin, pixelsPerUnit);
}
private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void {
glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit);
}
get pathID(): number {
if (this.glyphKey.subpixel == null)
return this.glyphStoreIndex + 1;
return this.glyphStoreIndex * SUBPIXEL_GRANULARITY + this.glyphKey.subpixel + 1;
}
}
export class GlyphKey {
readonly id: number;
readonly subpixel: number | null;
constructor(id: number, subpixel: number | null) {
this.id = id;
this.subpixel = subpixel;
}
get sortKey(): number {
return this.subpixel == null ? this.id : this.id * SUBPIXEL_GRANULARITY + this.subpixel;
}
}

View File

@ -1,653 +0,0 @@
// pathfinder/client/src/benchmark.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import * as opentype from "opentype.js";
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {SubpixelAAType} from "./aa-strategy";
import {AppController, DemoAppController, setSwitchInputsValue} from "./app-controller";
import PathfinderBufferTexture from './buffer-texture';
import {OrthographicCamera} from './camera';
import {UniformMap} from './gl-utils';
import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
import {PathTransformBuffers, Renderer} from './renderer';
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
import SSAAStrategy from './ssaa-strategy';
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
import {SVGRenderer} from './svg-renderer';
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
import {computeStemDarkeningAmount, TextRun} from "./text";
import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils";
import {DemoView, Timings} from "./view";
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const DEFAULT_FONT: string = 'nimbus-sans';
const DEFAULT_SVG_FILE: string = 'tiger';
const TEXT_COLOR: number[] = [0, 0, 0, 255];
// In milliseconds.
const MIN_RUNTIME: number = 100;
const MAX_RUNTIME: number = 3000;
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
xcaa: AdaptiveStencilMeshAAAStrategy,
};
interface BenchmarkModeMap<T> {
text: T;
svg: T;
}
type BenchmarkMode = 'text' | 'svg';
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
xcaa: typeof AdaptiveStencilMeshAAAStrategy;
}
interface TestParameter {
start: number;
stop: number;
step: number;
}
const DISPLAY_HEADER_LABELS: BenchmarkModeMap<string[]> = {
svg: ["Size (px)", "GPU time (ms)"],
text: ["Font size (px)", "GPU time per glyph (µs)"],
};
const TEST_SIZES: BenchmarkModeMap<TestParameter> = {
svg: { start: 64, stop: 2048, step: 16 },
text: { start: 6, stop: 200, step: 1 },
};
class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
font: PathfinderFont | null = null;
textRun: TextRun | null = null;
svgLoader!: SVGLoader;
mode!: BenchmarkMode;
protected get defaultFile(): string {
if (this.mode === 'text')
return DEFAULT_FONT;
return DEFAULT_SVG_FILE;
}
protected get builtinFileURI(): string {
if (this.mode === 'text')
return BUILTIN_FONT_URI;
return BUILTIN_SVG_URI;
}
private optionsModal!: HTMLDivElement;
private resultsModal!: HTMLDivElement;
private resultsTableHeader!: HTMLTableSectionElement;
private resultsTableBody!: HTMLTableSectionElement;
private resultsPartitioningTimeLabel!: HTMLSpanElement;
private glyphStore!: GlyphStore;
private baseMeshes!: PathfinderMeshPack;
private expandedMeshes!: ExpandedMeshData;
private size!: number;
private currentRun!: number;
private startTime!: number;
private elapsedTimes!: ElapsedTime[];
private partitionTime!: number;
start(): void {
super.start();
this.mode = 'text';
this.optionsModal = unwrapNull(document.getElementById('pf-benchmark-modal')) as
HTMLDivElement;
this.resultsModal = unwrapNull(document.getElementById('pf-benchmark-results-modal')) as
HTMLDivElement;
this.resultsTableHeader =
unwrapNull(document.getElementById('pf-benchmark-results-table-header')) as
HTMLTableSectionElement;
this.resultsTableBody =
unwrapNull(document.getElementById('pf-benchmark-results-table-body')) as
HTMLTableSectionElement;
this.resultsPartitioningTimeLabel =
unwrapNull(document.getElementById('pf-benchmark-results-partitioning-time')) as
HTMLSpanElement;
const resultsSaveCSVButton =
unwrapNull(document.getElementById('pf-benchmark-results-save-csv-button'));
resultsSaveCSVButton.addEventListener('click', () => this.saveCSV(), false);
const resultsCloseButton =
unwrapNull(document.getElementById('pf-benchmark-results-close-button'));
resultsCloseButton.addEventListener('click', () => {
window.jQuery(this.resultsModal).modal('hide');
}, false);
const runBenchmarkButton = unwrapNull(document.getElementById('pf-run-benchmark-button'));
runBenchmarkButton.addEventListener('click', () => this.runBenchmark(), false);
const aaLevelFormGroup = unwrapNull(document.getElementById('pf-aa-level-form-group')) as
HTMLDivElement;
const benchmarkTextForm = unwrapNull(document.getElementById('pf-benchmark-text-form')) as
HTMLFormElement;
const benchmarkSVGForm = unwrapNull(document.getElementById('pf-benchmark-svg-form')) as
HTMLFormElement;
window.jQuery(this.optionsModal).modal();
const benchmarkTextTab = document.getElementById('pf-benchmark-text-tab') as
HTMLAnchorElement;
const benchmarkSVGTab = document.getElementById('pf-benchmark-svg-tab') as
HTMLAnchorElement;
window.jQuery(benchmarkTextTab).on('shown.bs.tab', event => {
this.mode = 'text';
if (aaLevelFormGroup.parentElement != null)
aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup);
benchmarkTextForm.insertBefore(aaLevelFormGroup, benchmarkTextForm.firstChild);
this.modeChanged();
});
window.jQuery(benchmarkSVGTab).on('shown.bs.tab', event => {
this.mode = 'svg';
if (aaLevelFormGroup.parentElement != null)
aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup);
benchmarkSVGForm.insertBefore(aaLevelFormGroup, benchmarkSVGForm.firstChild);
this.modeChanged();
});
this.loadInitialFile(this.builtinFileURI);
}
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
switch (this.mode) {
case 'text':
this.textFileLoaded(fileData, builtinName);
return;
case 'svg':
this.svgFileLoaded(fileData, builtinName);
return;
}
}
protected createView(areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>):
BenchmarkTestView {
return new BenchmarkTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
private modeChanged(): void {
this.loadInitialFile(this.builtinFileURI);
if (this.aaLevelSelect != null)
this.aaLevelSelect.selectedIndex = 0;
if (this.subpixelAASelect != null)
this.subpixelAASelect.selectedIndex = 0;
this.updateAALevel();
}
private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
const font = new PathfinderFont(fileData, builtinName);
this.font = font;
const textRun = new TextRun(STRING, [0, 0], font);
textRun.layout();
this.textRun = textRun;
const textFrame = new TextFrame([textRun], font);
const glyphIDs = textFrame.allGlyphIDs;
glyphIDs.sort((a, b) => a - b);
this.glyphStore = new GlyphStore(font, glyphIDs);
this.glyphStore.partition().then(result => {
this.baseMeshes = result.meshes;
const partitionTime = result.time / this.glyphStore.glyphIDs.length * 1e6;
const timeLabel = this.resultsPartitioningTimeLabel;
while (timeLabel.firstChild != null)
timeLabel.removeChild(timeLabel.firstChild);
timeLabel.appendChild(document.createTextNode("" + partitionTime));
const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, glyphIDs);
this.expandedMeshes = expandedMeshes;
this.view.then(view => {
view.recreateRenderer();
view.attachMeshes([expandedMeshes.meshes]);
});
});
}
private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
this.svgLoader = new SVGLoader;
this.svgLoader.loadFile(fileData);
this.svgLoader.partition().then(meshes => {
this.view.then(view => {
view.recreateRenderer();
view.attachMeshes([new PathfinderPackedMeshes(meshes)]);
view.initCameraBounds(this.svgLoader.svgViewBox);
});
});
}
private reset(): void {
this.currentRun = 0;
this.startTime = Date.now();
}
private runBenchmark(): void {
window.jQuery(this.optionsModal).modal('hide');
this.reset();
this.elapsedTimes = [];
this.size = TEST_SIZES[this.mode].start;
this.view.then(view => this.runOneBenchmarkTest(view));
}
private runDone(): boolean {
const totalElapsedTime = Date.now() - this.startTime;
if (totalElapsedTime < MIN_RUNTIME)
return false;
if (totalElapsedTime >= MAX_RUNTIME)
return true;
// Compute median absolute devation.
const elapsedTime = unwrapUndef(_.last(this.elapsedTimes));
elapsedTime.times.sort((a, b) => a - b);
const median = unwrapNull(computeMedian(elapsedTime.times));
const absoluteDeviations = elapsedTime.times.map(time => Math.abs(time - median));
absoluteDeviations.sort((a, b) => a - b);
const medianAbsoluteDeviation = unwrapNull(computeMedian(absoluteDeviations));
const medianAbsoluteDeviationFraction = medianAbsoluteDeviation / median;
return medianAbsoluteDeviationFraction <= 0.01;
}
private runOneBenchmarkTest(view: BenchmarkTestView): void {
const renderedPromise = new Promise<number>((resolve, reject) => {
view.renderingPromiseCallback = resolve;
view.size = this.size;
});
renderedPromise.then(elapsedTime => {
if (this.currentRun === 0)
this.elapsedTimes.push(new ElapsedTime(this.size));
unwrapUndef(_.last(this.elapsedTimes)).times.push(elapsedTime);
this.currentRun++;
if (this.runDone()) {
this.reset();
if (this.size >= TEST_SIZES[this.mode].stop) {
this.showResults();
return;
}
this.size += TEST_SIZES[this.mode].step;
}
this.runOneBenchmarkTest(view);
});
}
private showResults(): void {
while (this.resultsTableHeader.lastChild != null)
this.resultsTableHeader.removeChild(this.resultsTableHeader.lastChild);
while (this.resultsTableBody.lastChild != null)
this.resultsTableBody.removeChild(this.resultsTableBody.lastChild);
const tr = document.createElement('tr');
for (const headerLabel of DISPLAY_HEADER_LABELS[this.mode]) {
const th = document.createElement('th');
th.appendChild(document.createTextNode(headerLabel));
tr.appendChild(th);
}
this.resultsTableHeader.appendChild(tr);
for (const elapsedTime of this.elapsedTimes) {
const tr = document.createElement('tr');
const sizeTH = document.createElement('th');
const timeTD = document.createElement('td');
sizeTH.appendChild(document.createTextNode("" + elapsedTime.size));
const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time;
timeTD.appendChild(document.createTextNode("" + time));
sizeTH.scope = 'row';
tr.appendChild(sizeTH);
tr.appendChild(timeTD);
this.resultsTableBody.appendChild(tr);
}
window.jQuery(this.resultsModal).modal();
}
private saveCSV(): void {
let output = "Size,Time\n";
for (const elapsedTime of this.elapsedTimes) {
const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time;
output += `${elapsedTime.size},${time}\n`;
}
// https://stackoverflow.com/a/30832210
const file = new Blob([output], {type: 'text/csv'});
const a = document.createElement('a');
const url = URL.createObjectURL(file);
a.href = url;
a.download = "pathfinder-benchmark-results.csv";
document.body.appendChild(a);
a.click();
window.setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);
}
}
class BenchmarkTestView extends DemoView {
renderer!: BenchmarkTextRenderer | BenchmarkSVGRenderer;
readonly appController: BenchmarkAppController;
renderingPromiseCallback: ((time: number) => void) | null = null;
get camera(): OrthographicCamera {
return this.renderer.camera;
}
set size(newSize: number) {
if (this.renderer instanceof BenchmarkTextRenderer) {
this.renderer.pixelsPerEm = newSize;
} else if (this.renderer instanceof BenchmarkSVGRenderer) {
const camera = this.renderer.camera;
camera.zoomToSize(newSize);
camera.center();
}
}
constructor(appController: BenchmarkAppController,
areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.recreateRenderer();
this.resizeToFit(true);
}
recreateRenderer(): void {
switch (this.appController.mode) {
case 'svg':
this.renderer = new BenchmarkSVGRenderer(this);
break;
case 'text':
this.renderer = new BenchmarkTextRenderer(this);
break;
}
}
initCameraBounds(viewBox: glmatrix.vec4): void {
if (this.renderer instanceof BenchmarkSVGRenderer)
this.renderer.initCameraBounds(viewBox);
}
protected renderingFinished(): void {
if (this.renderingPromiseCallback == null)
return;
const appController = this.appController;
let time = this.renderer.lastTimings.rendering * 1000.0;
if (appController.mode === 'text')
time /= unwrapNull(appController.textRun).glyphIDs.length;
this.renderingPromiseCallback(time);
}
}
class BenchmarkTextRenderer extends Renderer {
renderContext!: BenchmarkTestView;
camera: OrthographicCamera;
needsStencil: boolean = false;
isMulticolor: boolean = false;
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
}
get fgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
}
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.renderContext.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
get allowSubpixelAA(): boolean {
return true;
}
get emboldenAmount(): glmatrix.vec2 {
return this.stemDarkeningAmount;
}
get pixelsPerEm(): number {
return this._pixelsPerEm;
}
set pixelsPerEm(newPixelsPerEm: number) {
this._pixelsPerEm = newPixelsPerEm;
this.uploadPathTransforms(1);
this.renderContext.setDirty();
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get worldTransform(): glmatrix.mat4 {
const canvas = this.renderContext.canvas;
const transform = glmatrix.mat4.create();
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
const pixelsPerUnit = this.pixelsPerUnit;
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
return transform;
}
protected get objectCount(): number {
return this.meshBuffers == null ? 0 : this.meshBuffers.length;
}
private _pixelsPerEm: number = 32.0;
private get pixelsPerUnit(): number {
const font = unwrapNull(this.renderContext.appController.font);
return this._pixelsPerEm / font.opentypeFont.unitsPerEm;
}
private get stemDarkeningAmount(): glmatrix.vec2 {
return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
}
constructor(renderContext: BenchmarkTestView) {
super(renderContext);
this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true });
this.camera.onPan = () => renderContext.setDirty();
this.camera.onZoom = () => renderContext.setDirty();
}
attachMeshes(meshes: PathfinderPackedMeshes[]): void {
super.attachMeshes(meshes);
this.uploadPathColors(1);
this.uploadPathTransforms(1);
}
pathCountForObject(objectIndex: number): number {
return STRING.length;
}
pathBoundingRects(objectIndex: number): Float32Array {
const appController = this.renderContext.appController;
const font = unwrapNull(appController.font);
const boundingRects = new Float32Array((STRING.length + 1) * 4);
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
const metrics = font.metricsForGlyph(glyphID);
if (metrics == null)
continue;
boundingRects[(glyphIndex + 1) * 4 + 0] = metrics.xMin;
boundingRects[(glyphIndex + 1) * 4 + 1] = metrics.yMin;
boundingRects[(glyphIndex + 1) * 4 + 2] = metrics.xMax;
boundingRects[(glyphIndex + 1) * 4 + 3] = metrics.yMax;
}
return boundingRects;
}
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
const appController = this.renderContext.appController;
const canvas = this.renderContext.canvas;
const font = unwrapNull(appController.font);
const pathTransforms = this.createPathTransformBuffers(STRING.length);
let currentX = 0, currentY = 0;
const availableWidth = canvas.width / this.pixelsPerUnit;
const lineHeight = font.opentypeFont.lineHeight();
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
pathTransforms.st.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth;
if (currentX > availableWidth) {
currentX = 0;
currentY += lineHeight;
}
}
return pathTransforms;
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected compositeIfNecessary(): void {}
protected updateTimings(timings: Timings): void {
// TODO(pcwalton)
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const pathColors = new Uint8Array(4 * (STRING.length + 1));
for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4);
return pathColors;
}
protected directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
}
class BenchmarkSVGRenderer extends SVGRenderer {
renderContext!: BenchmarkTestView;
protected get loader(): SVGLoader {
return this.renderContext.appController.svgLoader;
}
protected get canvas(): HTMLCanvasElement {
return this.renderContext.canvas;
}
constructor(renderContext: BenchmarkTestView) {
super(renderContext, {sizeToFit: false});
}
}
function computeMedian(values: number[]): number | null {
if (values.length === 0)
return null;
const mid = values.length / 2;
if (values.length % 2 === 1)
return values[Math.floor(mid)];
return lerp(values[mid - 1], values[mid], 0.5);
}
class ElapsedTime {
readonly size: number;
readonly times: number[];
constructor(size: number) {
this.size = size;
this.times = [];
}
get time(): number {
const median = computeMedian(this.times);
return median == null ? 0.0 : median;
}
get timeInMS(): number {
return this.time / 1000.0;
}
}
function main() {
const controller = new BenchmarkAppController;
window.addEventListener('load', () => controller.start(), false);
}
main();

View File

@ -1,137 +0,0 @@
// pathfinder/client/src/buffer-texture.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import {setTextureParameters, UniformMap} from './gl-utils';
import {assert, expectNotNull} from './utils';
export default class PathfinderBufferTexture {
readonly texture: WebGLTexture;
readonly uniformName: string;
private size: glmatrix.vec2;
private glType: number;
private destroyed: boolean;
constructor(gl: WebGLRenderingContext, uniformName: string) {
this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!");
this.size = glmatrix.vec2.create();
this.uniformName = uniformName;
this.glType = 0;
this.destroyed = false;
}
destroy(gl: WebGLRenderingContext): void {
assert(!this.destroyed, "Buffer texture destroyed!");
gl.deleteTexture(this.texture);
this.destroyed = true;
}
upload(gl: WebGLRenderingContext, data: Float32Array | Uint8Array): void {
assert(!this.destroyed, "Buffer texture destroyed!");
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE;
const areaNeeded = Math.ceil(data.length / 4);
if (glType !== this.glType || areaNeeded > this.area) {
const sideLength = nextPowerOfTwo(Math.ceil(Math.sqrt(areaNeeded)));
this.size = glmatrix.vec2.clone([sideLength, sideLength]);
this.glType = glType;
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
sideLength,
sideLength,
0,
gl.RGBA,
glType,
null);
setTextureParameters(gl, gl.NEAREST);
}
const mainDimensions = glmatrix.vec4.clone([
0,
0,
this.size[0],
(areaNeeded / this.size[0]) | 0,
]);
const remainderDimensions = glmatrix.vec4.clone([
0,
mainDimensions[3],
areaNeeded % this.size[0],
1,
]);
const splitIndex = mainDimensions[2] * mainDimensions[3] * 4;
if (mainDimensions[2] > 0 && mainDimensions[3] > 0) {
gl.texSubImage2D(gl.TEXTURE_2D,
0,
mainDimensions[0],
mainDimensions[1],
mainDimensions[2],
mainDimensions[3],
gl.RGBA,
this.glType,
data.slice(0, splitIndex));
}
if (remainderDimensions[2] > 0) {
// Round data up to a multiple of 4 elements if necessary.
let remainderLength = data.length - splitIndex;
let remainder: Float32Array | Uint8Array;
if (remainderLength % 4 === 0) {
remainder = data.slice(splitIndex);
} else {
remainderLength += 4 - remainderLength % 4;
remainder = new (data.constructor as any)(remainderLength);
remainder.set(data.slice(splitIndex));
}
gl.texSubImage2D(gl.TEXTURE_2D,
0,
remainderDimensions[0],
remainderDimensions[1],
remainderDimensions[2],
remainderDimensions[3],
gl.RGBA,
this.glType,
remainder);
}
}
bind(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number): void {
assert(!this.destroyed, "Buffer texture destroyed!");
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.uniform2i(uniforms[`${this.uniformName}Dimensions`], this.size[0], this.size[1]);
gl.uniform1i(uniforms[this.uniformName], textureUnit);
}
private get area(): number {
assert(!this.destroyed, "Buffer texture destroyed!");
return this.size[0] * this.size[1];
}
}
/// https://stackoverflow.com/a/466242
function nextPowerOfTwo(n: number): number {
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n + 1;
}

View File

@ -1,506 +0,0 @@
// pathfinder/client/src/camera.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {EPSILON, unwrapNull} from "./utils";
import {PathfinderView} from "./view";
const PIXELS_PER_LINE: number = 16.0;
const ORTHOGRAPHIC_ZOOM_SPEED: number = 1.0 / 100.0;
const ORTHOGRAPHIC_ZOOM_IN_FACTOR: number = 1.2;
const ORTHOGRAPHIC_ZOOM_OUT_FACTOR: number = 1.0 / ORTHOGRAPHIC_ZOOM_IN_FACTOR;
const ORTHOGRAPHIC_DEFAULT_MIN_SCALE: number = 0.01;
const ORTHOGRAPHIC_DEFAULT_MAX_SCALE: number = 1000.0;
const PERSPECTIVE_MOVEMENT_SPEED: number = 10.0;
const PERSPECTIVE_ROTATION_SPEED: number = 1.0 / 300.0;
const PERSPECTIVE_MOVEMENT_VECTORS: PerspectiveMovementVectors = _.fromPairs([
['W'.charCodeAt(0), glmatrix.vec3.fromValues(0, 0, PERSPECTIVE_MOVEMENT_SPEED)],
['A'.charCodeAt(0), glmatrix.vec3.fromValues(PERSPECTIVE_MOVEMENT_SPEED, 0, 0)],
['S'.charCodeAt(0), glmatrix.vec3.fromValues(0, 0, -PERSPECTIVE_MOVEMENT_SPEED)],
['D'.charCodeAt(0), glmatrix.vec3.fromValues(-PERSPECTIVE_MOVEMENT_SPEED, 0, 0)],
]);
const PERSPECTIVE_MOVEMENT_INTERVAL_DELAY: number = 10;
const PERSPECTIVE_INITIAL_TRANSLATION: glmatrix.vec3 =
glmatrix.vec3.clone([1750.0, 700.0, -1750.0]);
const PERSPECTIVE_INITIAL_ROTATION: glmatrix.vec2 = glmatrix.vec2.clone([Math.PI * 0.25, 0.0]);
const PERSPECTIVE_OUTER_COLLISION_EXTENT: number = 3000.0;
const PERSPECTIVE_HITBOX_RADIUS: number = 1.0;
const KEYCODES = ["W", "A", "S", "D"].map(x => x.charCodeAt(0));
export interface OrthographicCameraOptions {
fixed?: boolean;
minScale?: number;
maxScale?: number;
scaleBounds?: boolean;
ignoreBounds?: boolean;
}
export interface PerspectiveCameraOptions {
innerCollisionExtent?: number;
}
interface PerspectiveMovementVectors {
[keyCode: number]: glmatrix.vec3;
}
interface PerspectiveMovementKeys {
[keyCode: number]: boolean;
}
export interface CameraView {
readonly width: number;
readonly height: number;
readonly classList: DOMTokenList | null;
addEventListener<K extends keyof HTMLElementEventMap>(type: K,
listener: (this: HTMLCanvasElement,
ev: HTMLElementEventMap[K]) =>
any,
useCapture?: boolean): void;
getBoundingClientRect(): ClientRect;
}
export abstract class Camera {
protected canvas: CameraView;
constructor(canvas: CameraView) {
this.canvas = canvas;
}
abstract zoom(scale: number): void;
abstract zoomIn(): void;
abstract zoomOut(): void;
abstract rotate(newAngle: number): void;
}
export class OrthographicCamera extends Camera {
onPan: (() => void) | null;
onZoom: (() => void) | null;
onRotate: (() => void) | null;
translation!: glmatrix.vec2;
scale!: number;
rotationAngle!: number;
private _bounds: glmatrix.vec4;
private readonly fixed: boolean;
private readonly minScale: number;
private readonly maxScale: number;
private readonly scaleBounds: boolean;
private readonly ignoreBounds: boolean;
constructor(canvas: CameraView, options?: OrthographicCameraOptions) {
super(canvas);
if (options == null)
options = {};
this.fixed = !!options.fixed;
this.minScale = _.defaultTo(options.minScale, ORTHOGRAPHIC_DEFAULT_MIN_SCALE);
this.maxScale = _.defaultTo(options.maxScale, ORTHOGRAPHIC_DEFAULT_MAX_SCALE);
this.scaleBounds = !!options.scaleBounds;
this.ignoreBounds = !!options.ignoreBounds;
this.reset();
this._bounds = glmatrix.vec4.create();
if (!this.fixed) {
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false);
if (this.canvas.classList != null)
this.canvas.classList.add('pf-draggable');
} else {
if (this.canvas.classList != null)
this.canvas.classList.remove('pf-draggable');
}
this.onPan = null;
this.onZoom = null;
this.onRotate = null;
}
onWheel(event: MouseWheelEvent): void {
if (this.canvas == null)
throw new Error("onWheel() with no canvas?!");
event.preventDefault();
if (!event.ctrlKey) {
const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY);
if (event.deltaMode === event.DOM_DELTA_LINE)
glmatrix.vec2.scale(delta, delta, PIXELS_PER_LINE);
this.pan(delta);
return;
}
// Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel
const mouseLocation = glmatrix.vec2.fromValues(event.clientX, event.clientY);
const canvasLocation = this.canvas.getBoundingClientRect();
mouseLocation[0] -= canvasLocation.left;
mouseLocation[1] = canvasLocation.bottom - mouseLocation[1];
glmatrix.vec2.scale(mouseLocation, mouseLocation, window.devicePixelRatio);
const scale = 1.0 - event.deltaY * window.devicePixelRatio * ORTHOGRAPHIC_ZOOM_SPEED;
this.doZoom(scale, mouseLocation);
}
zoomToFit(): void {
const size = this.objectSize();
this.scale = Math.min(this.canvas.width / size[0], this.canvas.height / size[1]);
this.center();
}
center(): void {
const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
this.translation = glmatrix.vec2.create();
glmatrix.vec2.lerp(this.translation, upperLeft, lowerRight, 0.5);
glmatrix.vec2.scale(this.translation, this.translation, -this.scale);
this.translation[0] += this.canvas.width * 0.5;
this.translation[1] += this.canvas.height * 0.5;
}
zoomToSize(newSize: number): void {
this.reset();
const size = this.objectSize();
const length = Math.max(size[0], size[1]);
this.zoom(newSize / length);
}
zoom(scale: number): void {
this.doZoom(scale, this.centerPoint);
}
zoomIn(): void {
this.doZoom(ORTHOGRAPHIC_ZOOM_IN_FACTOR, this.centerPoint);
}
zoomOut(): void {
this.doZoom(ORTHOGRAPHIC_ZOOM_OUT_FACTOR, this.centerPoint);
}
rotate(newAngle: number): void {
this.rotationAngle = newAngle;
if (this.onRotate != null)
this.onRotate();
}
private objectSize(): glmatrix.vec2 {
const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
const width = this._bounds[2] - this._bounds[0];
const height = Math.abs(this._bounds[1] - this._bounds[3]);
return glmatrix.vec2.clone([width, height]);
}
private reset(): void {
this.translation = glmatrix.vec2.create();
this.scale = 1.0;
this.rotationAngle = 0.0;
}
private onMouseDown(event: MouseEvent): void {
if (this.canvas.classList != null)
this.canvas.classList.add('pf-grabbing');
}
private onMouseUp(event: MouseEvent): void {
if (this.canvas.classList != null)
this.canvas.classList.remove('pf-grabbing');
}
private onMouseMove(event: MouseEvent): void {
if ((event.buttons & 1) !== 0)
this.pan(glmatrix.vec2.fromValues(event.movementX, -event.movementY));
}
private pan(delta: glmatrix.vec2): void {
// Pan event.
glmatrix.vec2.scale(delta, delta, window.devicePixelRatio);
glmatrix.vec2.add(this.translation, this.translation, delta);
this.clampViewport();
if (this.onPan != null)
this.onPan();
}
private clampViewport() {
if (this.ignoreBounds)
return;
const bounds = glmatrix.vec4.clone(this.bounds);
if (!this.scaleBounds)
glmatrix.vec4.scale(bounds, bounds, this.scale);
for (let axis = 0; axis < 2; axis++) {
const viewportLength = axis === 0 ? this.canvas.width : this.canvas.height;
const axisBounds = [bounds[axis + 0], bounds[axis + 2]];
const boundsLength = axisBounds[1] - axisBounds[0];
if (viewportLength < boundsLength) {
// Viewport must be inside bounds.
this.translation[axis] = _.clamp(this.translation[axis],
viewportLength - axisBounds[1],
-axisBounds[0]);
} else {
// Bounds must be inside viewport.
this.translation[axis] = _.clamp(this.translation[axis],
-axisBounds[0],
viewportLength - axisBounds[1]);
}
}
}
private doZoom(scale: number, point: glmatrix.vec2): void {
const absoluteTranslation = glmatrix.vec2.create();
glmatrix.vec2.sub(absoluteTranslation, this.translation, point);
glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, 1.0 / this.scale);
this.scale = _.clamp(this.scale * scale, this.minScale, this.maxScale);
glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, this.scale);
glmatrix.vec2.add(this.translation, absoluteTranslation, point);
this.clampViewport();
if (this.onZoom != null)
this.onZoom();
}
private get centerPoint(): glmatrix.vec2 {
return glmatrix.vec2.clone([this.canvas.width * 0.5, this.canvas.height * 0.5]);
}
get bounds(): glmatrix.vec4 {
const bounds = glmatrix.vec4.clone(this._bounds);
if (this.scaleBounds)
glmatrix.vec4.scale(bounds, bounds, this.scale);
return bounds;
}
set bounds(newBounds: glmatrix.vec4) {
this._bounds = glmatrix.vec4.clone(newBounds);
}
}
export class PerspectiveCamera extends Camera {
canvas!: HTMLCanvasElement;
onChange: (() => void) | null;
translation: glmatrix.vec3;
/// Yaw and pitch Euler angles.
rotation: glmatrix.vec2;
private movementDelta: glmatrix.vec3;
// If W, A, S, D are pressed
private wasdPress: PerspectiveMovementKeys;
private movementInterval: number | null;
private readonly innerCollisionExtent: number;
private vrRotationMatrix: glmatrix.mat4 | null;
constructor(canvas: HTMLCanvasElement, options?: PerspectiveCameraOptions) {
super(canvas);
if (options == null)
options = {};
this.innerCollisionExtent = options.innerCollisionExtent || 0.0;
this.translation = glmatrix.vec3.clone(PERSPECTIVE_INITIAL_TRANSLATION);
this.rotation = glmatrix.vec2.clone(PERSPECTIVE_INITIAL_ROTATION);
this.movementDelta = glmatrix.vec3.create();
this.movementInterval = null;
this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false);
window.addEventListener('keydown', event => this.onKeyDown(event), false);
window.addEventListener('keyup', event => this.onKeyUp(event), false);
this.onChange = null;
this.vrRotationMatrix = null;
this.wasdPress = _.fromPairs([
['W'.charCodeAt(0), false],
['A'.charCodeAt(0), false],
['S'.charCodeAt(0), false],
['D'.charCodeAt(0), false],
]);
}
zoom(scale: number): void {
// TODO(pcwalton)
}
zoomIn(): void {
// TODO(pcwalton)
}
zoomOut(): void {
// TODO(pcwalton)
}
rotate(newAngle: number): void {
// TODO(pcwalton)
}
setView(rotation: glmatrix.mat4, pose: VRPose): void {
this.vrRotationMatrix = rotation;
}
private onMouseDown(event: MouseEvent): void {
if (document.pointerLockElement !== this.canvas) {
this.canvas.requestPointerLock();
return;
}
this.movementDelta = glmatrix.vec3.fromValues(0.0, 0.0, PERSPECTIVE_MOVEMENT_SPEED);
if (event.button !== 1)
this.movementDelta[0] = -this.movementDelta[0];
this.startMoving();
}
private onMouseUp(event: MouseEvent): void {
this.stopMoving();
}
private onMouseMove(event: MouseEvent): void {
if (document.pointerLockElement !== this.canvas)
return;
this.rotation[0] += event.movementX * PERSPECTIVE_ROTATION_SPEED;
const trialYRotation = this.rotation[1] + event.movementY * PERSPECTIVE_ROTATION_SPEED;
this.rotation[1] = _.clamp(trialYRotation, -Math.PI * 0.5, Math.PI * 0.5);
if (this.onChange != null)
this.onChange();
}
private onKeyDown(event: KeyboardEvent): void {
if (PERSPECTIVE_MOVEMENT_VECTORS.hasOwnProperty(event.keyCode)) {
// keyDown will be repeated on prolonged holds of the key,
// don't do extra computation in that case
if (this.wasdPress[event.keyCode])
return;
this.wasdPress[event.keyCode] = true;
this.updateMovementDelta();
this.startMoving();
}
}
private onKeyUp(event: KeyboardEvent): void {
if (PERSPECTIVE_MOVEMENT_VECTORS.hasOwnProperty(event.keyCode)) {
this.wasdPress[event.keyCode] = false;
if (this.updateMovementDelta()) {
this.stopMoving();
}
}
}
// Updates the movementDelta vector based on what keys are currently pressed
// Returns true if the vector is now empty
private updateMovementDelta(): boolean {
this.movementDelta = glmatrix.vec3.create();
let empty = true;
for (const key of KEYCODES) {
if (this.wasdPress[key]) {
glmatrix.vec3.add(this.movementDelta,
this.movementDelta,
PERSPECTIVE_MOVEMENT_VECTORS[key]);
empty = false;
}
}
return empty;
}
private startMoving(): void {
if (this.movementInterval == null)
this.movementInterval = window.setInterval(() => this.move(),
PERSPECTIVE_MOVEMENT_INTERVAL_DELAY);
}
private stopMoving(): void {
if (this.movementInterval != null) {
window.clearInterval(this.movementInterval);
this.movementInterval = null;
this.movementDelta = glmatrix.vec3.create();
}
}
private move() {
const invRotationMatrix = glmatrix.mat4.create();
glmatrix.mat4.invert(invRotationMatrix, this.rotationMatrix);
const delta = glmatrix.vec3.clone(this.movementDelta);
glmatrix.vec3.transformMat4(delta, delta, invRotationMatrix);
const trialTranslation = glmatrix.vec3.create();
glmatrix.vec3.add(trialTranslation, this.translation, delta);
// TODO(pcwalton): Sliding…
const absoluteTrialTranslationX = Math.abs(trialTranslation[0]);
const absoluteTrialTranslationZ = Math.abs(trialTranslation[2]);
if (absoluteTrialTranslationX < this.innerCollisionExtent + PERSPECTIVE_HITBOX_RADIUS &&
absoluteTrialTranslationZ < this.innerCollisionExtent + PERSPECTIVE_HITBOX_RADIUS) {
return;
}
if (absoluteTrialTranslationX > PERSPECTIVE_OUTER_COLLISION_EXTENT -
PERSPECTIVE_HITBOX_RADIUS) {
trialTranslation[0] = Math.sign(trialTranslation[0]) *
(PERSPECTIVE_OUTER_COLLISION_EXTENT - PERSPECTIVE_HITBOX_RADIUS);
}
if (absoluteTrialTranslationZ > PERSPECTIVE_OUTER_COLLISION_EXTENT -
PERSPECTIVE_HITBOX_RADIUS) {
trialTranslation[2] = Math.sign(trialTranslation[2]) *
(PERSPECTIVE_OUTER_COLLISION_EXTENT - PERSPECTIVE_HITBOX_RADIUS);
}
this.translation = trialTranslation;
if (this.onChange != null)
this.onChange();
}
get rotationMatrix(): glmatrix.mat4 {
if (this.vrRotationMatrix != null) {
// This actually is a rotation + translation matrix, but it works
return this.vrRotationMatrix;
}
const matrix = glmatrix.mat4.create();
glmatrix.mat4.fromXRotation(matrix, this.rotation[1]);
glmatrix.mat4.rotateY(matrix, matrix, this.rotation[0]);
return matrix;
}
}

View File

@ -1,43 +0,0 @@
// pathfinder/client/src/file-picker.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import {expectNotNull} from "./utils";
export class FilePickerView {
static create(): FilePickerView | null {
const element = document.getElementById('pf-file-select') as (HTMLInputElement | null);
return element == null ? null : new FilePickerView(element);
}
onFileLoaded: ((fileData: ArrayBuffer) => void) | null;
private readonly element: HTMLInputElement;
private constructor(element: HTMLInputElement) {
this.element = element;
this.onFileLoaded = null;
element.addEventListener('change', event => this.loadFile(event), false);
}
open() {
this.element.click();
}
private loadFile(event: Event) {
const element = event.target as HTMLInputElement;
const file = expectNotNull(element.files, "No file selected!")[0];
const reader = new FileReader;
reader.addEventListener('loadend', () => {
if (this.onFileLoaded != null)
this.onFileLoaded(reader.result);
}, false);
reader.readAsArrayBuffer(file);
}
}

View File

@ -1,133 +0,0 @@
// pathfinder/client/src/gl-utils.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {assert, UINT32_SIZE, unwrapNull} from './utils';
import {ColorAlphaFormat, DemoView} from './view';
export type WebGLVertexArrayObject = any;
export interface AttributeMap {
[attributeName: string]: number;
}
export interface UniformMap {
[uniformName: string]: WebGLUniformLocation;
}
export interface EXTDisjointTimerQuery {
readonly QUERY_COUNTER_BITS_EXT: GLenum;
readonly CURRENT_QUERY_EXT: GLenum;
readonly QUERY_RESULT_EXT: GLenum;
readonly QUERY_RESULT_AVAILABLE_EXT: GLenum;
readonly TIME_ELAPSED_EXT: GLenum;
readonly TIMESTAMP_EXT: GLenum;
readonly GPU_DISJOINT_EXT: GLenum;
createQueryEXT(): WebGLQuery;
deleteQueryEXT(query: WebGLQuery): void;
isQueryEXT(query: any): GLboolean;
beginQueryEXT(target: GLenum, query: WebGLQuery): void;
endQueryEXT(target: GLenum): void;
queryCounterEXT(query: WebGLQuery, target: GLenum): void;
getQueryEXT(target: GLenum, pname: GLenum): any;
getQueryObjectEXT(query: WebGLQuery, pname: GLenum): any;
}
export class WebGLQuery {}
export const QUAD_ELEMENTS: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]);
export function createFramebufferColorTexture(gl: WebGLRenderingContext,
size: glmatrix.vec2,
colorAlphaFormat: ColorAlphaFormat,
filter?: number):
WebGLTexture {
// Firefox seems to have a bug whereby textures don't get marked as initialized when cleared
// if they're anything other than the first attachment of an FBO. To work around this, supply
// zero data explicitly when initializing the texture.
let format, type, internalFormat, bitDepth, zeroes;
if (colorAlphaFormat === 'RGBA8') {
format = internalFormat = gl.RGBA;
type = gl.UNSIGNED_BYTE;
bitDepth = 32;
zeroes = new Uint8Array(size[0] * size[1] * 4);
} else {
format = internalFormat = gl.RGBA;
type = gl.UNSIGNED_SHORT_5_5_5_1;
bitDepth = 16;
zeroes = new Uint16Array(size[0] * size[1] * 4);
}
const texture = unwrapNull(gl.createTexture());
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, size[0], size[1], 0, format, type, zeroes);
setTextureParameters(gl, _.defaultTo(filter, gl.NEAREST));
return texture;
}
export function createFramebufferDepthTexture(gl: WebGLRenderingContext, size: glmatrix.vec2):
WebGLTexture {
const texture = unwrapNull(gl.createTexture());
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.DEPTH_COMPONENT,
size[0],
size[1],
0,
gl.DEPTH_COMPONENT,
gl.UNSIGNED_INT,
null);
setTextureParameters(gl, gl.NEAREST);
return texture;
}
export function setTextureParameters(gl: WebGLRenderingContext, filter: number) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
}
export function createFramebuffer(gl: WebGLRenderingContext,
colorAttachment: WebGLTexture,
depthAttachment: WebGLTexture | null):
WebGLFramebuffer {
const framebuffer = unwrapNull(gl.createFramebuffer());
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
colorAttachment,
0);
if (depthAttachment != null) {
gl.framebufferTexture2D(gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.TEXTURE_2D,
depthAttachment,
0);
assert(gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) ===
gl.TEXTURE,
"Failed to attach depth texture!");
}
assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE,
"Framebuffer was incomplete!");
return framebuffer;
}

View File

@ -1,564 +0,0 @@
// pathfinder/client/src/mesh-debugger.ts
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as opentype from "opentype.js";
import {Font} from 'opentype.js';
import {AppController} from "./app-controller";
import {OrthographicCamera} from "./camera";
import {FilePickerView} from './file-picker';
import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET, PathfinderMeshPack} from "./meshes";
import {B_QUAD_LOWER_LEFT_VERTEX_OFFSET, B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET} from "./meshes";
import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderPackedMeshes} from "./meshes";
import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes";
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
import {BUILTIN_FONT_URI, TextRun} from "./text";
import {GlyphStore, PathfinderFont, TextFrame} from "./text";
import {assert, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
import {PathfinderView} from "./view";
const CHARACTER: string = 'A';
const FONT: string = 'eb-garamond';
const POINT_LABEL_FONT: string = "sans-serif";
const POINT_LABEL_FONT_SIZE: number = 12.0;
const POINT_LABEL_OFFSET: glmatrix.vec2 = glmatrix.vec2.fromValues(12.0, 12.0);
const POINT_RADIUS: number = 2.0;
const SEGMENT_POINT_RADIUS: number = 3.0;
const SEGMENT_STROKE_WIDTH: number = 1.0;
const SEGMENT_CONTROL_POINT_STROKE_WIDTH: number = 1.0;
const NORMAL_LENGTHS: NormalStyleParameter<number> = {
bVertex: 10.0,
edge: 14.0,
};
const NORMAL_ARROWHEAD_LENGTH: number = 4.0;
const NORMAL_ARROWHEAD_ANGLE: number = Math.PI * 5.0 / 6.0;
const SEGMENT_POINT_FILL_STYLE: string = "rgb(0, 0, 128)";
const LIGHT_STROKE_STYLE: string = "rgb(192, 192, 192)";
const LINE_STROKE_STYLE: string = "rgb(0, 128, 0)";
const CURVE_STROKE_STYLE: string = "rgb(128, 0, 0)";
const SEGMENT_LINE_STROKE_STYLE: string = "rgb(128, 192, 128)";
const SEGMENT_CONTROL_POINT_FILL_STYLE: string = "rgb(255, 255, 255)";
const SEGMENT_CONTROL_POINT_STROKE_STYLE: string = "rgb(0, 0, 128)";
const SEGMENT_CONTROL_POINT_HULL_STROKE_STYLE: string = "rgba(128, 128, 128, 0.5)";
const NORMAL_STROKE_STYLES: NormalStyleParameter<string> = {
bVertex: '#e6aa00',
edge: '#cc5500',
};
const BUILTIN_URIS = {
font: BUILTIN_FONT_URI,
svg: BUILTIN_SVG_URI,
};
const SVG_SCALE: number = 1.0;
type FileType = 'font' | 'svg';
type NormalType = 'edge' | 'bVertex';
interface NormalStyleParameter<T> {
edge: T;
bVertex: T;
}
interface NormalsTable<T> {
lowerCurve: T;
lowerLine: T;
upperCurve: T;
upperLine: T;
}
class MeshDebuggerAppController extends AppController {
meshes: PathfinderPackedMeshes | null = null;
protected readonly defaultFile: string = FONT;
private file: PathfinderFont | SVGLoader | null = null;
private fileType!: FileType;
private fileData: ArrayBuffer | null = null;
private openModal!: HTMLElement;
private openFileSelect!: HTMLSelectElement;
private fontPathSelectGroup!: HTMLElement;
private fontPathSelect!: HTMLSelectElement;
private filePicker!: FilePickerView;
private view!: MeshDebuggerView;
start() {
super.start();
this.fileType = 'font';
this.view = new MeshDebuggerView(this);
this.filePicker = unwrapNull(FilePickerView.create());
this.filePicker.onFileLoaded = fileData => this.fileLoaded(fileData, null);
this.openModal = unwrapNull(document.getElementById('pf-open-modal'));
this.fontPathSelectGroup =
unwrapNull(document.getElementById('pf-font-path-select-group'));
this.fontPathSelect = unwrapNull(document.getElementById('pf-font-path-select')) as
HTMLSelectElement;
this.openFileSelect = unwrapNull(document.getElementById('pf-open-file-select')) as
HTMLSelectElement;
this.openFileSelect.addEventListener('click', () => this.openSelectedFile(), false);
const openButton = unwrapNull(document.getElementById('pf-open-button'));
openButton.addEventListener('click', () => this.showOpenDialog(), false);
const openOKButton = unwrapNull(document.getElementById('pf-open-ok-button'));
openOKButton.addEventListener('click', () => this.loadPath(), false);
this.loadInitialFile(BUILTIN_FONT_URI);
}
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
while (this.fontPathSelect.lastChild != null)
this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
this.fontPathSelectGroup.classList.remove('pf-display-none');
if (this.fileType === 'font')
this.fontLoaded(fileData, builtinName);
else if (this.fileType === 'svg')
this.svgLoaded(fileData);
}
protected loadPath(opentypeGlyph?: opentype.Glyph | null) {
window.jQuery(this.openModal).modal('hide');
let promise: Promise<PathfinderMeshPack>;
if (this.file instanceof PathfinderFont && this.fileData != null) {
if (opentypeGlyph == null) {
const glyphIndex = parseInt(this.fontPathSelect.selectedOptions[0].value, 10);
opentypeGlyph = this.file.opentypeFont.glyphs.get(glyphIndex);
}
const glyphStorage = new GlyphStore(this.file, [(opentypeGlyph as any).index]);
promise = glyphStorage.partition().then(result => result.meshes);
} else if (this.file instanceof SVGLoader) {
promise = this.file.partition(this.fontPathSelect.selectedIndex);
} else {
return;
}
promise.then(meshes => {
this.meshes = new PathfinderPackedMeshes(meshes);
this.view.attachMeshes();
});
}
private showOpenDialog(): void {
window.jQuery(this.openModal).modal();
}
private openSelectedFile(): void {
const selectedOption = this.openFileSelect.selectedOptions[0] as HTMLOptionElement;
const optionValue = selectedOption.value;
this.fontPathSelectGroup.classList.add('pf-display-none');
const results = unwrapNull(/^([a-z]+)-(.*)$/.exec(optionValue));
this.fileType = results[1] as FileType;
const filename = results[2];
if (filename === 'custom')
this.filePicker.open();
else
this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
}
private fontLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
this.file = new PathfinderFont(fileData, builtinName);
this.fileData = fileData;
const glyphCount = this.file.opentypeFont.numGlyphs;
for (let glyphIndex = 1; glyphIndex < glyphCount; glyphIndex++) {
const newOption = document.createElement('option');
newOption.value = "" + glyphIndex;
const glyphName = this.file.opentypeFont.glyphIndexToName(glyphIndex);
newOption.appendChild(document.createTextNode(glyphName));
this.fontPathSelect.appendChild(newOption);
}
// Automatically load a path if this is the initial pageload.
if (this.meshes == null)
this.loadPath(this.file.opentypeFont.charToGlyph(CHARACTER));
}
private svgLoaded(fileData: ArrayBuffer): void {
this.file = new SVGLoader;
this.file.scale = SVG_SCALE;
this.file.loadFile(fileData);
const pathCount = this.file.pathInstances.length;
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
const newOption = document.createElement('option');
newOption.value = "" + pathIndex;
newOption.appendChild(document.createTextNode(`Path ${pathIndex}`));
this.fontPathSelect.appendChild(newOption);
}
}
}
class MeshDebuggerView extends PathfinderView {
camera: OrthographicCamera;
private appController: MeshDebuggerAppController;
private drawControl: boolean = true;
private drawNormals: boolean = true;
private drawVertices: boolean = true;
private drawSegments: boolean = false;
constructor(appController: MeshDebuggerAppController) {
super();
this.appController = appController;
this.camera = new OrthographicCamera(this.canvas, { ignoreBounds: true });
this.camera.onPan = () => this.setDirty();
this.camera.onZoom = () => this.setDirty();
window.addEventListener('keypress', event => this.onKeyPress(event), false);
this.resizeToFit(true);
}
attachMeshes() {
this.setDirty();
}
redraw() {
super.redraw();
const meshes = this.appController.meshes;
if (meshes == null)
return;
const context = unwrapNull(this.canvas.getContext('2d'));
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
context.save();
context.translate(this.camera.translation[0],
this.canvas.height - this.camera.translation[1]);
context.scale(this.camera.scale, this.camera.scale);
const invScaleFactor = window.devicePixelRatio / this.camera.scale;
context.font = `12px ${POINT_LABEL_FONT}`;
context.lineWidth = invScaleFactor;
const bQuadVertexPositions = new Float32Array(meshes.bQuadVertexPositions);
const normals: NormalsTable<Float32Array> = {
lowerCurve: new Float32Array(0),
lowerLine: new Float32Array(0),
upperCurve: new Float32Array(0),
upperLine: new Float32Array(0),
};
// Draw B-quads.
for (let bQuadIndex = 0; bQuadIndex < meshes.bQuadVertexPositions.length; bQuadIndex++) {
const bQuadStartOffset = (B_QUAD_SIZE * bQuadIndex) / UINT32_SIZE;
const upperLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 0);
const upperControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 1);
const upperRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 2);
const lowerRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 3);
const lowerControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 4);
const lowerLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 5);
if (this.drawVertices) {
drawVertexIfNecessary(context, upperLeftPosition, invScaleFactor);
drawVertexIfNecessary(context, upperRightPosition, invScaleFactor);
drawVertexIfNecessary(context, lowerLeftPosition, invScaleFactor);
drawVertexIfNecessary(context, lowerRightPosition, invScaleFactor);
}
context.beginPath();
context.moveTo(upperLeftPosition[0], -upperLeftPosition[1]);
if (upperControlPointPosition != null) {
context.strokeStyle = CURVE_STROKE_STYLE;
context.quadraticCurveTo(upperControlPointPosition[0],
-upperControlPointPosition[1],
upperRightPosition[0],
-upperRightPosition[1]);
} else {
context.strokeStyle = LINE_STROKE_STYLE;
context.lineTo(upperRightPosition[0], -upperRightPosition[1]);
}
context.stroke();
context.strokeStyle = LIGHT_STROKE_STYLE;
context.beginPath();
context.moveTo(upperRightPosition[0], -upperRightPosition[1]);
context.lineTo(lowerRightPosition[0], -lowerRightPosition[1]);
context.stroke();
context.beginPath();
context.moveTo(lowerRightPosition[0], -lowerRightPosition[1]);
if (lowerControlPointPosition != null) {
context.strokeStyle = CURVE_STROKE_STYLE;
context.quadraticCurveTo(lowerControlPointPosition[0],
-lowerControlPointPosition[1],
lowerLeftPosition[0],
-lowerLeftPosition[1]);
} else {
context.strokeStyle = LINE_STROKE_STYLE;
context.lineTo(lowerLeftPosition[0], -lowerLeftPosition[1]);
}
context.stroke();
context.strokeStyle = LIGHT_STROKE_STYLE;
context.beginPath();
context.moveTo(lowerLeftPosition[0], -lowerLeftPosition[1]);
context.lineTo(upperLeftPosition[0], -upperLeftPosition[1]);
context.stroke();
}
// Draw segments.
if (this.drawVertices) {
drawSegmentVertices(context,
new Float32Array(meshes.stencilSegments),
new Float32Array(meshes.stencilNormals),
meshes.count('stencilSegments'),
[0, 2],
1,
3,
invScaleFactor,
this.drawControl,
this.drawNormals,
this.drawSegments);
}
context.restore();
}
private onKeyPress(event: KeyboardEvent): void {
if (event.key === "c") {
this.drawControl = !this.drawControl;
} else if (event.key === "n") {
this.drawNormals = !this.drawNormals;
} else if (event.key === "v") {
this.drawVertices = !this.drawVertices;
} else if (event.key === "r") {
// Reset
this.drawControl = true;
this.drawNormals = true;
this.drawVertices = true;
}
this.setDirty();
}
}
function getPosition(positions: Float32Array, bQuadIndex: number, vertexIndex: number):
glmatrix.vec2 {
return glmatrix.vec2.clone([
positions[(bQuadIndex * 6 + vertexIndex) * 2 + 0],
positions[(bQuadIndex * 6 + vertexIndex) * 2 + 1],
]);
}
function getNormal(normals: Float32Array, bQuadIndex: number, vertexIndex: number): number {
return normals[bQuadIndex * 6 + vertexIndex];
}
function getNormals(normals: NormalsTable<Float32Array>,
normalIndices: NormalsTable<number>,
isCurve: boolean,
side: 'upper' | 'lower'):
{ left: number, right: number } {
const key: keyof NormalsTable<void> = (side + (isCurve ? 'Curve' : 'Line')) as keyof
NormalsTable<void>;
const startOffset = normalIndices[key];
normalIndices[key]++;
return {
left: normals[key][startOffset * 2 + 0],
right: normals[key][startOffset * 2 + 1],
};
}
function drawSegmentVertices(context: CanvasRenderingContext2D,
segments: Float32Array,
normals: Float32Array,
segmentCount: number,
endpointOffsets: number[],
controlPointOffset: number | null,
segmentSize: number,
invScaleFactor: number,
drawControl: boolean,
drawNormals: boolean,
drawSegments: boolean) {
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
const positionStartOffset = segmentSize * 2 * segmentIndex;
const normalStartOffset = segmentSize * 2 * segmentIndex;
const position0 =
glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[0] * 2 + 0],
segments[positionStartOffset + endpointOffsets[0] * 2 + 1]]);
const position1 =
glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[1] * 2 + 0],
segments[positionStartOffset + endpointOffsets[1] * 2 + 1]]);
let controlPoint: glmatrix.vec2 | null;
if (controlPointOffset != null) {
controlPoint =
glmatrix.vec2.clone([segments[positionStartOffset + controlPointOffset * 2 + 0],
segments[positionStartOffset + controlPointOffset * 2 + 1]]);
} else {
controlPoint = null;
}
const normal0 =
glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[0] * 2 + 0],
normals[normalStartOffset + endpointOffsets[0] * 2 + 1]]);
const normal1 =
glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[1] * 2 + 0],
normals[normalStartOffset + endpointOffsets[1] * 2 + 1]]);
let normalControlPoint: glmatrix.vec2 | null;
if (controlPointOffset != null) {
normalControlPoint =
glmatrix.vec2.clone([normals[normalStartOffset + controlPointOffset * 2 + 0],
normals[normalStartOffset + controlPointOffset * 2 + 1]]);
} else {
normalControlPoint = null;
}
if (drawNormals) {
drawNormal(context, position0, normal0, invScaleFactor, 'edge');
drawNormal(context, position1, normal1, invScaleFactor, 'edge');
if (drawControl && controlPoint != null && normalControlPoint != null)
drawNormal(context, controlPoint, normalControlPoint, invScaleFactor, 'edge');
}
drawSegmentVertex(context, position0, invScaleFactor);
drawSegmentVertex(context, position1, invScaleFactor);
if (drawControl && controlPoint != null) {
context.save();
context.strokeStyle = SEGMENT_CONTROL_POINT_HULL_STROKE_STYLE;
context.setLineDash([2 * invScaleFactor, 2 * invScaleFactor]);
context.beginPath();
context.moveTo(position0[0], -position0[1]);
context.lineTo(controlPoint[0], -controlPoint[1]);
context.lineTo(position1[0], -position1[1]);
context.stroke();
context.restore();
drawSegmentControlPoint(context, controlPoint, invScaleFactor);
}
// TODO(pcwalton): Draw the curves too.
if (drawSegments) {
context.save();
context.strokeStyle = SEGMENT_LINE_STROKE_STYLE;
context.beginPath();
context.moveTo(position0[0], -position0[1]);
context.lineTo(position1[0], -position1[1]);
context.stroke();
context.restore();
}
}
}
function drawVertexIfNecessary(context: CanvasRenderingContext2D,
position: Float32Array,
invScaleFactor: number) {
context.beginPath();
context.moveTo(position[0], -position[1]);
context.arc(position[0], -position[1], POINT_RADIUS * invScaleFactor, 0, 2.0 * Math.PI);
context.fill();
}
function drawSegmentVertex(context: CanvasRenderingContext2D,
position: glmatrix.vec2,
invScaleFactor: number) {
context.save();
context.fillStyle = SEGMENT_POINT_FILL_STYLE;
context.lineWidth = invScaleFactor * SEGMENT_STROKE_WIDTH;
context.beginPath();
context.arc(position[0],
-position[1],
SEGMENT_POINT_RADIUS * invScaleFactor,
0,
2.0 * Math.PI);
context.fill();
context.restore();
}
function drawSegmentControlPoint(context: CanvasRenderingContext2D,
position: glmatrix.vec2,
invScaleFactor: number) {
context.save();
context.strokeStyle = SEGMENT_CONTROL_POINT_STROKE_STYLE;
context.fillStyle = SEGMENT_CONTROL_POINT_FILL_STYLE;
context.lineWidth = invScaleFactor * SEGMENT_CONTROL_POINT_STROKE_WIDTH;
context.beginPath();
context.arc(position[0],
-position[1],
SEGMENT_POINT_RADIUS * invScaleFactor,
0,
2.0 * Math.PI);
context.fill();
context.stroke();
context.restore();
}
function drawNormal(context: CanvasRenderingContext2D,
position: glmatrix.vec2,
normalVector: glmatrix.vec2,
invScaleFactor: number,
normalType: NormalType) {
const length = invScaleFactor * NORMAL_LENGTHS[normalType];
const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH;
const endpoint = glmatrix.vec2.clone([position[0] + length * normalVector[0],
-position[1] + length * -normalVector[1]]);
context.save();
context.strokeStyle = NORMAL_STROKE_STYLES[normalType];
context.beginPath();
context.moveTo(position[0], -position[1]);
context.lineTo(endpoint[0], endpoint[1]);
context.lineTo(endpoint[0] + arrowheadLength *
(Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] +
Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]),
endpoint[1] + arrowheadLength *
(Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] -
Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]));
context.stroke();
context.beginPath();
context.moveTo(endpoint[0], endpoint[1]);
context.lineTo(endpoint[0] + arrowheadLength *
(Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] -
Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]),
endpoint[1] - arrowheadLength *
(Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1] +
Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0]));
context.stroke();
context.restore();
}
function main() {
const controller = new MeshDebuggerAppController;
window.addEventListener('load', () => controller.start(), false);
}
main();

View File

@ -1,391 +0,0 @@
// pathfinder/client/src/meshes.ts
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
import * as base64js from 'base64-js';
import * as _ from 'lodash';
import {expectNotNull, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from './utils';
import {UINT32_MAX, UINT32_SIZE, UINT8_SIZE, unwrapNull, unwrapUndef} from './utils';
interface BufferTypeFourCCTable {
[fourCC: string]: keyof MeshLike<void>;
}
interface PathRangeTypeFourCCTable {
[fourCC: string]: keyof PathRanges;
}
interface RangeToCountTable {
[rangeKey: string]: keyof MeshDataCounts;
}
type PathIDBufferTable = Partial<MeshLike<PackedMeshBufferType>>;
interface ArrayLike {
readonly length: number;
}
interface VertexCopyResult {
originalStartIndex: number;
originalEndIndex: number;
expandedStartIndex: number;
expandedEndIndex: number;
}
type PrimitiveType = 'Uint16' | 'Uint32' | 'Float32';
type PrimitiveTypeArray = Float32Array | Uint16Array | Uint32Array;
type MeshBufferType = keyof MeshLike<void>;
type PackedMeshBufferType = keyof PackedMeshLike<void>;
interface MeshBufferTypeDescriptor {
type: PrimitiveType;
size: number;
}
const PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS = {
Float32: Float32Array,
Uint16: Uint16Array,
Uint32: Uint32Array,
};
export const B_QUAD_SIZE: number = 4 * 8;
export const B_QUAD_UPPER_LEFT_VERTEX_OFFSET: number = 4 * 0;
export const B_QUAD_UPPER_RIGHT_VERTEX_OFFSET: number = 4 * 1;
export const B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 2;
export const B_QUAD_LOWER_LEFT_VERTEX_OFFSET: number = 4 * 4;
export const B_QUAD_LOWER_RIGHT_VERTEX_OFFSET: number = 4 * 5;
export const B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 6;
export const B_QUAD_UPPER_INDICES_OFFSET: number = B_QUAD_UPPER_LEFT_VERTEX_OFFSET;
export const B_QUAD_LOWER_INDICES_OFFSET: number = B_QUAD_LOWER_LEFT_VERTEX_OFFSET;
const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE;
// FIXME(pcwalton): This duplicates information below in `MESH_TYPES`.
const INDEX_SIZE: number = 4;
const B_QUAD_VERTEX_POSITION_SIZE: number = 12 * 4;
const B_VERTEX_POSITION_SIZE: number = 4 * 2;
const MESH_TYPES: PackedMeshLike<MeshBufferTypeDescriptor> = {
bBoxPathIDs: { type: 'Uint16', size: 1 },
bBoxes: { type: 'Float32', size: 20 },
bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 },
bQuadVertexPositionPathIDs: { type: 'Uint16', size: 1 },
bQuadVertexPositions: { type: 'Float32', size: 2 },
stencilNormals: { type: 'Float32', size: 6 },
stencilSegmentPathIDs: { type: 'Uint16', size: 1 },
stencilSegments: { type: 'Float32', size: 6 },
};
const BUFFER_TYPES: PackedMeshLike<BufferType> = {
bBoxPathIDs: 'ARRAY_BUFFER',
bBoxes: 'ARRAY_BUFFER',
bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
bQuadVertexPositionPathIDs: 'ARRAY_BUFFER',
bQuadVertexPositions: 'ARRAY_BUFFER',
stencilNormals: 'ARRAY_BUFFER',
stencilSegmentPathIDs: 'ARRAY_BUFFER',
stencilSegments: 'ARRAY_BUFFER',
};
const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve'];
const RIFF_FOURCC: string = 'RIFF';
const MESH_PACK_FOURCC: string = 'PFMP';
const MESH_FOURCC: string = 'mesh';
// Must match the FourCCs in `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into()`.
const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = {
bbox: 'bBoxes',
bqii: 'bQuadVertexInteriorIndices',
bqvp: 'bQuadVertexPositions',
snor: 'stencilNormals',
sseg: 'stencilSegments',
};
const RANGE_TO_COUNT_TABLE: RangeToCountTable = {
bBoxPathRanges: 'bBoxCount',
bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount',
bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount',
stencilSegmentPathRanges: 'stencilSegmentCount',
};
const INDEX_TYPE_DESCRIPTOR_TABLE: {[P in MeshBufferType]?: IndexTypeDescriptor} = {
bQuadVertexInteriorIndices: {
bufferType: 'bQuadVertexPositions',
},
};
const PATH_ID_BUFFER_TABLE: PathIDBufferTable = {
bBoxes: 'bBoxPathIDs',
bQuadVertexPositions: 'bQuadVertexPositionPathIDs',
stencilSegments: 'stencilSegmentPathIDs',
};
const PATH_RANGE_TO_BUFFER_TYPE_TABLE: {[P in keyof PathRanges]: MeshBufferType} = {
bBoxPathRanges: 'bBoxes',
bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndices',
bQuadVertexPositionPathRanges: 'bQuadVertexPositions',
stencilSegmentPathRanges: 'stencilSegments',
};
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
export interface MeshBuilder<T> {
bQuadVertexPositions: T;
bQuadVertexInteriorIndices: T;
bBoxes: T;
stencilSegments: T;
stencilNormals: T;
}
export interface PackedMeshBuilder<T> extends MeshBuilder<T> {
bBoxPathIDs: T;
bQuadVertexPositionPathIDs: T;
stencilSegmentPathIDs: T;
}
export type MeshLike<T> = {
readonly [P in keyof MeshBuilder<void>]: T;
};
export type PackedMeshLike<T> = {
readonly [P in keyof PackedMeshBuilder<void>]: T;
};
interface PathRanges {
readonly bBoxPathRanges: Range[];
readonly bQuadVertexInteriorIndexPathRanges: Range[];
readonly bQuadVertexPositionPathRanges: Range[];
readonly stencilSegmentPathRanges: Range[];
}
interface MeshDataCounts {
readonly bQuadVertexPositionCount: number;
readonly bQuadVertexInteriorIndexCount: number;
readonly bBoxCount: number;
readonly stencilSegmentCount: number;
}
interface IndexTypeDescriptor {
bufferType: MeshBufferType;
}
export class PathfinderMeshPack {
meshes: PathfinderMesh[];
constructor(meshes: ArrayBuffer) {
this.meshes = [];
// RIFF encoded data.
if (toFourCC(meshes, 0) !== RIFF_FOURCC)
panic("Supplied array buffer is not a mesh library (no RIFF header)!");
if (toFourCC(meshes, 8) !== MESH_PACK_FOURCC)
panic("Supplied array buffer is not a mesh library (no PFMP header)!");
let offset = 12;
while (offset < meshes.byteLength) {
const fourCC = toFourCC(meshes, offset);
const chunkLength = readUInt32(meshes, offset + 4);
const startOffset = offset + 8;
const endOffset = startOffset + chunkLength;
if (fourCC === MESH_FOURCC)
this.meshes.push(new PathfinderMesh(meshes.slice(startOffset, endOffset)));
offset = endOffset;
}
}
}
export class PathfinderMesh implements MeshLike<ArrayBuffer> {
bQuadVertexPositions!: ArrayBuffer;
bQuadVertexInteriorIndices!: ArrayBuffer;
bBoxes!: ArrayBuffer;
stencilSegments!: ArrayBuffer;
stencilNormals!: ArrayBuffer;
constructor(data: ArrayBuffer) {
let offset = 0;
while (offset < data.byteLength) {
const fourCC = toFourCC(data, offset);
const chunkLength = readUInt32(data, offset + 4);
const startOffset = offset + 8;
const endOffset = startOffset + chunkLength;
if (BUFFER_TYPE_FOURCCS.hasOwnProperty(fourCC))
this[BUFFER_TYPE_FOURCCS[fourCC]] = data.slice(startOffset, endOffset);
offset = endOffset;
}
for (const type of Object.keys(BUFFER_TYPE_FOURCCS) as Array<keyof MeshLike<void>>) {
if (this[type] == null)
this[type] = new ArrayBuffer(0);
}
}
}
export class PathfinderPackedMeshes implements PackedMeshLike<PrimitiveTypeArray>, PathRanges {
readonly bBoxes!: Float32Array;
readonly bQuadVertexInteriorIndices!: Uint32Array;
readonly bQuadVertexPositions!: Float32Array;
readonly stencilSegments!: Float32Array;
readonly stencilNormals!: Float32Array;
readonly bBoxPathIDs!: Uint16Array;
readonly bQuadVertexPositionPathIDs!: Uint16Array;
readonly stencilSegmentPathIDs!: Uint16Array;
readonly bBoxPathRanges!: Range[];
readonly bQuadVertexInteriorIndexPathRanges!: Range[];
readonly bQuadVertexPositionPathRanges!: Range[];
readonly stencilSegmentPathRanges!: Range[];
/// NB: Mesh indices are 1-indexed.
constructor(meshPack: PathfinderMeshPack, meshIndices?: number[]) {
if (meshIndices == null)
meshIndices = meshPack.meshes.map((value, index) => index + 1);
const meshData: PackedMeshBuilder<number[]> = {
bBoxPathIDs: [],
bBoxes: [],
bQuadVertexInteriorIndices: [],
bQuadVertexPositionPathIDs: [],
bQuadVertexPositions: [],
stencilNormals: [],
stencilSegmentPathIDs: [],
stencilSegments: [],
};
const pathRanges: PathRanges = {
bBoxPathRanges: [],
bQuadVertexInteriorIndexPathRanges: [],
bQuadVertexPositionPathRanges: [],
stencilSegmentPathRanges: [],
};
for (let destMeshIndex = 0; destMeshIndex < meshIndices.length; destMeshIndex++) {
const srcMeshIndex = meshIndices[destMeshIndex];
const mesh = meshPack.meshes[srcMeshIndex - 1];
for (const pathRangeType of Object.keys(pathRanges) as Array<keyof PathRanges>) {
const bufferType = PATH_RANGE_TO_BUFFER_TYPE_TABLE[pathRangeType];
const startIndex = bufferCount(meshData, bufferType);
pathRanges[pathRangeType].push(new Range(startIndex, startIndex));
}
for (const indexType of Object.keys(BUFFER_TYPES) as MeshBufferType[]) {
if (BUFFER_TYPES[indexType] !== 'ELEMENT_ARRAY_BUFFER')
continue;
const indexTypeDescriptor = unwrapUndef(INDEX_TYPE_DESCRIPTOR_TABLE[indexType]);
const offset = bufferCount(meshData, indexTypeDescriptor.bufferType);
for (const index of new Uint32Array(mesh[indexType]))
meshData[indexType].push(index + offset);
}
for (const bufferType of Object.keys(BUFFER_TYPES) as MeshBufferType[]) {
if (BUFFER_TYPES[bufferType] !== 'ARRAY_BUFFER')
continue;
meshData[bufferType].push(...new Float32Array(mesh[bufferType]));
const pathIDBufferType = PATH_ID_BUFFER_TABLE[bufferType];
if (pathIDBufferType != null) {
const length = bufferCount(meshData, bufferType);
while (meshData[pathIDBufferType].length < length)
meshData[pathIDBufferType].push(destMeshIndex + 1);
}
}
for (const pathRangeType of Object.keys(PATH_RANGE_TO_BUFFER_TYPE_TABLE) as
Array<keyof PathRanges>) {
const bufferType = PATH_RANGE_TO_BUFFER_TYPE_TABLE[pathRangeType];
const endIndex = bufferCount(meshData, bufferType);
unwrapUndef(_.last(pathRanges[pathRangeType])).end = endIndex;
}
}
for (const bufferType of Object.keys(BUFFER_TYPES) as PackedMeshBufferType[]) {
const arrayCtor = PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS[MESH_TYPES[bufferType].type];
this[bufferType] = (new arrayCtor(meshData[bufferType])) as any;
}
_.assign(this, pathRanges);
}
count(bufferType: MeshBufferType): number {
return bufferCount(this, bufferType);
}
}
export class PathfinderPackedMeshBuffers implements PackedMeshLike<WebGLBuffer>, PathRanges {
readonly bBoxes!: WebGLBuffer;
readonly bQuadVertexInteriorIndices!: WebGLBuffer;
readonly bQuadVertexPositions!: WebGLBuffer;
readonly stencilSegments!: WebGLBuffer;
readonly stencilNormals!: WebGLBuffer;
readonly bBoxPathIDs!: WebGLBuffer;
readonly bQuadVertexPositionPathIDs!: WebGLBuffer;
readonly stencilSegmentPathIDs!: WebGLBuffer;
readonly bBoxPathRanges!: Range[];
readonly bQuadVertexInteriorIndexPathRanges!: Range[];
readonly bQuadVertexPositionPathRanges!: Range[];
readonly stencilSegmentPathRanges!: Range[];
constructor(gl: WebGLRenderingContext, packedMeshes: PathfinderPackedMeshes) {
for (const bufferName of Object.keys(BUFFER_TYPES) as PackedMeshBufferType[]) {
const bufferType = gl[BUFFER_TYPES[bufferName]];
const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!");
gl.bindBuffer(bufferType, buffer);
gl.bufferData(bufferType, packedMeshes[bufferName], gl.STATIC_DRAW);
this[bufferName] = buffer;
}
for (const rangeName of Object.keys(PATH_RANGE_TO_BUFFER_TYPE_TABLE) as
Array<keyof PathRanges>) {
this[rangeName] = packedMeshes[rangeName];
}
}
}
function bufferCount(mesh: MeshLike<ArrayLike>, bufferType: MeshBufferType): number {
return mesh[bufferType].length / MESH_TYPES[bufferType].size;
}
function sizeOfPrimitive(primitiveType: PrimitiveType): number {
switch (primitiveType) {
case 'Uint16': return UINT16_SIZE;
case 'Uint32': return UINT32_SIZE;
case 'Float32': return FLOAT32_SIZE;
}
}
function toFourCC(buffer: ArrayBuffer, position: number): string {
let result = "";
const bytes = new Uint8Array(buffer, position, 4);
for (const byte of bytes)
result += String.fromCharCode(byte);
return result;
}
export function parseServerTiming(headers: Headers): number {
if (!headers.has('Server-Timing'))
return 0.0;
const timing = headers.get('Server-Timing')!;
const matches = /^Partitioning\s*=\s*([0-9.]+)$/.exec(timing);
return matches != null ? parseFloat(matches[1]) / 1000.0 : 0.0;
}
function readUInt32(buffer: ArrayBuffer, offset: number): number {
return (new Uint32Array(buffer.slice(offset, offset + 4)))[0];
}

View File

@ -1,917 +0,0 @@
// pathfinder/client/src/reference-test.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as imageSSIM from 'image-ssim';
import * as _ from 'lodash';
import * as papaparse from 'papaparse';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
import {SubpixelAAType} from './aa-strategy';
import {DemoAppController, setSwitchInputsValue} from "./app-controller";
import {SUBPIXEL_GRANULARITY} from './atlas';
import {OrthographicCamera} from './camera';
import {UniformMap} from './gl-utils';
import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshPack} from './meshes';
import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
import {PathTransformBuffers, Renderer} from "./renderer";
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
import SSAAStrategy from './ssaa-strategy';
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
import {SVGRenderer} from './svg-renderer';
import {BUILTIN_FONT_URI, computeStemDarkeningAmount, ExpandedMeshData, GlyphStore} from "./text";
import {Hint} from "./text";
import {PathfinderFont, TextFrame, TextRun} from "./text";
import {MAX_SUBPIXEL_AA_FONT_SIZE} from './text-renderer';
import {unwrapNull} from "./utils";
import {DemoView} from "./view";
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
const FONT: string = 'open-sans';
const TEXT_COLOR: number[] = [0, 0, 0, 255];
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
xcaa: AdaptiveStencilMeshAAAStrategy,
};
const RENDER_REFERENCE_URIS: PerTestType<string> = {
font: "/render-reference/text",
svg: "/render-reference/svg",
};
const TEST_DATA_URI: string = "/test-data/reference-test-text.csv";
const SSIM_TOLERANCE: number = 0.01;
const SSIM_WINDOW_SIZE: number = 8;
const FILES: PerTestType<File[]> = {
font: [
{ id: 'open-sans', title: "Open Sans" },
{ id: 'eb-garamond', title: "EB Garamond" },
{ id: 'nimbus-sans', title: "Nimbus Sans" },
],
svg: [
{ id: 'tiger', title: "Ghostscript Tiger" },
],
};
interface ReferenceTestGroup {
font: string;
tests: ReferenceTestCase[];
}
interface ReferenceTestCase {
size: number;
character: string;
aaMode: keyof AntialiasingStrategyTable;
subpixel: boolean;
referenceRenderer: ReferenceRenderer;
expectedSSIM: number;
}
interface PerTestType<T> {
font: T;
svg: T;
}
interface File {
id: string;
title: string;
}
type ReferenceRenderer = 'core-graphics' | 'freetype';
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
xcaa: typeof AdaptiveStencilMeshAAAStrategy;
}
class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
font: PathfinderFont | null = null;
textRun: TextRun | null = null;
svgLoader!: SVGLoader;
builtinSvgName: string | null = null;
referenceCanvas!: HTMLCanvasElement;
tests!: Promise<ReferenceTestGroup[]>;
currentTestType!: 'font' | 'svg';
protected readonly defaultFile: string = FONT;
protected get builtinFileURI(): string {
if (this.currentTestType === 'font')
return BUILTIN_FONT_URI;
return BUILTIN_SVG_URI;
}
private glyphStore!: GlyphStore;
private baseMeshes!: PathfinderMeshPack;
private expandedMeshes!: ExpandedMeshData;
private fontSizeInput!: HTMLInputElement;
private characterInput!: HTMLInputElement;
private referenceRendererSelect!: HTMLSelectElement;
private differenceCanvas!: HTMLCanvasElement;
private aaLevelGroup!: HTMLElement;
private customTabs!: PerTestType<HTMLElement>;
private customTestForms!: PerTestType<HTMLFormElement>;
private selectFileGroups!: PerTestType<HTMLElement>;
private runTestsButtons!: PerTestType<HTMLButtonElement>;
private ssimGroups!: PerTestType<HTMLElement>;
private ssimLabels!: PerTestType<HTMLElement>;
private resultsTables!: PerTestType<HTMLTableElement>;
private currentTestGroupIndex: number | null = null;
private currentTestCaseIndex: number | null = null;
private currentGlobalTestCaseIndex: number | null = null;
get currentFontSize(): number {
return parseInt(this.fontSizeInput.value, 10);
}
set currentFontSize(newFontSize: number) {
this.fontSizeInput.value = "" + newFontSize;
}
get currentCharacter(): string {
return this.characterInput.value;
}
set currentCharacter(newCharacter: string) {
this.characterInput.value = newCharacter;
}
get currentReferenceRenderer(): ReferenceRenderer {
return this.referenceRendererSelect.value as ReferenceRenderer;
}
set currentReferenceRenderer(newReferenceRenderer: ReferenceRenderer) {
this.referenceRendererSelect.value = newReferenceRenderer;
}
start(): void {
this.referenceRendererSelect =
unwrapNull(document.getElementById('pf-font-reference-renderer')) as HTMLSelectElement;
this.referenceRendererSelect.addEventListener('change', () => {
this.view.then(view => this.runSingleTest(view));
}, false);
super.start();
this.currentTestGroupIndex = null;
this.currentTestCaseIndex = null;
this.currentGlobalTestCaseIndex = null;
this.referenceCanvas = unwrapNull(document.getElementById('pf-reference-canvas')) as
HTMLCanvasElement;
this.fontSizeInput = unwrapNull(document.getElementById('pf-font-size')) as
HTMLInputElement;
this.fontSizeInput.addEventListener('change', () => {
this.view.then(view => this.runSingleTest(view));
}, false);
this.characterInput = unwrapNull(document.getElementById('pf-character')) as
HTMLInputElement;
this.characterInput.addEventListener('change', () => {
this.view.then(view => this.runSingleTest(view));
}, false);
this.aaLevelGroup = unwrapNull(document.getElementById('pf-aa-level-group')) as
HTMLElement;
this.differenceCanvas = unwrapNull(document.getElementById('pf-difference-canvas')) as
HTMLCanvasElement;
this.customTabs = {
font: unwrapNull(document.getElementById('pf-font-custom-test-tab')) as HTMLElement,
svg: unwrapNull(document.getElementById('pf-svg-custom-test-tab')) as HTMLElement,
};
this.customTestForms = {
font: unwrapNull(document.getElementById('pf-font-custom-form')) as HTMLFormElement,
svg: unwrapNull(document.getElementById('pf-svg-custom-form')) as HTMLFormElement,
};
this.selectFileGroups = {
font: unwrapNull(document.getElementById('pf-font-select-file-group')) as HTMLElement,
svg: unwrapNull(document.getElementById('pf-svg-select-file-group')) as HTMLElement,
};
this.runTestsButtons = {
font: unwrapNull(document.getElementById('pf-run-font-tests-button')) as
HTMLButtonElement,
svg: unwrapNull(document.getElementById('pf-run-svg-tests-button')) as
HTMLButtonElement,
};
this.ssimGroups = {
font: unwrapNull(document.getElementById('pf-font-ssim-group')) as HTMLElement,
svg: unwrapNull(document.getElementById('pf-svg-ssim-group')) as HTMLElement,
};
this.ssimLabels = {
font: unwrapNull(document.getElementById('pf-font-ssim-label')) as HTMLElement,
svg: unwrapNull(document.getElementById('pf-svg-ssim-label')) as HTMLElement,
};
this.resultsTables = {
font: unwrapNull(document.getElementById('pf-font-results-table')) as HTMLTableElement,
svg: unwrapNull(document.getElementById('pf-svg-results-table')) as HTMLTableElement,
};
this.customTabs.font.addEventListener('click',
() => this.showCustomTabPane('font'),
false);
this.customTabs.svg.addEventListener('click', () => this.showCustomTabPane('svg'), false);
this.runTestsButtons.font.addEventListener('click', () => {
this.view.then(view => this.runTests());
}, false);
this.runTestsButtons.svg.addEventListener('click', () => {
this.view.then(view => this.runTests());
}, false);
this.currentTestType = 'font';
this.loadTestData();
this.populateResultsTable();
this.populateFilesSelect();
this.loadInitialFile(this.builtinFileURI);
}
runNextTestIfNecessary(view: ReferenceTestView, tests: ReferenceTestGroup[]): void {
if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
this.currentGlobalTestCaseIndex == null) {
return;
}
this.currentTestCaseIndex++;
this.currentGlobalTestCaseIndex++;
if (this.currentTestCaseIndex === tests[this.currentTestGroupIndex].tests.length) {
this.currentTestCaseIndex = 0;
this.currentTestGroupIndex++;
if (this.currentTestGroupIndex === tests.length) {
// Done running tests.
this.currentTestCaseIndex = null;
this.currentTestGroupIndex = null;
this.currentGlobalTestCaseIndex = null;
this.view.then(view => view.suppressAutomaticRedraw = false);
return;
}
}
this.loadFontForTestGroupIfNecessary(tests).then(() => {
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
});
}
recordSSIMResult(tests: ReferenceTestGroup[], ssimResult: imageSSIM.IResult): void {
const formattedSSIM: string = "" + (Math.round(ssimResult.ssim * 1000.0) / 1000.0);
this.ssimLabels[this.currentTestType].textContent = formattedSSIM;
if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
this.currentGlobalTestCaseIndex == null) {
return;
}
const testGroup = tests[this.currentTestGroupIndex];
const expectedSSIM = testGroup.tests[this.currentTestCaseIndex].expectedSSIM;
const passed = Math.abs(expectedSSIM - ssimResult.ssim) <= SSIM_TOLERANCE;
const resultsBody: Element = unwrapNull(this.resultsTables[this.currentTestType]
.lastElementChild);
let resultsRow = unwrapNull(resultsBody.firstElementChild);
for (let rowIndex = 0; rowIndex < this.currentGlobalTestCaseIndex; rowIndex++)
resultsRow = unwrapNull(resultsRow.nextElementSibling);
const passCell = unwrapNull(resultsRow.firstElementChild);
const resultsCell = unwrapNull(resultsRow.lastElementChild);
resultsCell.textContent = formattedSSIM;
passCell.textContent = passed ? "✓" : "✗";
resultsRow.classList.remove('table-success', 'table-danger');
resultsRow.classList.add(passed ? 'table-success' : 'table-danger');
}
drawDifferenceImage(differenceImage: imageSSIM.IImage): void {
const canvas = this.differenceCanvas;
const context = unwrapNull(canvas.getContext('2d'));
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
const data = new Uint8ClampedArray(differenceImage.data);
const imageData = new ImageData(data, differenceImage.width, differenceImage.height);
context.putImageData(imageData, 0, 0);
}
protected createView(areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>):
ReferenceTestView {
return new ReferenceTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
switch (this.currentTestType) {
case 'font':
this.textFileLoaded(fileData, builtinName);
break;
case 'svg':
this.svgFileLoaded(fileData, builtinName);
break;
}
// Don't automatically run the test unless this is a custom test.
if (this.currentGlobalTestCaseIndex == null)
this.view.then(view => this.runSingleTest(view));
}
private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
const font = new PathfinderFont(fileData, builtinName);
this.font = font;
}
private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
this.builtinSvgName = builtinName;
this.svgLoader = new SVGLoader;
this.svgLoader.loadFile(fileData);
}
private populateFilesSelect(): void {
const selectFileElement = unwrapNull(this.selectFileElement);
while (selectFileElement.lastChild != null)
selectFileElement.removeChild(selectFileElement.lastChild);
for (const file of FILES[this.currentTestType]) {
const option = document.createElement('option');
option.value = file.id;
option.appendChild(document.createTextNode(file.title));
selectFileElement.appendChild(option);
}
}
private loadTestData(): void {
this.tests = window.fetch(TEST_DATA_URI)
.then(response => response.text())
.then(testDataText => {
const fontNames = [];
const groups: {[font: string]: ReferenceTestCase[]} = {};
const testData = papaparse.parse(testDataText, {
comments: "#",
header: true,
skipEmptyLines: true,
});
for (const row of testData.data) {
if (!groups.hasOwnProperty(row.Font)) {
fontNames.push(row.Font);
groups[row.Font] = [];
}
groups[row.Font].push({
aaMode: row['AA Mode'] as keyof AntialiasingStrategyTable,
character: row.Character,
expectedSSIM: parseFloat(row['Expected SSIM']),
referenceRenderer: row['Reference Renderer'],
size: parseInt(row.Size, 10),
subpixel: !!row.Subpixel,
});
}
return fontNames.map(fontName => {
return {
font: fontName,
tests: groups[fontName],
};
});
});
}
private populateResultsTable(): void {
this.tests.then(tests => {
const resultsBody: Element = unwrapNull(this.resultsTables[this.currentTestType]
.lastElementChild);
for (const testGroup of tests) {
for (const test of testGroup.tests) {
const row = document.createElement('tr');
addCell(row, "");
addCell(row, testGroup.font);
addCell(row, test.character);
addCell(row, "" + test.size);
addCell(row, "" + test.aaMode);
addCell(row, test.subpixel ? "Y" : "N");
addCell(row, test.referenceRenderer);
addCell(row, "" + test.expectedSSIM);
addCell(row, "");
resultsBody.appendChild(row);
}
}
});
}
private runSingleTest(view: ReferenceTestView): void {
if (this.currentTestType === 'font')
this.setUpTextRun();
this.loadReference(view).then(() => this.loadRendering());
}
private runTests(): void {
this.view.then(view => {
view.suppressAutomaticRedraw = true;
this.tests.then(tests => {
this.currentTestGroupIndex = 0;
this.currentTestCaseIndex = 0;
this.currentGlobalTestCaseIndex = 0;
this.loadFontForTestGroupIfNecessary(tests).then(() => {
this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
});
});
});
}
private loadFontForTestGroupIfNecessary(tests: ReferenceTestGroup[]): Promise<void> {
return new Promise(resolve => {
if (this.currentTestGroupIndex == null) {
resolve();
return;
}
this.fetchFile(tests[this.currentTestGroupIndex].font, BUILTIN_FONT_URI).then(() => {
resolve();
});
});
}
private setOptionsForCurrentTest(tests: ReferenceTestGroup[]): Promise<void> {
if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null)
return new Promise(resolve => resolve());
const currentTestCase = tests[this.currentTestGroupIndex].tests[this.currentTestCaseIndex];
this.currentFontSize = currentTestCase.size;
this.currentCharacter = currentTestCase.character;
this.currentReferenceRenderer = currentTestCase.referenceRenderer;
const aaLevelSelect = unwrapNull(this.aaLevelSelect);
aaLevelSelect.selectedIndex = _.findIndex(aaLevelSelect.options, option => {
return option.value.startsWith(currentTestCase.aaMode);
});
const subpixelAASelect = unwrapNull(this.subpixelAASelect);
subpixelAASelect.selectedIndex = currentTestCase.subpixel ? 1 : 0;
return this.updateAALevel();
}
private setUpTextRun(): void {
const font = unwrapNull(this.font);
const textRun = new TextRun(this.currentCharacter, [0, 0], font);
textRun.layout();
this.textRun = textRun;
this.glyphStore = new GlyphStore(font, [textRun.glyphIDs[0]]);
}
private loadRendering(): void {
switch (this.currentTestType) {
case 'font':
this.loadTextRendering();
break;
case 'svg':
this.loadSVGRendering();
break;
}
}
private loadTextRendering(): void {
this.glyphStore.partition().then(result => {
const textRun = unwrapNull(this.textRun);
this.baseMeshes = result.meshes;
const textFrame = new TextFrame([textRun], unwrapNull(this.font));
const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, [textRun.glyphIDs[0]]);
this.expandedMeshes = expandedMeshes;
this.view.then(view => {
view.recreateRenderer();
view.attachMeshes([expandedMeshes.meshes]);
view.redraw();
});
});
}
private loadSVGRendering(): void {
this.svgLoader.partition().then(meshes => {
this.view.then(view => {
view.recreateRenderer();
view.attachMeshes([new PathfinderPackedMeshes(meshes)]);
view.initCameraBounds(this.svgLoader.svgViewBox);
});
});
}
private loadReference(view: ReferenceTestView): Promise<void> {
let request;
switch (this.currentTestType) {
case 'font':
request = {
face: {
Builtin: unwrapNull(this.font).builtinFontName,
},
fontIndex: 0,
glyph: this.glyphStore.glyphIDs[0],
pointSize: this.currentFontSize,
renderer: this.currentReferenceRenderer,
};
break;
case 'svg':
// TODO(pcwalton): Custom SVGs.
request = {
name: unwrapNull(this.builtinSvgName),
renderer: 'pixman',
scale: 1.0,
};
break;
}
return window.fetch(RENDER_REFERENCE_URIS[this.currentTestType], {
body: JSON.stringify(request),
headers: {'Content-Type': 'application/json'} as any,
method: 'POST',
}).then(response => response.blob()).then(blob => {
const imgElement = document.createElement('img');
imgElement.src = URL.createObjectURL(blob);
imgElement.addEventListener('load', () => {
const canvas = this.referenceCanvas;
const context = unwrapNull(canvas.getContext('2d'));
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(imgElement, 0, 0);
}, false);
});
}
private showCustomTabPane(testType: 'font' | 'svg'): void {
this.currentTestType = testType;
const selectFileElement = unwrapNull(this.selectFileElement);
const aaLevelGroup = unwrapNull(this.aaLevelGroup);
const customTestForm = this.customTestForms[testType];
const selectFileGroup = this.selectFileGroups[testType];
const ssimGroup = this.ssimGroups[testType];
unwrapNull(selectFileElement.parentNode).removeChild(selectFileElement);
unwrapNull(aaLevelGroup.parentNode).removeChild(aaLevelGroup);
selectFileGroup.appendChild(selectFileElement);
customTestForm.insertBefore(aaLevelGroup, ssimGroup);
this.populateFilesSelect();
}
}
class ReferenceTestView extends DemoView {
renderer!: ReferenceTestTextRenderer | ReferenceTestSVGRenderer;
readonly appController: ReferenceTestAppController;
get camera(): OrthographicCamera {
return this.renderer.camera;
}
constructor(appController: ReferenceTestAppController,
areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.recreateRenderer();
this.resizeToFit(true);
}
recreateRenderer(): void {
switch (this.appController.currentTestType) {
case 'svg':
this.renderer = new ReferenceTestSVGRenderer(this);
break;
case 'font':
this.renderer = new ReferenceTestTextRenderer(this);
break;
}
}
initCameraBounds(viewBox: glmatrix.vec4): void {
if (this.renderer instanceof ReferenceTestSVGRenderer)
this.renderer.initCameraBounds(viewBox);
}
protected renderingFinished(): void {
const gl = this.renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// TODO(pcwalton): Get the SVG pixel rect.
let pixelRect: glmatrix.vec4 = glmatrix.vec4.create();
if (this.renderer instanceof ReferenceTestTextRenderer)
pixelRect = this.renderer.getPixelRectForGlyphAt(0);
const canvasHeight = this.canvas.height;
const width = pixelRect[2] - pixelRect[0], height = pixelRect[3] - pixelRect[1];
const originY = Math.max(canvasHeight - height, 0);
const flippedBuffer = new Uint8Array(width * height * 4);
gl.readPixels(0, originY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, flippedBuffer);
const buffer = new Uint8Array(width * height * 4);
for (let y = 0; y < height; y++) {
const destRowStart = y * width * 4;
const srcRowStart = (height - y - 1) * width * 4;
buffer.set(flippedBuffer.slice(srcRowStart, srcRowStart + width * 4),
destRowStart);
}
const renderedImage = createSSIMImage(buffer, pixelRect);
this.appController.tests.then(tests => {
const referenceImage = createSSIMImage(this.appController.referenceCanvas,
pixelRect);
const ssimResult = imageSSIM.compare(referenceImage, renderedImage, SSIM_WINDOW_SIZE);
const differenceImage = generateDifferenceImage(referenceImage, renderedImage);
this.appController.recordSSIMResult(tests, ssimResult);
this.appController.drawDifferenceImage(differenceImage);
this.appController.runNextTestIfNecessary(this, tests);
});
}
}
class ReferenceTestTextRenderer extends Renderer {
renderContext!: ReferenceTestView;
camera: OrthographicCamera;
needsStencil: boolean = false;
isMulticolor: boolean = false;
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
get fgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
}
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.renderContext.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
get emboldenAmount(): glmatrix.vec2 {
return this.stemDarkeningAmount;
}
get allowSubpixelAA(): boolean {
const appController = this.renderContext.appController;
return appController.currentFontSize <= MAX_SUBPIXEL_AA_FONT_SIZE;
}
protected get objectCount(): number {
return this.meshBuffers == null ? 0 : this.meshBuffers.length;
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get worldTransform(): glmatrix.mat4 {
const canvas = this.renderContext.canvas;
const transform = glmatrix.mat4.create();
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
const pixelsPerUnit = this.pixelsPerUnit;
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
return transform;
}
private get pixelsPerUnit(): number {
const appController = this.renderContext.appController;
const font = unwrapNull(appController.font);
return appController.currentFontSize / font.opentypeFont.unitsPerEm;
}
private get stemDarkeningAmount(): glmatrix.vec2 {
const appController = this.renderContext.appController;
return computeStemDarkeningAmount(appController.currentFontSize, this.pixelsPerUnit);
}
constructor(renderContext: ReferenceTestView) {
super(renderContext);
this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true });
this.camera.onPan = () => renderContext.setDirty();
this.camera.onZoom = () => renderContext.setDirty();
}
attachMeshes(meshes: PathfinderPackedMeshes[]): void {
super.attachMeshes(meshes);
this.uploadPathColors(1);
this.uploadPathTransforms(1);
}
pathCountForObject(objectIndex: number): number {
return 1;
}
pathBoundingRects(objectIndex: number): Float32Array {
const appController = this.renderContext.appController;
const font = unwrapNull(appController.font);
const boundingRects = new Float32Array(2 * 4);
const glyphID = unwrapNull(appController.textRun).glyphIDs[0];
const metrics = unwrapNull(font.metricsForGlyph(glyphID));
boundingRects[4 + 0] = metrics.xMin;
boundingRects[4 + 1] = metrics.yMin;
boundingRects[4 + 2] = metrics.xMax;
boundingRects[4 + 3] = metrics.yMax;
return boundingRects;
}
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
getPixelRectForGlyphAt(glyphIndex: number): glmatrix.vec4 {
const textRun = unwrapNull(this.renderContext.appController.textRun);
return textRun.pixelRectForGlyphAt(glyphIndex);
}
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
const appController = this.renderContext.appController;
const canvas = this.renderContext.canvas;
const font = unwrapNull(appController.font);
const hint = new Hint(font, this.pixelsPerUnit, true);
const pathTransforms = this.createPathTransformBuffers(1);
const textRun = unwrapNull(appController.textRun);
const glyphID = textRun.glyphIDs[0];
textRun.recalculatePixelRects(this.pixelsPerUnit,
0.0,
hint,
glmatrix.vec2.create(),
SUBPIXEL_GRANULARITY,
glmatrix.vec4.create());
const pixelRect = textRun.pixelRectForGlyphAt(0);
const x = -pixelRect[0] / this.pixelsPerUnit;
const y = (canvas.height - (pixelRect[3] - pixelRect[1])) / this.pixelsPerUnit;
pathTransforms.st.set([1, 1, x, y], 1 * 4);
return pathTransforms;
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected compositeIfNecessary(): void {}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const pathColors = new Uint8Array(4 * 2);
pathColors.set(TEXT_COLOR, 1 * 4);
return pathColors;
}
protected directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
}
class ReferenceTestSVGRenderer extends SVGRenderer {
renderContext!: ReferenceTestView;
protected get loader(): SVGLoader {
return this.renderContext.appController.svgLoader;
}
protected get canvas(): HTMLCanvasElement {
return this.renderContext.canvas;
}
constructor(renderContext: ReferenceTestView) {
super(renderContext, { sizeToFit: false, fixed: true });
}
}
function createSSIMImage(image: HTMLCanvasElement | Uint8Array, rect: glmatrix.vec4):
imageSSIM.IImage {
const size = glmatrix.vec2.clone([rect[2] - rect[0], rect[3] - rect[1]]);
let data;
if (image instanceof HTMLCanvasElement) {
const context = unwrapNull(image.getContext('2d'));
data = new Uint8Array(context.getImageData(0, 0, size[0], size[1]).data);
} else {
data = image;
}
return {
channels: imageSSIM.Channels.RGBAlpha,
data: data,
height: size[1],
width: size[0],
};
}
function generateDifferenceImage(referenceImage: imageSSIM.IImage,
renderedImage: imageSSIM.IImage):
imageSSIM.IImage {
const differenceImage = new Uint8Array(referenceImage.width * referenceImage.height * 4);
for (let y = 0; y < referenceImage.height; y++) {
const rowStart = y * referenceImage.width * 4;
for (let x = 0; x < referenceImage.width; x++) {
const pixelStart = rowStart + x * 4;
let differenceSum = 0;
for (let channel = 0; channel < 3; channel++) {
differenceSum += Math.abs(referenceImage.data[pixelStart + channel] -
renderedImage.data[pixelStart + channel]);
}
if (differenceSum === 0) {
// Lighten to indicate no difference.
for (let channel = 0; channel < 4; channel++) {
differenceImage[pixelStart + channel] =
Math.floor(referenceImage.data[pixelStart + channel] / 2) + 128;
}
continue;
}
// Draw differences in red.
const differenceMean = differenceSum / 3;
differenceImage[pixelStart + 0] = 127 + Math.round(differenceMean / 2);
differenceImage[pixelStart + 1] = differenceImage[pixelStart + 2] = 0;
differenceImage[pixelStart + 3] = 255;
}
}
return {
channels: referenceImage.channels,
data: differenceImage,
height: referenceImage.height,
width: referenceImage.width,
};
}
function addCell(row: HTMLTableRowElement, text: string): void {
const tableCell = document.createElement('td');
tableCell.textContent = text;
row.appendChild(tableCell);
}
function main() {
const controller = new ReferenceTestAppController;
window.addEventListener('load', () => controller.start(), false);
}
main();

View File

@ -1,847 +0,0 @@
// pathfinder/client/src/renderer.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy';
import {GammaCorrectionMode} from './aa-strategy';
import {TileInfo} from './aa-strategy';
import {NoAAStrategy, StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {AAOptions} from './app-controller';
import PathfinderBufferTexture from "./buffer-texture";
import {UniformMap, WebGLQuery} from './gl-utils';
import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from "./meshes";
import {ShaderMap} from './shader-loader';
import {FLOAT32_SIZE, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull, unwrapUndef} from './utils';
import {RenderContext, Timings} from "./view";
const MAX_PATHS: number = 65535;
const MAX_VERTICES: number = 4 * 1024 * 1024;
const TIME_INTERVAL_DELAY: number = 32;
const B_LOOP_BLINN_DATA_SIZE: number = 4;
const B_LOOP_BLINN_DATA_TEX_COORD_OFFSET: number = 0;
const B_LOOP_BLINN_DATA_SIGN_OFFSET: number = 2;
export interface PathTransformBuffers<T> {
st: T;
ext: T;
}
export abstract class Renderer {
readonly renderContext: RenderContext;
readonly pathTransformBufferTextures: Array<PathTransformBuffers<PathfinderBufferTexture>>;
meshBuffers: PathfinderPackedMeshBuffers[] | null;
meshes: PathfinderPackedMeshes[] | null;
lastTimings: Timings;
inVR: boolean = false;
get emboldenAmount(): glmatrix.vec2 {
return glmatrix.vec2.create();
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
get fgColor(): glmatrix.vec4 | null {
return null;
}
get backgroundColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
get meshesAttached(): boolean {
return this.meshBuffers != null && this.meshes != null;
}
abstract get isMulticolor(): boolean;
abstract get needsStencil(): boolean;
abstract get allowSubpixelAA(): boolean;
abstract get destFramebuffer(): WebGLFramebuffer | null;
abstract get destAllocatedSize(): glmatrix.vec2;
abstract get destUsedSize(): glmatrix.vec2;
protected antialiasingStrategy: AntialiasingStrategy | null;
protected pathColorsBufferTextures: PathfinderBufferTexture[];
protected gammaCorrectionMode: GammaCorrectionMode;
protected get pathIDsAreInstanced(): boolean {
return false;
}
protected abstract get objectCount(): number;
protected abstract get usedSizeFactor(): glmatrix.vec2;
protected abstract get worldTransform(): glmatrix.mat4;
private implicitCoverInteriorVAO: WebGLVertexArrayObjectOES | null = null;
private implicitCoverCurveVAO: WebGLVertexArrayObjectOES | null = null;
private gammaLUTTexture: WebGLTexture | null = null;
private areaLUTTexture: WebGLTexture | null = null;
private instancedPathIDVBO: WebGLBuffer | null = null;
private vertexIDVBO: WebGLBuffer | null = null;
private timerQueryPollInterval: number | null = null;
constructor(renderContext: RenderContext) {
this.renderContext = renderContext;
this.meshes = null;
this.meshBuffers = null;
this.lastTimings = { rendering: 0, compositing: 0 };
this.gammaCorrectionMode = 'on';
this.pathTransformBufferTextures = [];
this.pathColorsBufferTextures = [];
if (this.pathIDsAreInstanced)
this.initInstancedPathIDVBO();
this.initVertexIDVBO();
this.initLUTTexture('gammaLUT', 'gammaLUTTexture');
this.initLUTTexture('areaLUT', 'areaLUTTexture');
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
this.antialiasingStrategy.init(this);
this.antialiasingStrategy.setFramebufferSize(this);
}
attachMeshes(meshes: PathfinderPackedMeshes[]): void {
const renderContext = this.renderContext;
this.meshes = meshes;
this.meshBuffers = meshes.map(meshes => {
return new PathfinderPackedMeshBuffers(renderContext.gl, meshes);
});
unwrapNull(this.antialiasingStrategy).attachMeshes(this);
}
abstract pathBoundingRects(objectIndex: number): Float32Array;
abstract setHintsUniform(uniforms: UniformMap): void;
abstract pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array>;
redrawVR(frame: VRFrameData): void {
this.redraw();
}
setDrawViewport() {
const renderContext = this.renderContext;
const gl = renderContext.gl;
gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]);
}
setClipPlanes(display: VRDisplay) {
}
redraw(): void {
const renderContext = this.renderContext;
if (this.meshBuffers == null)
return;
this.clearDestFramebuffer(false);
// Start timing rendering.
if (this.timerQueryPollInterval == null &&
renderContext.timerQueryExt != null &&
renderContext.atlasRenderingTimerQuery != null) {
renderContext.timerQueryExt.beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT,
renderContext.atlasRenderingTimerQuery);
}
const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
antialiasingStrategy.prepareForRendering(this);
// Draw "scenery" (used in the 3D view).
this.drawSceneryIfNecessary();
const passCount = antialiasingStrategy.passCount;
for (let pass = 0; pass < passCount; pass++) {
if (antialiasingStrategy.directRenderingMode !== 'none')
antialiasingStrategy.prepareForDirectRendering(this);
const objectCount = this.objectCount;
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
if (antialiasingStrategy.directRenderingMode !== 'none') {
// Prepare for direct rendering.
antialiasingStrategy.prepareToRenderObject(this, objectIndex);
// Clear.
this.clearForDirectRendering(objectIndex);
// Perform direct rendering (Loop-Blinn).
this.directlyRenderObject(pass, objectIndex);
}
// Antialias.
antialiasingStrategy.antialiasObject(this, objectIndex);
// End the timer, and start a new one.
// FIXME(pcwalton): This is kinda bogus for multipass.
if (this.timerQueryPollInterval == null &&
objectIndex === objectCount - 1 &&
pass === passCount - 1 &&
renderContext.timerQueryExt != null &&
renderContext.compositingTimerQuery != null) {
renderContext.timerQueryExt
.endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT);
renderContext.timerQueryExt
.beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT,
renderContext.compositingTimerQuery);
}
// Perform post-antialiasing tasks.
antialiasingStrategy.finishAntialiasingObject(this, objectIndex);
antialiasingStrategy.resolveAAForObject(this, objectIndex);
}
antialiasingStrategy.resolve(pass, this);
}
// Draw the glyphs with the resolved atlas to the default framebuffer.
this.compositeIfNecessary();
// Finish timing.
this.finishTiming();
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number,
aaOptions: AAOptions):
void {
this.gammaCorrectionMode = aaOptions.gammaCorrection;
this.antialiasingStrategy = this.createAAStrategy(aaType,
aaLevel,
aaOptions.subpixelAA,
aaOptions.stemDarkening);
this.antialiasingStrategy.init(this);
if (this.meshes != null)
this.antialiasingStrategy.attachMeshes(this);
this.antialiasingStrategy.setFramebufferSize(this);
this.renderContext.setDirty();
}
canvasResized(): void {
if (this.antialiasingStrategy != null)
this.antialiasingStrategy.init(this);
}
setFramebufferSizeUniform(uniforms: UniformMap): void {
const gl = this.renderContext.gl;
gl.uniform2i(uniforms.uFramebufferSize,
this.destAllocatedSize[0],
this.destAllocatedSize[1]);
}
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap, tileInfo?: TileInfo): void {
const renderContext = this.renderContext;
const usedSize = this.usedSizeFactor;
let tileSize, tilePosition;
if (tileInfo == null) {
tileSize = glmatrix.vec2.clone([1.0, 1.0]);
tilePosition = glmatrix.vec2.create();
} else {
tileSize = tileInfo.size;
tilePosition = tileInfo.position;
}
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [
-1.0 + tilePosition[0] / tileSize[0] * 2.0,
-1.0 + tilePosition[1] / tileSize[1] * 2.0,
0.0,
]);
glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
glmatrix.mat4.scale(transform, transform, [1.0 / tileSize[0], 1.0 / tileSize[1], 1.0]);
renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
renderContext.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
}
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const usedSize = this.usedSizeFactor;
gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0);
gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
}
setTransformUniform(uniforms: UniformMap, pass: number, objectIndex: number): void {
const transform = this.computeTransform(pass, objectIndex);
this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
}
setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void {
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
// Refactor.
const renderContext = this.renderContext;
const gl = renderContext.gl;
const transform = this.computeTransform(0, objectIndex);
gl.uniform4f(uniforms.uTransformST,
transform[0],
transform[5],
transform[12],
transform[13]);
}
affineTransform(objectIndex: number): glmatrix.mat2d {
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an affine matrix is ugly and
// fragile. Refactor.
const transform = this.computeTransform(0, objectIndex);
return glmatrix.mat2d.fromValues(transform[0], transform[1],
transform[4], transform[5],
transform[12], transform[13]);
}
setTransformAffineUniforms(uniforms: UniformMap, objectIndex: number): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const transform = this.affineTransform(objectIndex);
gl.uniform4f(uniforms.uTransformST,
transform[0],
transform[3],
transform[4],
transform[5]);
gl.uniform2f(uniforms.uTransformExt, transform[1], transform[2]);
}
uploadPathColors(objectCount: number): void {
const renderContext = this.renderContext;
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathColors = this.pathColorsForObject(objectIndex);
let pathColorsBufferTexture;
if (objectIndex >= this.pathColorsBufferTextures.length) {
pathColorsBufferTexture = new PathfinderBufferTexture(renderContext.gl,
'uPathColors');
this.pathColorsBufferTextures[objectIndex] = pathColorsBufferTexture;
} else {
pathColorsBufferTexture = this.pathColorsBufferTextures[objectIndex];
}
pathColorsBufferTexture.upload(renderContext.gl, pathColors);
}
}
uploadPathTransforms(objectCount: number): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathTransforms = this.pathTransformsForObject(objectIndex);
let pathTransformBufferTextures;
if (objectIndex >= this.pathTransformBufferTextures.length) {
pathTransformBufferTextures = {
ext: new PathfinderBufferTexture(gl, 'uPathTransformExt'),
st: new PathfinderBufferTexture(gl, 'uPathTransformST'),
};
this.pathTransformBufferTextures[objectIndex] = pathTransformBufferTextures;
} else {
pathTransformBufferTextures = this.pathTransformBufferTextures[objectIndex];
}
pathTransformBufferTextures.st.upload(gl, pathTransforms.st);
pathTransformBufferTextures.ext.upload(gl, pathTransforms.ext);
}
}
setPathColorsUniform(objectIndex: number, uniforms: UniformMap, textureUnit: number): void {
const gl = this.renderContext.gl;
const meshIndex = this.meshIndexForObject(objectIndex);
this.pathColorsBufferTextures[meshIndex].bind(gl, uniforms, textureUnit);
}
setEmboldenAmountUniform(objectIndex: number, uniforms: UniformMap): void {
const gl = this.renderContext.gl;
const emboldenAmount = this.emboldenAmount;
gl.uniform2f(uniforms.uEmboldenAmount, emboldenAmount[0], emboldenAmount[1]);
}
meshIndexForObject(objectIndex: number): number {
return objectIndex;
}
pathRangeForObject(objectIndex: number): Range {
if (this.meshBuffers == null)
return new Range(0, 0);
const bVertexPathRanges = this.meshBuffers[objectIndex].bQuadVertexPositionPathRanges;
return new Range(1, bVertexPathRanges.length + 1);
}
bindAreaLUT(textureUnit: number, uniforms: UniformMap): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.uniform1i(uniforms.uAreaLUT, textureUnit);
}
protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
return null;
}
protected bindGammaLUT(bgColor: glmatrix.vec3, textureUnit: number, uniforms: UniformMap):
void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, this.gammaLUTTexture);
gl.uniform1i(uniforms.uGammaLUT, textureUnit);
gl.uniform3f(uniforms.uBGColor, bgColor[0], bgColor[1], bgColor[2]);
}
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode):
AntialiasingStrategy;
protected abstract compositeIfNecessary(): void;
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
protected abstract directCurveProgramName(): keyof ShaderMap<void>;
protected abstract directInteriorProgramName(renderingMode: DirectRenderingMode):
keyof ShaderMap<void>;
protected drawSceneryIfNecessary(): void {}
protected clearDestFramebuffer(force: boolean): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const clearColor = this.backgroundColor;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.destFramebuffer);
gl.depthMask(true);
gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]);
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
protected clearForDirectRendering(objectIndex: number): void {
const renderingMode = unwrapNull(this.antialiasingStrategy).directRenderingMode;
const renderContext = this.renderContext;
const gl = renderContext.gl;
const clearColor = this.clearColorForObject(objectIndex);
if (clearColor == null)
return;
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
gl.clearDepth(0.0);
gl.depthMask(true);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
return glmatrix.mat4.create();
}
/// If non-instanced, returns instance 0. An empty range skips rendering the object entirely.
protected instanceRangeForObject(objectIndex: number): Range {
return new Range(0, 1);
}
/// Called whenever new GPU timing statistics are available.
protected newTimingsReceived(): void {}
protected createPathTransformBuffers(pathCount: number): PathTransformBuffers<Float32Array> {
pathCount += 1;
return {
ext: new Float32Array((pathCount + (pathCount & 1)) * 2),
st: new Float32Array(pathCount * 4),
};
}
private directlyRenderObject(pass: number, objectIndex: number): void {
if (this.meshBuffers == null || this.meshes == null)
return;
const renderContext = this.renderContext;
const gl = renderContext.gl;
const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
const renderingMode = antialiasingStrategy.directRenderingMode;
const objectCount = this.objectCount;
const instanceRange = this.instanceRangeForObject(objectIndex);
if (instanceRange.isEmpty)
return;
const pathRange = this.pathRangeForObject(objectIndex);
const meshIndex = this.meshIndexForObject(objectIndex);
const meshes = this.meshBuffers![meshIndex];
const meshData = this.meshes![meshIndex];
// Set up implicit cover state.
gl.depthFunc(gl.GREATER);
gl.depthMask(true);
gl.enable(gl.DEPTH_TEST);
gl.disable(gl.BLEND);
gl.cullFace(gl.BACK);
gl.frontFace(gl.CCW);
gl.enable(gl.CULL_FACE);
// Set up the implicit cover interior VAO.
const directInteriorProgramName = this.directInteriorProgramName(renderingMode);
const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName];
if (this.implicitCoverInteriorVAO == null) {
this.implicitCoverInteriorVAO = renderContext.vertexArrayObjectExt
.createVertexArrayOES();
}
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverInteriorVAO);
this.initImplicitCoverInteriorVAO(objectIndex, instanceRange, renderingMode);
// Draw direct interior parts.
if (renderingMode === 'conservative')
this.setTransformAffineUniforms(directInteriorProgram.uniforms, objectIndex);
else
this.setTransformUniform(directInteriorProgram.uniforms, pass, objectIndex);
this.setFramebufferSizeUniform(directInteriorProgram.uniforms);
this.setHintsUniform(directInteriorProgram.uniforms);
this.setPathColorsUniform(objectIndex, directInteriorProgram.uniforms, 0);
this.setEmboldenAmountUniform(objectIndex, directInteriorProgram.uniforms);
this.pathTransformBufferTextures[meshIndex].st.bind(gl, directInteriorProgram.uniforms, 1);
this.pathTransformBufferTextures[meshIndex]
.ext
.bind(gl, directInteriorProgram.uniforms, 2);
const bQuadInteriorRange = getMeshIndexRange(meshes.bQuadVertexInteriorIndexPathRanges,
pathRange);
if (!this.pathIDsAreInstanced) {
gl.drawElements(gl.TRIANGLES,
bQuadInteriorRange.length,
gl.UNSIGNED_INT,
bQuadInteriorRange.start * UINT32_SIZE);
} else {
renderContext.instancedArraysExt
.drawElementsInstancedANGLE(gl.TRIANGLES,
bQuadInteriorRange.length,
gl.UNSIGNED_INT,
0,
instanceRange.length);
}
gl.disable(gl.CULL_FACE);
// Render curves, if applicable.
if (renderingMode !== 'conservative') {
// Set up direct curve state.
gl.depthMask(false);
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
// Set up the direct curve VAO.
//
// TODO(pcwalton): Cache these.
const directCurveProgramName = this.directCurveProgramName();
const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName];
if (this.implicitCoverCurveVAO == null) {
this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt
.createVertexArrayOES();
}
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverCurveVAO);
this.initImplicitCoverCurveVAO(objectIndex, instanceRange);
// Draw direct curve parts.
this.setTransformUniform(directCurveProgram.uniforms, pass, objectIndex);
this.setFramebufferSizeUniform(directCurveProgram.uniforms);
this.setHintsUniform(directCurveProgram.uniforms);
this.setPathColorsUniform(objectIndex, directCurveProgram.uniforms, 0);
this.setEmboldenAmountUniform(objectIndex, directCurveProgram.uniforms);
this.pathTransformBufferTextures[meshIndex]
.st
.bind(gl, directCurveProgram.uniforms, 1);
this.pathTransformBufferTextures[meshIndex]
.ext
.bind(gl, directCurveProgram.uniforms, 2);
const coverCurveRange = getMeshIndexRange(meshes.bQuadVertexPositionPathRanges,
pathRange);
if (!this.pathIDsAreInstanced) {
gl.drawArrays(gl.TRIANGLES, coverCurveRange.start * 6, coverCurveRange.length * 6);
} else {
renderContext.instancedArraysExt
.drawArraysInstancedANGLE(gl.TRIANGLES,
0,
coverCurveRange.length * 6,
instanceRange.length);
}
}
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
// Finish direct rendering. Right now, this performs compositing if necessary.
antialiasingStrategy.finishDirectlyRenderingObject(this, objectIndex);
}
private finishTiming(): void {
const renderContext = this.renderContext;
if (this.timerQueryPollInterval != null ||
renderContext.timerQueryExt == null ||
renderContext.atlasRenderingTimerQuery == null ||
renderContext.compositingTimerQuery == null) {
return;
}
renderContext.timerQueryExt.endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT);
this.timerQueryPollInterval = window.setInterval(() => {
if (renderContext.timerQueryExt == null ||
renderContext.atlasRenderingTimerQuery == null ||
renderContext.compositingTimerQuery == null) {
return;
}
for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as
Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) {
if (renderContext.timerQueryExt
.getQueryObjectEXT(renderContext[queryName] as WebGLQuery,
renderContext.timerQueryExt
.QUERY_RESULT_AVAILABLE_EXT) ===
0) {
return;
}
}
const atlasRenderingTime =
renderContext.timerQueryExt
.getQueryObjectEXT(renderContext.atlasRenderingTimerQuery,
renderContext.timerQueryExt.QUERY_RESULT_EXT);
const compositingTime =
renderContext.timerQueryExt
.getQueryObjectEXT(renderContext.compositingTimerQuery,
renderContext.timerQueryExt.QUERY_RESULT_EXT);
this.lastTimings = {
compositing: compositingTime / 1000000.0,
rendering: atlasRenderingTime / 1000000.0,
};
this.newTimingsReceived();
window.clearInterval(this.timerQueryPollInterval!);
this.timerQueryPollInterval = null;
}, TIME_INTERVAL_DELAY);
}
private initLUTTexture(imageName: 'gammaLUT' | 'areaLUT',
textureName: 'gammaLUTTexture' | 'areaLUTTexture'):
void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const image = renderContext[imageName];
const texture = unwrapNull(gl.createTexture());
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, image);
const filter = imageName === 'gammaLUT' ? gl.NEAREST : gl.LINEAR;
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
this[textureName] = texture;
}
private initImplicitCoverCurveVAO(objectIndex: number, instanceRange: Range): void {
if (this.meshBuffers == null)
return;
const renderContext = this.renderContext;
const gl = renderContext.gl;
const meshIndex = this.meshIndexForObject(objectIndex);
const meshes = this.meshBuffers[meshIndex];
const meshData = unwrapNull(this.meshes)[meshIndex];
const directCurveProgramName = this.directCurveProgramName();
const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName];
gl.useProgram(directCurveProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions);
gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
gl.vertexAttribPointer(directCurveProgram.attributes.aVertexID, 1, gl.FLOAT, false, 0, 0);
if (this.pathIDsAreInstanced)
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
else
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs);
gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
1,
gl.UNSIGNED_SHORT,
false,
0,
instanceRange.start * UINT16_SIZE);
if (this.pathIDsAreInstanced) {
renderContext.instancedArraysExt
.vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1);
}
gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
gl.enableVertexAttribArray(directCurveProgram.attributes.aVertexID);
gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
}
private initImplicitCoverInteriorVAO(objectIndex: number,
instanceRange: Range,
renderingMode: DirectRenderingMode):
void {
if (this.meshBuffers == null)
return;
const renderContext = this.renderContext;
const gl = renderContext.gl;
const meshIndex = this.meshIndexForObject(objectIndex);
const meshes = this.meshBuffers[meshIndex];
const directInteriorProgramName = this.directInteriorProgramName(renderingMode);
const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName];
gl.useProgram(directInteriorProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions);
gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition,
2,
gl.FLOAT,
false,
0,
0);
if (this.pathIDsAreInstanced)
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
else
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs);
gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
1,
gl.UNSIGNED_SHORT,
false,
0,
instanceRange.start * UINT16_SIZE);
if (this.pathIDsAreInstanced) {
renderContext.instancedArraysExt
.vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1);
}
if (directInteriorProgramName === 'conservativeInterior') {
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
gl.vertexAttribPointer(directInteriorProgram.attributes.aVertexID,
1,
gl.FLOAT,
false,
0,
0);
}
gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
if (directInteriorProgramName === 'conservativeInterior')
gl.enableVertexAttribArray(directInteriorProgram.attributes.aVertexID);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.bQuadVertexInteriorIndices);
}
private initInstancedPathIDVBO(): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const pathIDs = new Uint16Array(MAX_PATHS);
for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++)
pathIDs[pathIndex] = pathIndex + 1;
this.instancedPathIDVBO = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
gl.bufferData(gl.ARRAY_BUFFER, pathIDs, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
private initVertexIDVBO(): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const vertexIDs = new Float32Array(MAX_VERTICES);
for (let vertexID = 0; vertexID < MAX_VERTICES; vertexID++)
vertexIDs[vertexID] = vertexID;
this.vertexIDVBO = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
gl.bufferData(gl.ARRAY_BUFFER, vertexIDs, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
private computeTransform(pass: number, objectIndex: number): glmatrix.mat4 {
let transform;
if (this.antialiasingStrategy == null)
transform = glmatrix.mat4.create();
else
transform = this.antialiasingStrategy.worldTransformForPass(this, pass);
glmatrix.mat4.mul(transform, transform, this.worldTransform);
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
return transform;
}
}
function getMeshIndexRange(indexRanges: Range[], pathRange: Range): Range {
if (indexRanges.length === 0)
return new Range(0, 0);
const lastIndexRange = unwrapUndef(_.last(indexRanges));
const descending = indexRanges[0].start > lastIndexRange.start;
pathRange = new Range(pathRange.start - 1, pathRange.end - 1);
let startIndex;
if (pathRange.start >= indexRanges.length)
startIndex = lastIndexRange.end;
else if (!descending)
startIndex = indexRanges[pathRange.start].start;
else
startIndex = indexRanges[pathRange.start].end;
let endIndex;
if (descending)
endIndex = indexRanges[pathRange.end - 1].start;
else if (pathRange.end >= indexRanges.length)
endIndex = lastIndexRange.end;
else
endIndex = indexRanges[pathRange.end].start;
if (descending) {
const tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
}
return new Range(startIndex, endIndex);
}

View File

@ -1,189 +0,0 @@
// pathfinder/demo/client/src/shader-loader.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import {AttributeMap, UniformMap} from './gl-utils';
import {expectNotNull, PathfinderError, unwrapNull} from './utils';
export interface ShaderMap<T> {
blitLinear: T;
blitGamma: T;
conservativeInterior: T;
demo3DDistantGlyph: T;
demo3DMonument: T;
directCurve: T;
directInterior: T;
direct3DCurve: T;
direct3DInterior: T;
mcaa: T;
ssaaSubpixelResolve: T;
stencilAAA: T;
xcaaMonoResolve: T;
xcaaMonoSubpixelResolve: T;
}
export interface UnlinkedShaderProgram {
vertex: WebGLShader;
fragment: WebGLShader;
}
const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl';
export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
'blitLinear',
'blitGamma',
'conservativeInterior',
'directCurve',
'directInterior',
'direct3DCurve',
'direct3DInterior',
'ssaaSubpixelResolve',
'mcaa',
'stencilAAA',
'xcaaMonoResolve',
'xcaaMonoSubpixelResolve',
'demo3DDistantGlyph',
'demo3DMonument',
];
const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
blitGamma: {
fragment: "/glsl/gles2/blit-gamma.fs.glsl",
vertex: "/glsl/gles2/blit.vs.glsl",
},
blitLinear: {
fragment: "/glsl/gles2/blit-linear.fs.glsl",
vertex: "/glsl/gles2/blit.vs.glsl",
},
conservativeInterior: {
fragment: "/glsl/gles2/direct-interior.fs.glsl",
vertex: "/glsl/gles2/conservative-interior.vs.glsl",
},
demo3DDistantGlyph: {
fragment: "/glsl/gles2/demo-3d-distant-glyph.fs.glsl",
vertex: "/glsl/gles2/demo-3d-distant-glyph.vs.glsl",
},
demo3DMonument: {
fragment: "/glsl/gles2/demo-3d-monument.fs.glsl",
vertex: "/glsl/gles2/demo-3d-monument.vs.glsl",
},
direct3DCurve: {
fragment: "/glsl/gles2/direct-curve.fs.glsl",
vertex: "/glsl/gles2/direct-3d-curve.vs.glsl",
},
direct3DInterior: {
fragment: "/glsl/gles2/direct-interior.fs.glsl",
vertex: "/glsl/gles2/direct-3d-interior.vs.glsl",
},
directCurve: {
fragment: "/glsl/gles2/direct-curve.fs.glsl",
vertex: "/glsl/gles2/direct-curve.vs.glsl",
},
directInterior: {
fragment: "/glsl/gles2/direct-interior.fs.glsl",
vertex: "/glsl/gles2/direct-interior.vs.glsl",
},
mcaa: {
fragment: "/glsl/gles2/mcaa.fs.glsl",
vertex: "/glsl/gles2/mcaa.vs.glsl",
},
ssaaSubpixelResolve: {
fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl",
vertex: "/glsl/gles2/blit.vs.glsl",
},
stencilAAA: {
fragment: "/glsl/gles2/stencil-aaa.fs.glsl",
vertex: "/glsl/gles2/stencil-aaa.vs.glsl",
},
xcaaMonoResolve: {
fragment: "/glsl/gles2/xcaa-mono-resolve.fs.glsl",
vertex: "/glsl/gles2/xcaa-mono-resolve.vs.glsl",
},
xcaaMonoSubpixelResolve: {
fragment: "/glsl/gles2/xcaa-mono-subpixel-resolve.fs.glsl",
vertex: "/glsl/gles2/xcaa-mono-subpixel-resolve.vs.glsl",
},
};
export interface ShaderProgramSource {
vertex: string;
fragment: string;
}
interface ShaderProgramURLs {
vertex: string;
fragment: string;
}
export class ShaderLoader {
common!: Promise<string>;
shaders!: Promise<ShaderMap<ShaderProgramSource>>;
load(): void {
this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text());
const shaderKeys = Object.keys(SHADER_URLS) as Array<keyof ShaderMap<string>>;
const promises = [];
for (const shaderKey of shaderKeys) {
promises.push(Promise.all([
window.fetch(SHADER_URLS[shaderKey].vertex).then(response => response.text()),
window.fetch(SHADER_URLS[shaderKey].fragment).then(response => response.text()),
]).then(results => ({ vertex: results[0], fragment: results[1] })));
}
this.shaders = Promise.all(promises).then(promises => {
const shaderMap: Partial<ShaderMap<ShaderProgramSource>> = {};
for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++)
shaderMap[shaderKeys[keyIndex]] = promises[keyIndex];
return shaderMap as ShaderMap<ShaderProgramSource>;
});
}
}
export class PathfinderShaderProgram {
readonly uniforms: UniformMap;
readonly attributes: AttributeMap;
readonly program: WebGLProgram;
readonly programName: string;
constructor(gl: WebGLRenderingContext,
programName: string,
unlinkedShaderProgram: UnlinkedShaderProgram) {
this.programName = programName;
this.program = expectNotNull(gl.createProgram(), "Failed to create shader program!");
for (const compiledShader of Object.values(unlinkedShaderProgram))
gl.attachShader(this.program, compiledShader);
gl.linkProgram(this.program);
if (gl.getProgramParameter(this.program, gl.LINK_STATUS) === 0) {
const infoLog = gl.getProgramInfoLog(this.program);
throw new PathfinderError(`Failed to link program "${programName}":\n${infoLog}`);
}
const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
const attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
const uniforms: UniformMap = {};
const attributes: AttributeMap = {};
for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) {
const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name;
uniforms[uniformName] = expectNotNull(gl.getUniformLocation(this.program, uniformName),
`Didn't find uniform "${uniformName}"!`);
}
for (let attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
const attributeName = unwrapNull(gl.getActiveAttrib(this.program, attributeIndex)).name;
attributes[attributeName] = attributeIndex;
}
this.uniforms = uniforms;
this.attributes = attributes;
}
}

View File

@ -1,202 +0,0 @@
// pathfinder/demo/client/src/ssaa-strategy.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, DirectRenderingMode, SubpixelAAType, TileInfo} from './aa-strategy';
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
import {createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
import {Renderer} from './renderer';
import {unwrapNull} from './utils';
import {DemoView} from './view';
export default class SSAAStrategy extends AntialiasingStrategy {
get passCount(): number {
switch (this.level) {
case 16:
return 4;
case 8:
return 2;
}
return 1;
}
private level: number;
private destFramebufferSize: glmatrix.vec2;
private supersampledFramebufferSize: glmatrix.vec2;
private supersampledColorTexture!: WebGLTexture;
private supersampledDepthTexture!: WebGLTexture;
private supersampledFramebuffer!: WebGLFramebuffer;
constructor(level: number, subpixelAA: SubpixelAAType) {
super(subpixelAA);
this.level = level;
this.subpixelAA = subpixelAA;
this.destFramebufferSize = glmatrix.vec2.create();
this.supersampledFramebufferSize = glmatrix.vec2.create();
}
attachMeshes(renderer: Renderer): void {}
setFramebufferSize(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
this.destFramebufferSize = glmatrix.vec2.clone(renderer.destAllocatedSize);
this.supersampledFramebufferSize = glmatrix.vec2.create();
glmatrix.vec2.mul(this.supersampledFramebufferSize,
this.destFramebufferSize,
this.supersampleScale);
this.supersampledColorTexture =
createFramebufferColorTexture(gl,
this.supersampledFramebufferSize,
renderContext.colorAlphaFormat,
gl.LINEAR);
this.supersampledDepthTexture =
createFramebufferDepthTexture(gl, this.supersampledFramebufferSize);
this.supersampledFramebuffer = createFramebuffer(gl,
this.supersampledColorTexture,
this.supersampledDepthTexture);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
get transform(): glmatrix.mat4 {
const scale = glmatrix.vec2.create();
glmatrix.vec2.div(scale, this.supersampledFramebufferSize, this.destFramebufferSize);
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromScaling(transform, [scale[0], scale[1], 1.0]);
return transform;
}
prepareForRendering(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const framebufferSize = this.supersampledFramebufferSize;
const usedSize = this.usedSupersampledFramebufferSize(renderer);
gl.bindFramebuffer(gl.FRAMEBUFFER, this.supersampledFramebuffer);
gl.viewport(0, 0, framebufferSize[0], framebufferSize[1]);
gl.scissor(0, 0, usedSize[0], usedSize[1]);
gl.enable(gl.SCISSOR_TEST);
const clearColor = renderer.backgroundColor;
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
prepareForDirectRendering(renderer: Renderer): void {}
prepareToRenderObject(renderer: Renderer, objectIndex: number): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.supersampledFramebuffer);
gl.viewport(0,
0,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1]);
gl.disable(gl.SCISSOR_TEST);
}
finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {}
antialiasObject(renderer: Renderer): void {}
finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {}
resolveAAForObject(renderer: Renderer): void {}
resolve(pass: number, renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
renderer.setDrawViewport();
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.BLEND);
// Set up the blit program VAO.
let resolveProgram;
if (this.subpixelAA !== 'none')
resolveProgram = renderContext.shaderPrograms.ssaaSubpixelResolve;
else
resolveProgram = renderContext.shaderPrograms.blitLinear;
gl.useProgram(resolveProgram.program);
renderContext.initQuadVAO(resolveProgram.attributes);
// Resolve framebuffer.
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.supersampledColorTexture);
gl.uniform1i(resolveProgram.uniforms.uSource, 0);
gl.uniform2i(resolveProgram.uniforms.uSourceDimensions,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1]);
const tileInfo = this.tileInfoForPass(pass);
renderer.setTransformAndTexScaleUniformsForDest(resolveProgram.uniforms, tileInfo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
}
worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
const tileInfo = this.tileInfoForPass(pass);
const usedSize = renderer.destUsedSize;
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [tileInfo.size[0], tileInfo.size[1], 1.0]);
glmatrix.mat4.translate(transform, transform, [
-tileInfo.position[0] / tileInfo.size[0] * 2.0,
-tileInfo.position[1] / tileInfo.size[1] * 2.0,
0.0,
]);
glmatrix.mat4.translate(transform, transform, [1.0, 1.0, 0.0]);
return transform;
}
private tileInfoForPass(pass: number): TileInfo {
const tileSize = this.tileSize;
return {
position: glmatrix.vec2.clone([pass % tileSize[0], Math.floor(pass / tileSize[0])]),
size: tileSize,
};
}
get directRenderingMode(): DirectRenderingMode {
return 'color';
}
private get supersampleScale(): glmatrix.vec2 {
return glmatrix.vec2.clone([this.subpixelAA !== 'none' ? 3 : 2, this.level === 2 ? 1 : 2]);
}
private get tileSize(): glmatrix.vec2 {
switch (this.level) {
case 16:
return glmatrix.vec2.clone([2.0, 2.0]);
case 8:
return glmatrix.vec2.clone([2.0, 1.0]);
}
return glmatrix.vec2.clone([1.0, 1.0]);
}
private usedSupersampledFramebufferSize(renderer: Renderer): glmatrix.vec2 {
const result = glmatrix.vec2.create();
glmatrix.vec2.mul(result, renderer.destUsedSize, this.supersampleScale);
return result;
}
}

View File

@ -1,120 +0,0 @@
// pathfinder/demo/client/src/svg-demo.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {DemoAppController} from './app-controller';
import {OrthographicCamera} from "./camera";
import {PathfinderPackedMeshes} from "./meshes";
import {ShaderMap, ShaderProgramSource} from './shader-loader';
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
import {SVGRenderer} from './svg-renderer';
import {DemoView} from './view';
const parseColor = require('parse-color');
const SVG_NS: string = "http://www.w3.org/2000/svg";
const DEFAULT_FILE: string = 'tiger';
class SVGDemoController extends DemoAppController<SVGDemoView> {
loader!: SVGLoader;
protected readonly builtinFileURI: string = BUILTIN_SVG_URI;
private meshes!: PathfinderPackedMeshes;
start() {
super.start();
this.loader = new SVGLoader;
this.loadInitialFile(this.builtinFileURI);
}
protected fileLoaded(fileData: ArrayBuffer) {
this.loader.loadFile(fileData);
this.loader.partition().then(meshes => {
this.meshes = new PathfinderPackedMeshes(meshes);
this.meshesReceived();
});
}
protected createView(areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>):
SVGDemoView {
return new SVGDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected get defaultFile(): string {
return DEFAULT_FILE;
}
private meshesReceived(): void {
this.view.then(view => {
view.attachMeshes([this.meshes]);
view.initCameraBounds(this.loader.svgViewBox);
});
}
}
class SVGDemoView extends DemoView {
renderer: SVGDemoRenderer;
appController: SVGDemoController;
get camera(): OrthographicCamera {
return this.renderer.camera;
}
constructor(appController: SVGDemoController,
areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new SVGDemoRenderer(this, {sizeToFit: true});
this.resizeToFit(true);
}
initCameraBounds(viewBox: glmatrix.vec4): void {
this.renderer.initCameraBounds(viewBox);
}
}
class SVGDemoRenderer extends SVGRenderer {
renderContext!: SVGDemoView;
needsStencil: boolean = false;
protected get loader(): SVGLoader {
return this.renderContext.appController.loader;
}
protected get canvas(): HTMLCanvasElement {
return this.renderContext.canvas;
}
protected newTimingsReceived(): void {
this.renderContext.appController.newTimingsReceived(this.lastTimings);
}
}
function main() {
const controller = new SVGDemoController;
window.addEventListener('load', () => controller.start(), false);
}
main();

View File

@ -1,254 +0,0 @@
// pathfinder/client/src/svg-loader.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as base64js from 'base64-js';
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import 'path-data-polyfill.js';
import {parseServerTiming, PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
import {lerp, panic, Range, unwrapNull, unwrapUndef} from "./utils";
export const BUILTIN_SVG_URI: string = "/svg/demo";
const parseColor = require('parse-color');
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
declare class SVGPathSegment {
type: string;
values: number[];
}
declare global {
interface SVGPathElement {
getPathData(settings: any): SVGPathSegment[];
}
}
type FillRule = 'evenodd' | 'winding';
export abstract class SVGPath {
element: SVGPathElement;
color: glmatrix.vec4;
constructor(element: SVGPathElement, colorProperty: keyof CSSStyleDeclaration) {
this.element = element;
const style = window.getComputedStyle(element);
this.color = unwrapNull(colorFromStyle(style[colorProperty]));
}
}
export class SVGFill extends SVGPath {
fillRule: FillRule;
get pathfinderFillRule(): string {
return { evenodd: 'EvenOdd', winding: 'Winding' }[this.fillRule];
}
constructor(element: SVGPathElement) {
super(element, 'fill');
const style = window.getComputedStyle(element);
this.fillRule = style.fillRule === 'evenodd' ? 'evenodd' : 'winding';
}
}
export class SVGStroke extends SVGPath {
width: number;
constructor(element: SVGPathElement) {
super(element, 'stroke');
const style = window.getComputedStyle(element);
const ctm = unwrapNull(element.getCTM());
const strokeWidthString = unwrapNull(style.strokeWidth);
const matches = /^(\d+\.?\d*)(.*)$/.exec(strokeWidthString);
let strokeWidth: number;
if (matches == null) {
strokeWidth = 0.0;
} else {
strokeWidth = unwrapNull(parseFloat(matches[1]));
if (matches[2] === 'px')
strokeWidth *= lerp(ctm.a, ctm.d, 0.5);
}
this.width = strokeWidth;
}
}
interface ClipPathIDTable {
[id: string]: number;
}
export class SVGLoader {
pathInstances: SVGPath[];
scale: number;
pathBounds: glmatrix.vec4[];
svgBounds: glmatrix.vec4;
svgViewBox: glmatrix.vec4;
isMonochrome: boolean;
private svg: SVGSVGElement;
private fileData!: ArrayBuffer;
private paths: any[];
private clipPathIDs: ClipPathIDTable;
constructor() {
this.scale = 1.0;
this.pathInstances = [];
this.pathBounds = [];
this.paths = [];
this.clipPathIDs = {};
this.svgBounds = glmatrix.vec4.create();
this.svgViewBox = glmatrix.vec4.create();
this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement;
this.isMonochrome = false;
}
loadFile(fileData: ArrayBuffer) {
this.fileData = fileData;
const decoder = new (window as any).TextDecoder('utf-8');
const fileStringData = decoder.decode(new DataView(this.fileData));
const svgDocument = (new DOMParser).parseFromString(fileStringData, 'image/svg+xml');
const svgElement = svgDocument.documentElement as Element as SVGSVGElement;
this.attachSVG(svgElement);
}
partition(pathIndex?: number | undefined): Promise<PathfinderMeshPack> {
// Make the request.
const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]];
let time = 0;
return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
body: JSON.stringify({
paths: paths,
viewBoxHeight: this.svgViewBox[3] - this.svgViewBox[1],
viewBoxWidth: this.svgViewBox[2] - this.svgViewBox[0],
}),
headers: {'Content-Type': 'application/json'} as any,
method: 'POST',
}).then(response => {
time = parseServerTiming(response.headers);
return response.arrayBuffer();
}).then(buffer => new PathfinderMeshPack(buffer));
}
private attachSVG(svgElement: SVGSVGElement) {
// Clear out the current document.
let kid;
while ((kid = this.svg.firstChild) != null)
this.svg.removeChild(kid);
// Add all kids of the incoming SVG document.
while ((kid = svgElement.firstChild) != null)
this.svg.appendChild(kid);
const viewBox = svgElement.viewBox.baseVal;
this.svg.setAttribute('width', "" + viewBox.width);
this.svg.setAttribute('height', "" + viewBox.height);
// Scan for geometry elements.
this.pathInstances.length = 0;
this.clipPathIDs = {};
this.scanElement(this.svg);
this.paths = [];
for (const instance of this.pathInstances) {
const element = instance.element;
const svgCTM = unwrapNull(element.getCTM());
const ctm = glmatrix.mat2d.fromValues(svgCTM.a, -svgCTM.b,
svgCTM.c, -svgCTM.d,
svgCTM.e, viewBox.height - svgCTM.f);
glmatrix.mat2d.scale(ctm, ctm, [this.scale, this.scale]);
const bottomLeft = glmatrix.vec2.create();
const topRight = glmatrix.vec2.create();
const segments = element.getPathData({ normalize: true }).map(segment => {
const newValues = _.flatMap(_.chunk(segment.values, 2), coords => {
const point = glmatrix.vec2.create();
glmatrix.vec2.transformMat2d(point, coords, ctm);
glmatrix.vec2.min(bottomLeft, bottomLeft, point);
glmatrix.vec2.max(topRight, topRight, point);
return [point[0], point[1]];
});
return {
type: segment.type,
values: newValues,
};
});
const pathBounds = glmatrix.vec4.clone([
bottomLeft[0], bottomLeft[1],
topRight[0], topRight[1],
]);
if (instance instanceof SVGFill) {
this.paths.push({
kind: { Fill: instance.pathfinderFillRule },
segments: segments,
});
this.pathBounds.push(pathBounds);
} else if (instance instanceof SVGStroke) {
this.paths.push({ kind: { Stroke: instance.width }, segments: segments });
this.pathBounds.push(pathBounds);
}
}
this.isMonochrome = this.pathInstances.every(pathInstance => {
return glmatrix.vec4.equals(pathInstance.color, this.pathInstances[0].color);
});
this.svgViewBox = glmatrix.vec4.clone([
viewBox.x, viewBox.y, viewBox.x + viewBox.width, viewBox.y + viewBox.height,
]);
}
private scanElement(element: Element): void {
const style = window.getComputedStyle(element);
if (element instanceof SVGPathElement) {
if (colorFromStyle(style.fill) != null)
this.addPathInstance(new SVGFill(element));
if (colorFromStyle(style.stroke) != null)
this.addPathInstance(new SVGStroke(element));
}
for (const kid of element.childNodes) {
if (kid instanceof Element)
this.scanElement(kid);
}
}
private addPathInstance(pathInstance: SVGPath): void {
this.pathInstances.push(pathInstance);
}
}
function colorFromStyle(style: string | null): glmatrix.vec4 | null {
if (style == null || style === 'none')
return null;
// TODO(pcwalton): Gradients?
const color = parseColor(style);
if (color.rgba == null)
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
if (color.rgba[3] === 0.0)
return null;
return glmatrix.vec4.clone(color.rgba);
}

View File

@ -1,217 +0,0 @@
// pathfinder/demo/client/src/svg-renderer.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy';
import {NoAAStrategy} from './aa-strategy';
import {SubpixelAAType} from './aa-strategy';
import {OrthographicCamera} from "./camera";
import {UniformMap} from './gl-utils';
import {PathfinderPackedMeshes} from './meshes';
import {PathTransformBuffers, Renderer} from "./renderer";
import {ShaderMap} from './shader-loader';
import SSAAStrategy from './ssaa-strategy';
import {SVGLoader} from './svg-loader';
import {Range} from './utils';
import {RenderContext} from './view';
import {MCAAStrategy, XCAAStrategy} from './xcaa-strategy';
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
xcaa: typeof MCAAStrategy;
}
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
xcaa: MCAAStrategy,
};
export interface SVGRendererOptions {
sizeToFit?: boolean;
fixed?: boolean;
}
export abstract class SVGRenderer extends Renderer {
renderContext!: RenderContext;
camera: OrthographicCamera;
needsStencil: boolean = false;
private options: SVGRendererOptions;
get isMulticolor(): boolean {
return !this.loader.isMonochrome;
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
get fgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
}
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);
}
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
get backgroundColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
get allowSubpixelAA(): boolean {
return false;
}
protected get objectCount(): number {
return 1;
}
protected abstract get loader(): SVGLoader;
protected abstract get canvas(): HTMLCanvasElement;
constructor(renderContext: RenderContext, options: SVGRendererOptions) {
super(renderContext);
this.options = options;
// FIXME(pcwalton): Get the canvas a better way?
this.camera = new OrthographicCamera((this as any).canvas, {
fixed: !!this.options.fixed,
scaleBounds: true,
});
this.camera.onPan = () => this.renderContext.setDirty();
this.camera.onZoom = () => this.renderContext.setDirty();
this.camera.onRotate = () => this.renderContext.setDirty();
}
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
pathBoundingRects(objectIndex: number): Float32Array {
const loader = this.loader;
const boundingRectsBuffer = new Float32Array((loader.pathBounds.length + 1) * 4);
for (let pathIndex = 0; pathIndex < loader.pathBounds.length; pathIndex++)
boundingRectsBuffer.set(loader.pathBounds[pathIndex], (pathIndex + 1) * 4);
return boundingRectsBuffer;
}
attachMeshes(meshes: PathfinderPackedMeshes[]): void {
super.attachMeshes(meshes);
this.uploadPathColors(1);
this.uploadPathTransforms(1);
}
initCameraBounds(svgViewBox: glmatrix.vec4): void {
// The SVG origin is in the upper left, but the camera origin is in the lower left.
this.camera.bounds = svgViewBox;
if (this.options.sizeToFit)
this.camera.zoomToFit();
}
meshIndexForObject(objectIndex: number): number {
return 0;
}
pathRangeForObject(objectIndex: number): Range {
return new Range(1, this.loader.pathInstances.length + 1);
}
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
const instances = this.loader.pathInstances;
const pathTransforms = this.createPathTransformBuffers(instances.length);
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
// TODO(pcwalton): Set transform.
const startOffset = (pathIndex + 1) * 4;
pathTransforms.st.set([1, 1, 0, 0], startOffset);
}
return pathTransforms;
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get worldTransform(): glmatrix.mat4 {
const canvas = this.canvas;
const transform = glmatrix.mat4.create();
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
const centerPoint = glmatrix.vec3.clone([canvas.width * 0.5, canvas.height * 0.5, 0.0]);
glmatrix.mat4.translate(transform, transform, centerPoint);
glmatrix.mat4.rotateZ(transform, transform, this.camera.rotationAngle);
glmatrix.vec3.negate(centerPoint, centerPoint);
glmatrix.mat4.translate(transform, transform, centerPoint);
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
return transform;
}
protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
return glmatrix.vec4.create();
}
protected directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected directInteriorProgramName(renderingMode: DirectRenderingMode):
keyof ShaderMap<void> {
return renderingMode === 'conservative' ? 'conservativeInterior' : 'directInterior';
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const instances = this.loader.pathInstances;
const pathColors = new Uint8Array(4 * (instances.length + 1));
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
const startOffset = (pathIndex + 1) * 4;
// Set color.
const color: ArrayLike<number> = instances[pathIndex].color;
pathColors.set(instances[pathIndex].color, startOffset);
pathColors[startOffset + 3] = color[3] * 255;
}
return pathColors;
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected compositeIfNecessary(): void {}
}

View File

@ -1,470 +0,0 @@
// pathfinder/client/src/text-demo.ts
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
import * as base64js from 'base64-js';
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import * as opentype from 'opentype.js';
import {Metrics} from 'opentype.js';
import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {AAOptions, DemoAppController, setSwitchInputsValue, SwitchInputs} from './app-controller';
import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas';
import PathfinderBufferTexture from './buffer-texture';
import {CameraView, OrthographicCamera} from "./camera";
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
import {UniformMap} from './gl-utils';
import {PathfinderMeshPack, PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
import SSAAStrategy from './ssaa-strategy';
import {calculatePixelRectForGlyph, PathfinderFont} from "./text";
import {BUILTIN_FONT_URI, calculatePixelXMin, computeStemDarkeningAmount} from "./text";
import {GlyphStore, Hint, SimpleTextLayout, UnitMetrics} from "./text";
import {SimpleTextLayoutRenderContext, SimpleTextLayoutRenderer} from './text-renderer';
import {TextRenderContext, TextRenderer} from './text-renderer';
import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils';
import {unwrapNull} from './utils';
import {DemoView, RenderContext, Timings, TIMINGS} from './view';
const DEFAULT_TEXT: string =
`Twas brillig, and the slithy toves
Did gyre and gimble in the wabe;
All mimsy were the borogoves,
And the mome raths outgrabe.
Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!
He took his vorpal sword in hand:
Long time the manxome foe he sought
So rested he by the Tumtum tree,
And stood awhile in thought.
And as in uffish thought he stood,
The Jabberwock, with eyes of flame,
Came whiffling through the tulgey wood,
And burbled as it came!
One, two! One, two! And through and through
The vorpal blade went snicker-snack!
He left it dead, and with its head
He went galumphing back.
And hast thou slain the Jabberwock?
Come to my arms, my beamish boy!
O frabjous day! Callooh! Callay!
He chortled in his joy.
Twas brillig, and the slithy toves
Did gyre and gimble in the wabe;
All mimsy were the borogoves,
And the mome raths outgrabe.`;
const INITIAL_FONT_SIZE: number = 72.0;
const DEFAULT_FONT: string = 'open-sans';
const B_POSITION_SIZE: number = 8;
const B_PATH_INDEX_SIZE: number = 2;
declare global {
interface Window {
jQuery(element: HTMLElement): JQuerySubset;
}
}
interface TabShownEvent {
target: EventTarget;
relatedTarget: EventTarget;
}
interface JQuerySubset {
modal(options?: any): void;
on(name: 'shown.bs.tab', handler: (event: TabShownEvent) => void): void;
}
type Matrix4D = Float32Array;
type Rect = glmatrix.vec4;
interface Point2D {
x: number;
y: number;
}
type Size2D = glmatrix.vec2;
type ShaderType = number;
// `opentype.js` monkey patches
declare module 'opentype.js' {
interface Font {
isSupported(): boolean;
lineHeight(): number;
}
interface Glyph {
getIndex(): number;
}
}
class TextDemoController extends DemoAppController<TextDemoView> {
font!: PathfinderFont;
layout!: SimpleTextLayout;
glyphStore!: GlyphStore;
atlasGlyphs!: AtlasGlyph[];
private hintingSelect!: HTMLSelectElement;
private emboldenInput!: HTMLInputElement;
private editTextModal!: HTMLElement;
private editTextArea!: HTMLTextAreaElement;
private _atlas: Atlas;
private meshes!: PathfinderPackedMeshes;
private _fontSize!: number;
private _rotationAngle!: number;
private _emboldenAmount!: number;
private text: string;
constructor() {
super();
this.text = DEFAULT_TEXT;
this._atlas = new Atlas;
}
start(): void {
this._fontSize = INITIAL_FONT_SIZE;
this._rotationAngle = 0.0;
this._emboldenAmount = 0.0;
this.hintingSelect = unwrapNull(document.getElementById('pf-hinting-select')) as
HTMLSelectElement;
this.hintingSelect.addEventListener('change', () => this.hintingChanged(), false);
this.emboldenInput = unwrapNull(document.getElementById('pf-embolden')) as
HTMLInputElement;
this.emboldenInput.addEventListener('input', () => this.emboldenAmountChanged(), false);
this.editTextModal = unwrapNull(document.getElementById('pf-edit-text-modal'));
this.editTextArea = unwrapNull(document.getElementById('pf-edit-text-area')) as
HTMLTextAreaElement;
const editTextOkButton = unwrapNull(document.getElementById('pf-edit-text-ok-button'));
editTextOkButton.addEventListener('click', () => this.updateText(), false);
super.start();
this.loadInitialFile(this.builtinFileURI);
}
showTextEditor(): void {
this.editTextArea.value = this.text;
window.jQuery(this.editTextModal).modal();
}
get emboldenAmount(): number {
return this._emboldenAmount;
}
protected updateUIForAALevelChange(aaType: AntialiasingStrategyName, aaLevel: number): void {
const gammaCorrectionSwitchInputs = unwrapNull(this.gammaCorrectionSwitchInputs);
const stemDarkeningSwitchInputs = unwrapNull(this.stemDarkeningSwitchInputs);
const emboldenInput = unwrapNull(this.emboldenInput);
const emboldenLabel = getLabelFor(emboldenInput);
switch (aaType) {
case 'none':
case 'ssaa':
enableSwitchInputs(gammaCorrectionSwitchInputs, false);
enableSwitchInputs(stemDarkeningSwitchInputs, false);
emboldenInput.value = "0";
emboldenInput.disabled = true;
enableLabel(emboldenLabel, false);
break;
case 'xcaa':
enableSwitchInputs(gammaCorrectionSwitchInputs, true);
enableSwitchInputs(stemDarkeningSwitchInputs, true);
emboldenInput.value = "0";
emboldenInput.disabled = false;
enableLabel(emboldenLabel, true);
break;
}
function enableSwitchInputs(switchInputs: SwitchInputs, enabled: boolean): void {
switchInputs.off.disabled = switchInputs.on.disabled = !enabled;
enableLabel(getLabelFor(switchInputs.on), enabled);
}
function enableLabel(label: HTMLLabelElement | null, enabled: boolean): void {
if (label == null)
return;
if (enabled)
label.classList.remove('pf-disabled');
else
label.classList.add('pf-disabled');
}
function getLabelFor(element: HTMLElement): HTMLLabelElement | null {
return document.querySelector(`label[for="${element.id}"]`) as HTMLLabelElement | null;
}
}
protected createView(areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>):
TextDemoView {
return new TextDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
}
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null) {
const font = new PathfinderFont(fileData, builtinName);
this.recreateLayout(font);
}
private hintingChanged(): void {
this.view.then(view => view.renderer.updateHinting());
}
private emboldenAmountChanged(): void {
this._emboldenAmount = parseFloat(this.emboldenInput.value);
this.view.then(view => view.renderer.updateEmboldenAmount());
}
private updateText(): void {
this.text = this.editTextArea.value;
window.jQuery(this.editTextModal).modal('hide');
this.recreateLayout(this.font);
}
private recreateLayout(font: PathfinderFont) {
const newLayout = new SimpleTextLayout(font, this.text);
let uniqueGlyphIDs = newLayout.textFrame.allGlyphIDs;
uniqueGlyphIDs.sort((a, b) => a - b);
uniqueGlyphIDs = _.sortedUniq(uniqueGlyphIDs);
const glyphStore = new GlyphStore(font, uniqueGlyphIDs);
glyphStore.partition().then(result => {
const meshes = this.expandMeshes(result.meshes, uniqueGlyphIDs.length);
this.view.then(view => {
this.font = font;
this.layout = newLayout;
this.glyphStore = glyphStore;
this.meshes = meshes;
view.attachText();
view.attachMeshes([this.meshes]);
});
});
}
private expandMeshes(meshes: PathfinderMeshPack, glyphCount: number): PathfinderPackedMeshes {
const pathIDs = [];
for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
for (let subpixel = 0; subpixel < SUBPIXEL_GRANULARITY; subpixel++)
pathIDs.push(glyphIndex + 1);
}
return new PathfinderPackedMeshes(meshes, pathIDs);
}
get atlas(): Atlas {
return this._atlas;
}
/// The font size in pixels per em.
get fontSize(): number {
return this._fontSize;
}
/// The font size in pixels per em.
set fontSize(newFontSize: number) {
this._fontSize = newFontSize;
this.view.then(view => view.renderer.relayoutText());
}
get rotationAngle(): number {
return this._rotationAngle;
}
set rotationAngle(newRotationAngle: number) {
this._rotationAngle = newRotationAngle;
this.view.then(view => view.renderer.relayoutText());
}
get unitsPerEm(): number {
return this.font.opentypeFont.unitsPerEm;
}
get pixelsPerUnit(): number {
return this._fontSize / this.unitsPerEm;
}
get useHinting(): boolean {
return this.hintingSelect.selectedIndex !== 0;
}
get pathCount(): number {
return this.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY;
}
protected get builtinFileURI(): string {
return BUILTIN_FONT_URI;
}
protected get defaultFile(): string {
return DEFAULT_FONT;
}
}
class TextDemoView extends DemoView implements SimpleTextLayoutRenderContext {
renderer: TextDemoRenderer;
appController: TextDemoController;
get cameraView(): CameraView {
return this.canvas;
}
get atlasGlyphs(): AtlasGlyph[] {
return this.appController.atlasGlyphs;
}
set atlasGlyphs(newAtlasGlyphs: AtlasGlyph[]) {
this.appController.atlasGlyphs = newAtlasGlyphs;
}
get atlas(): Atlas {
return this.appController.atlas;
}
get glyphStore(): GlyphStore {
return this.appController.glyphStore;
}
get font(): PathfinderFont {
return this.appController.font;
}
get fontSize(): number {
return this.appController.fontSize;
}
get pathCount(): number {
return this.appController.pathCount;
}
get pixelsPerUnit(): number {
return this.appController.pixelsPerUnit;
}
get useHinting(): boolean {
return this.appController.useHinting;
}
get layout(): SimpleTextLayout {
return this.appController.layout;
}
get rotationAngle(): number {
return this.appController.rotationAngle;
}
get emboldenAmount(): number {
return this.appController.emboldenAmount;
}
get unitsPerEm(): number {
return this.appController.unitsPerEm;
}
protected get camera(): OrthographicCamera {
return this.renderer.camera;
}
constructor(appController: TextDemoController,
areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new TextDemoRenderer(this);
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
this.resizeToFit(true);
}
attachText() {
this.panZoomEventsEnabled = false;
this.renderer.prepareToAttachText();
this.renderer.camera.zoomToFit();
this.appController.fontSize = this.renderer.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
this.renderer.finishAttachingText();
this.panZoomEventsEnabled = true;
}
newTimingsReceived(newTimings: Timings) {
this.appController.newTimingsReceived(newTimings);
}
protected onPan(): void {
this.renderer.viewPanned();
}
protected onZoom(): void {
this.appController.fontSize = this.renderer.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
}
protected onRotate(): void {
this.appController.rotationAngle = this.renderer.camera.rotationAngle;
}
private set panZoomEventsEnabled(flag: boolean) {
if (flag) {
this.renderer.camera.onPan = () => this.onPan();
this.renderer.camera.onZoom = () => this.onZoom();
this.renderer.camera.onRotate = () => this.onRotate();
} else {
this.renderer.camera.onPan = null;
this.renderer.camera.onZoom = null;
this.renderer.camera.onRotate = null;
}
}
}
class TextDemoRenderer extends SimpleTextLayoutRenderer {
renderContext!: TextDemoView;
}
function main(): void {
const controller = new TextDemoController;
window.addEventListener('load', () => controller.start(), false);
}
main();

View File

@ -1,649 +0,0 @@
// pathfinder/client/src/text-renderer.ts
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {AAOptions} from './app-controller';
import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas';
import {CameraView, OrthographicCamera} from './camera';
import {createFramebuffer, createFramebufferDepthTexture, QUAD_ELEMENTS} from './gl-utils';
import {UniformMap} from './gl-utils';
import {PathTransformBuffers, Renderer} from './renderer';
import {ShaderMap} from './shader-loader';
import SSAAStrategy from './ssaa-strategy';
import {calculatePixelRectForGlyph, computeStemDarkeningAmount, GlyphStore, Hint} from "./text";
import {MAX_STEM_DARKENING_PIXELS_PER_EM, PathfinderFont, SimpleTextLayout} from "./text";
import {UnitMetrics} from "./text";
import {unwrapNull} from './utils';
import {RenderContext, Timings} from "./view";
import {StencilAAAStrategy} from './xcaa-strategy';
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
xcaa: typeof StencilAAAStrategy;
}
const SQRT_1_2: number = 1.0 / Math.sqrt(2.0);
const MIN_SCALE: number = 0.0025;
const MAX_SCALE: number = 0.5;
export const MAX_SUBPIXEL_AA_FONT_SIZE: number = 48.0;
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
xcaa: StencilAAAStrategy,
};
export interface TextRenderContext extends RenderContext {
atlasGlyphs: AtlasGlyph[];
readonly cameraView: CameraView;
readonly atlas: Atlas;
readonly glyphStore: GlyphStore;
readonly font: PathfinderFont;
readonly fontSize: number;
readonly useHinting: boolean;
newTimingsReceived(timings: Timings): void;
}
export abstract class TextRenderer extends Renderer {
renderContext!: TextRenderContext;
camera: OrthographicCamera;
atlasFramebuffer!: WebGLFramebuffer;
atlasDepthTexture!: WebGLTexture;
get isMulticolor(): boolean {
return false;
}
get needsStencil(): boolean {
return this.renderContext.fontSize <= MAX_STEM_DARKENING_PIXELS_PER_EM;
}
get destFramebuffer(): WebGLFramebuffer {
return this.atlasFramebuffer;
}
get destAllocatedSize(): glmatrix.vec2 {
return ATLAS_SIZE;
}
get destUsedSize(): glmatrix.vec2 {
return this.renderContext.atlas.usedSize;
}
get emboldenAmount(): glmatrix.vec2 {
const emboldenAmount = glmatrix.vec2.create();
glmatrix.vec2.add(emboldenAmount, this.extraEmboldenAmount, this.stemDarkeningAmount);
return emboldenAmount;
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
}
get fgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
get rotationAngle(): number {
return 0.0;
}
get allowSubpixelAA(): boolean {
return this.renderContext.fontSize <= MAX_SUBPIXEL_AA_FONT_SIZE;
}
protected get pixelsPerUnit(): number {
return this.renderContext.fontSize / this.renderContext.font.opentypeFont.unitsPerEm;
}
protected get worldTransform(): glmatrix.mat4 {
const transform = glmatrix.mat4.create();
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / ATLAS_SIZE[0], 2.0 / ATLAS_SIZE[1], 1.0]);
return transform;
}
protected get stemDarkeningAmount(): glmatrix.vec2 {
if (this.stemDarkening === 'dark')
return computeStemDarkeningAmount(this.renderContext.fontSize, this.pixelsPerUnit);
return glmatrix.vec2.create();
}
protected get usedSizeFactor(): glmatrix.vec2 {
const usedSize = glmatrix.vec2.create();
glmatrix.vec2.div(usedSize, this.renderContext.atlas.usedSize, ATLAS_SIZE);
return usedSize;
}
private get pathCount(): number {
return this.renderContext.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY;
}
protected get objectCount(): number {
return this.meshBuffers == null ? 0 : this.meshBuffers.length;
}
protected get extraEmboldenAmount(): glmatrix.vec2 {
return glmatrix.vec2.create();
}
private stemDarkening!: StemDarkeningMode;
private subpixelAA!: SubpixelAAType;
constructor(renderContext: TextRenderContext) {
super(renderContext);
this.camera = new OrthographicCamera(this.renderContext.cameraView, {
maxScale: MAX_SCALE,
minScale: MIN_SCALE,
});
}
setHintsUniform(uniforms: UniformMap): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
const hint = this.createHint();
gl.uniform4f(uniforms.uHints,
hint.xHeight,
hint.hintedXHeight,
hint.stemHeight,
hint.hintedStemHeight);
}
pathBoundingRects(objectIndex: number): Float32Array {
const pathCount = this.pathCount;
const atlasGlyphs = this.renderContext.atlasGlyphs;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const font = this.renderContext.font;
const hint = this.createHint();
const boundingRects = new Float32Array((pathCount + 1) * 4);
for (const glyph of atlasGlyphs) {
const atlasGlyphMetrics = font.metricsForGlyph(glyph.glyphKey.id);
if (atlasGlyphMetrics == null)
continue;
const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics, 0.0, this.emboldenAmount);
const pathID = glyph.pathID;
boundingRects[pathID * 4 + 0] = atlasUnitMetrics.left;
boundingRects[pathID * 4 + 1] = atlasUnitMetrics.descent;
boundingRects[pathID * 4 + 2] = atlasUnitMetrics.right;
boundingRects[pathID * 4 + 3] = atlasUnitMetrics.ascent;
}
return boundingRects;
}
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
const pathCount = this.pathCount;
const atlasGlyphs = this.renderContext.atlasGlyphs;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
// FIXME(pcwalton): This is a hack that tries to preserve the vertical extents of the glyph
// after stem darkening. It's better than nothing, but we should really do better.
//
// This hack seems to produce *better* results than what macOS does on sans-serif fonts;
// the ascenders and x-heights of the glyphs are pixel snapped, while they aren't on macOS.
// But we should really figure out what macOS does…
const ascender = this.renderContext.font.opentypeFont.ascender;
const emboldenAmount = this.emboldenAmount;
const stemDarkeningYScale = (ascender + emboldenAmount[1]) / ascender;
const stemDarkeningOffset = glmatrix.vec2.clone(emboldenAmount);
glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, pixelsPerUnit);
glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, SQRT_1_2);
glmatrix.vec2.mul(stemDarkeningOffset, stemDarkeningOffset, [1, stemDarkeningYScale]);
const transform = glmatrix.mat2d.create();
const transforms = this.createPathTransformBuffers(pathCount);
for (const glyph of atlasGlyphs) {
const pathID = glyph.pathID;
const atlasOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
glmatrix.mat2d.identity(transform);
glmatrix.mat2d.translate(transform, transform, atlasOrigin);
glmatrix.mat2d.translate(transform, transform, stemDarkeningOffset);
glmatrix.mat2d.rotate(transform, transform, rotationAngle);
glmatrix.mat2d.scale(transform,
transform,
[pixelsPerUnit, pixelsPerUnit * stemDarkeningYScale]);
transforms.st[pathID * 4 + 0] = transform[0];
transforms.st[pathID * 4 + 1] = transform[3];
transforms.st[pathID * 4 + 2] = transform[4];
transforms.st[pathID * 4 + 3] = transform[5];
transforms.ext[pathID * 2 + 0] = transform[1];
transforms.ext[pathID * 2 + 1] = transform[2];
}
return transforms;
}
protected createAtlasFramebuffer(): void {
const atlasColorTexture = this.renderContext.atlas.ensureTexture(this.renderContext);
this.atlasDepthTexture = createFramebufferDepthTexture(this.renderContext.gl, ATLAS_SIZE);
this.atlasFramebuffer = createFramebuffer(this.renderContext.gl,
atlasColorTexture,
this.atlasDepthTexture);
// Allow the antialiasing strategy to set up framebuffers as necessary.
if (this.antialiasingStrategy != null)
this.antialiasingStrategy.setFramebufferSize(this);
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode):
AntialiasingStrategy {
this.subpixelAA = subpixelAA;
this.stemDarkening = stemDarkening;
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected clearForDirectRendering(): void {}
protected buildAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void {
const renderContext = this.renderContext;
const font = renderContext.font;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const hint = this.createHint();
atlasGlyphs.sort((a, b) => a.glyphKey.sortKey - b.glyphKey.sortKey);
atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.glyphKey.sortKey);
if (atlasGlyphs.length === 0)
return;
renderContext.atlasGlyphs = atlasGlyphs;
renderContext.atlas.layoutGlyphs(atlasGlyphs,
font,
pixelsPerUnit,
rotationAngle,
hint,
this.emboldenAmount);
this.uploadPathTransforms(1);
this.uploadPathColors(1);
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const pathCount = this.pathCount;
const pathColors = new Uint8Array(4 * (pathCount + 1));
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
for (let channel = 0; channel < 3; channel++)
pathColors[(pathIndex + 1) * 4 + channel] = 0xff; // RGB
pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha
}
return pathColors;
}
protected newTimingsReceived(): void {
this.renderContext.newTimingsReceived(this.lastTimings);
}
protected createHint(): Hint {
return new Hint(this.renderContext.font,
this.pixelsPerUnit,
this.renderContext.useHinting);
}
protected directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
}
export interface SimpleTextLayoutRenderContext extends TextRenderContext {
readonly layout: SimpleTextLayout;
readonly rotationAngle: number;
readonly emboldenAmount: number;
readonly unitsPerEm: number;
}
export abstract class SimpleTextLayoutRenderer extends TextRenderer {
abstract get renderContext(): SimpleTextLayoutRenderContext;
glyphPositionsBuffer!: WebGLBuffer;
glyphTexCoordsBuffer!: WebGLBuffer;
glyphElementsBuffer!: WebGLBuffer;
private glyphBounds!: Float32Array;
get layout(): SimpleTextLayout {
return this.renderContext.layout;
}
get backgroundColor(): glmatrix.vec4 {
return glmatrix.vec4.create();
}
get rotationAngle(): number {
return this.renderContext.rotationAngle;
}
protected get extraEmboldenAmount(): glmatrix.vec2 {
const emboldenLength = this.renderContext.emboldenAmount * this.renderContext.unitsPerEm;
return glmatrix.vec2.clone([emboldenLength, emboldenLength]);
}
prepareToAttachText(): void {
if (this.atlasFramebuffer == null)
this.createAtlasFramebuffer();
this.layoutText();
}
finishAttachingText(): void {
this.buildGlyphs();
this.renderContext.setDirty();
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number,
aaOptions: AAOptions):
void {
super.setAntialiasingOptions(aaType, aaLevel, aaOptions);
// Need to relayout because changing AA options can cause font dilation to change...
this.layoutText();
this.buildGlyphs();
this.renderContext.setDirty();
}
relayoutText(): void {
this.layoutText();
this.buildGlyphs();
this.renderContext.setDirty();
}
updateHinting(): void {
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
this.layoutText();
this.buildGlyphs();
this.renderContext.setDirty();
}
updateEmboldenAmount(): void {
// Likewise, need to relayout the text.
this.layoutText();
this.buildGlyphs();
this.renderContext.setDirty();
}
viewPanned(): void {
this.buildGlyphs();
this.renderContext.setDirty();
}
protected compositeIfNecessary(): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
// Set up composite state.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, renderContext.cameraView.width, renderContext.cameraView.height);
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.SCISSOR_TEST);
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE);
gl.enable(gl.BLEND);
// Clear.
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Set the appropriate program.
const programName = this.gammaCorrectionMode === 'off' ? 'blitLinear' : 'blitGamma';
const blitProgram = this.renderContext.shaderPrograms[programName];
// Set up the composite VAO.
const attributes = blitProgram.attributes;
gl.useProgram(blitProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
gl.vertexAttribPointer(attributes.aPosition, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
gl.vertexAttribPointer(attributes.aTexCoord, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(attributes.aPosition);
gl.enableVertexAttribArray(attributes.aTexCoord);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
// Create the transform.
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [
2.0 / this.renderContext.cameraView.width,
2.0 / this.renderContext.cameraView.height,
1.0,
]);
glmatrix.mat4.translate(transform,
transform,
[this.camera.translation[0], this.camera.translation[1], 0.0]);
// Blit.
gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
gl.activeTexture(gl.TEXTURE0);
const destTexture = this.renderContext
.atlas
.ensureTexture(this.renderContext);
gl.bindTexture(gl.TEXTURE_2D, destTexture);
gl.uniform1i(blitProgram.uniforms.uSource, 0);
this.setIdentityTexScaleUniform(blitProgram.uniforms);
this.bindGammaLUT(glmatrix.vec3.clone([1.0, 1.0, 1.0]), 1, blitProgram.uniforms);
const totalGlyphCount = this.layout.textFrame.totalGlyphCount;
gl.drawElements(gl.TRIANGLES, totalGlyphCount * 6, gl.UNSIGNED_INT, 0);
}
private layoutText(): void {
const renderContext = this.renderContext;
const gl = renderContext.gl;
this.layout.layoutRuns();
const textBounds = this.layout.textFrame.bounds;
this.camera.bounds = textBounds;
const totalGlyphCount = this.layout.textFrame.totalGlyphCount;
const glyphPositions = new Float32Array(totalGlyphCount * 8);
const glyphIndices = new Uint32Array(totalGlyphCount * 6);
const hint = this.createHint();
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
let globalGlyphIndex = 0;
for (const run of this.layout.textFrame.runs) {
run.recalculatePixelRects(pixelsPerUnit,
rotationAngle,
hint,
this.emboldenAmount,
SUBPIXEL_GRANULARITY,
textBounds);
for (let glyphIndex = 0;
glyphIndex < run.glyphIDs.length;
glyphIndex++, globalGlyphIndex++) {
const rect = run.pixelRectForGlyphAt(glyphIndex);
glyphPositions.set([
rect[0], rect[3],
rect[2], rect[3],
rect[0], rect[1],
rect[2], rect[1],
], globalGlyphIndex * 8);
for (let glyphIndexIndex = 0;
glyphIndexIndex < QUAD_ELEMENTS.length;
glyphIndexIndex++) {
glyphIndices[glyphIndexIndex + globalGlyphIndex * 6] =
QUAD_ELEMENTS[glyphIndexIndex] + 4 * globalGlyphIndex;
}
}
}
this.glyphPositionsBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, glyphPositions, gl.STATIC_DRAW);
this.glyphElementsBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, glyphIndices, gl.STATIC_DRAW);
}
private buildGlyphs(): void {
const font = this.renderContext.font;
const glyphStore = this.renderContext.glyphStore;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const textFrame = this.layout.textFrame;
const textBounds = textFrame.bounds;
const hint = this.createHint();
// Only build glyphs in view.
const translation = this.camera.translation;
const canvasRect = glmatrix.vec4.clone([
-translation[0],
-translation[1],
-translation[0] + this.renderContext.cameraView.width,
-translation[1] + this.renderContext.cameraView.height,
]);
const atlasGlyphs = [];
for (const run of textFrame.runs) {
for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) {
const pixelRect = run.pixelRectForGlyphAt(glyphIndex);
if (!rectsIntersect(pixelRect, canvasRect))
continue;
const glyphID = run.glyphIDs[glyphIndex];
const glyphStoreIndex = glyphStore.indexOfGlyphWithID(glyphID);
if (glyphStoreIndex == null)
continue;
const subpixel = run.subpixelForGlyphAt(glyphIndex,
pixelsPerUnit,
rotationAngle,
hint,
SUBPIXEL_GRANULARITY,
textBounds);
const glyphKey = new GlyphKey(glyphID, subpixel);
atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey));
}
}
this.buildAtlasGlyphs(atlasGlyphs);
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
this.setGlyphTexCoords();
}
private setGlyphTexCoords(): void {
const gl = this.renderContext.gl;
const textFrame = this.layout.textFrame;
const textBounds = textFrame.bounds;
const font = this.renderContext.font;
const atlasGlyphs = this.renderContext.atlasGlyphs;
const hint = this.createHint();
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
this.glyphBounds = new Float32Array(textFrame.totalGlyphCount * 8);
let globalGlyphIndex = 0;
for (const run of textFrame.runs) {
for (let glyphIndex = 0;
glyphIndex < run.glyphIDs.length;
glyphIndex++, globalGlyphIndex++) {
const textGlyphID = run.glyphIDs[glyphIndex];
const subpixel = run.subpixelForGlyphAt(glyphIndex,
pixelsPerUnit,
rotationAngle,
hint,
SUBPIXEL_GRANULARITY,
textBounds);
const glyphKey = new GlyphKey(textGlyphID, subpixel);
const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphKeys, glyphKey.sortKey);
if (atlasGlyphIndex < 0)
continue;
// Set texture coordinates.
const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
const atlasGlyphMetrics = font.metricsForGlyph(atlasGlyph.glyphKey.id);
if (atlasGlyphMetrics == null)
continue;
const atlasGlyphUnitMetrics = new UnitMetrics(atlasGlyphMetrics,
rotationAngle,
this.emboldenAmount);
const atlasGlyphPixelOrigin =
atlasGlyph.calculateSubpixelOrigin(pixelsPerUnit);
const atlasGlyphRect = calculatePixelRectForGlyph(atlasGlyphUnitMetrics,
atlasGlyphPixelOrigin,
pixelsPerUnit,
hint);
const atlasGlyphBL = atlasGlyphRect.slice(0, 2) as glmatrix.vec2;
const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2;
glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE);
glmatrix.vec2.div(atlasGlyphTR, atlasGlyphTR, ATLAS_SIZE);
this.glyphBounds.set([
atlasGlyphBL[0], atlasGlyphTR[1],
atlasGlyphTR[0], atlasGlyphTR[1],
atlasGlyphBL[0], atlasGlyphBL[1],
atlasGlyphTR[0], atlasGlyphBL[1],
], globalGlyphIndex * 8);
}
}
this.glyphTexCoordsBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.glyphBounds, gl.STATIC_DRAW);
}
private setIdentityTexScaleUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
}
}
/// The separating axis theorem.
function rectsIntersect(a: glmatrix.vec4, b: glmatrix.vec4): boolean {
return a[2] > b[0] && a[3] > b[1] && a[0] < b[2] && a[1] < b[3];
}

View File

@ -1,453 +0,0 @@
// pathfinder/client/src/text.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as base64js from 'base64-js';
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import * as opentype from "opentype.js";
import {Metrics} from 'opentype.js';
import {B_QUAD_SIZE, parseServerTiming, PathfinderMeshPack} from "./meshes";
import {PathfinderPackedMeshes} from "./meshes";
import {assert, lerp, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
export const BUILTIN_FONT_URI: string = "/otf/demo";
const SQRT_2: number = Math.sqrt(2.0);
// Should match macOS 10.13 High Sierra.
//
// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the
// pixel square on macOS and relative to the vertex normal in Pathfinder.
const STEM_DARKENING_FACTORS: glmatrix.vec2 = glmatrix.vec2.clone([
0.0121 * SQRT_2,
0.0121 * 1.25 * SQRT_2,
]);
// Likewise.
const MAX_STEM_DARKENING_AMOUNT: glmatrix.vec2 = glmatrix.vec2.clone([0.3 * SQRT_2, 0.3 * SQRT_2]);
// This value is a subjective cutoff. Above this ppem value, no stem darkening is performed.
export const MAX_STEM_DARKENING_PIXELS_PER_EM: number = 72.0;
const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
export interface ExpandedMeshData {
meshes: PathfinderPackedMeshes;
}
export interface PartitionResult {
meshes: PathfinderMeshPack;
time: number;
}
export interface PixelMetrics {
left: number;
right: number;
ascent: number;
descent: number;
}
opentype.Font.prototype.isSupported = function() {
return (this as any).supported;
};
opentype.Font.prototype.lineHeight = function() {
const os2Table = this.tables.os2;
return os2Table.sTypoAscender - os2Table.sTypoDescender + os2Table.sTypoLineGap;
};
export class PathfinderFont {
readonly opentypeFont: opentype.Font;
readonly data: ArrayBuffer;
readonly builtinFontName: string | null;
private metricsCache: Metrics[];
constructor(data: ArrayBuffer, builtinFontName: string | null) {
this.data = data;
this.builtinFontName = builtinFontName != null ? builtinFontName : null;
this.opentypeFont = opentype.parse(data);
if (!this.opentypeFont.isSupported())
panic("Unsupported font!");
this.metricsCache = [];
}
metricsForGlyph(glyphID: number): Metrics | null {
if (this.metricsCache[glyphID] == null)
this.metricsCache[glyphID] = this.opentypeFont.glyphs.get(glyphID).getMetrics();
return this.metricsCache[glyphID];
}
}
export class TextRun {
readonly glyphIDs: number[];
advances: number[];
readonly origin: number[];
private readonly font: PathfinderFont;
private pixelRects: glmatrix.vec4[];
constructor(text: number[] | string, origin: number[], font: PathfinderFont) {
if (typeof(text) === 'string') {
this.glyphIDs = font.opentypeFont
.stringToGlyphs(text)
.map(glyph => (glyph as any).index);
} else {
this.glyphIDs = text;
}
this.origin = origin;
this.advances = [];
this.font = font;
this.pixelRects = [];
}
layout() {
this.advances = [];
let currentX = 0;
for (const glyphID of this.glyphIDs) {
this.advances.push(currentX);
currentX += this.font.opentypeFont.glyphs.get(glyphID).advanceWidth;
}
}
calculatePixelOriginForGlyphAt(index: number,
pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
textFrameBounds: glmatrix.vec4):
glmatrix.vec2 {
const textFrameCenter = glmatrix.vec2.clone([
0.5 * (textFrameBounds[0] + textFrameBounds[2]),
0.5 * (textFrameBounds[1] + textFrameBounds[3]),
]);
const transform = glmatrix.mat2d.create();
glmatrix.mat2d.fromTranslation(transform, textFrameCenter);
glmatrix.mat2d.rotate(transform, transform, -rotationAngle);
glmatrix.vec2.negate(textFrameCenter, textFrameCenter);
glmatrix.mat2d.translate(transform, transform, textFrameCenter);
const textGlyphOrigin = glmatrix.vec2.create();
glmatrix.vec2.add(textGlyphOrigin, [this.advances[index], 0.0], this.origin);
glmatrix.vec2.transformMat2d(textGlyphOrigin, textGlyphOrigin, transform);
glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit);
return textGlyphOrigin;
}
pixelRectForGlyphAt(index: number): glmatrix.vec4 {
return this.pixelRects[index];
}
subpixelForGlyphAt(index: number,
pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
subpixelGranularity: number,
textFrameBounds: glmatrix.vec4):
number {
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index,
pixelsPerUnit,
rotationAngle,
hint,
textFrameBounds)[0];
return Math.abs(Math.round(textGlyphOrigin * subpixelGranularity) % subpixelGranularity);
}
recalculatePixelRects(pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
emboldenAmount: glmatrix.vec2,
subpixelGranularity: number,
textFrameBounds: glmatrix.vec4):
void {
for (let index = 0; index < this.glyphIDs.length; index++) {
const metrics = unwrapNull(this.font.metricsForGlyph(this.glyphIDs[index]));
const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount);
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index,
pixelsPerUnit,
rotationAngle,
hint,
textFrameBounds);
textGlyphOrigin[0] *= subpixelGranularity;
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
textGlyphOrigin[0] /= subpixelGranularity;
const pixelRect = calculatePixelRectForGlyph(unitMetrics,
textGlyphOrigin,
pixelsPerUnit,
hint);
this.pixelRects[index] = pixelRect;
}
}
get measure(): number {
const lastGlyphID = _.last(this.glyphIDs), lastAdvance = _.last(this.advances);
if (lastGlyphID == null || lastAdvance == null)
return 0.0;
return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth;
}
}
export class TextFrame {
readonly runs: TextRun[];
readonly origin: glmatrix.vec3;
private readonly font: PathfinderFont;
constructor(runs: TextRun[], font: PathfinderFont) {
this.runs = runs;
this.origin = glmatrix.vec3.create();
this.font = font;
}
expandMeshes(meshes: PathfinderMeshPack, glyphIDs: number[]): ExpandedMeshData {
const pathIDs = [];
for (const textRun of this.runs) {
for (const glyphID of textRun.glyphIDs) {
if (glyphID === 0)
continue;
const pathID = _.sortedIndexOf(glyphIDs, glyphID);
pathIDs.push(pathID + 1);
}
}
return {
meshes: new PathfinderPackedMeshes(meshes, pathIDs),
};
}
get bounds(): glmatrix.vec4 {
if (this.runs.length === 0)
return glmatrix.vec4.create();
const upperLeft = glmatrix.vec2.clone(this.runs[0].origin);
const lowerRight = glmatrix.vec2.clone(_.last(this.runs)!.origin);
const lowerLeft = glmatrix.vec2.clone([upperLeft[0], lowerRight[1]]);
const upperRight = glmatrix.vec2.clone([lowerRight[0], upperLeft[1]]);
const lineHeight = this.font.opentypeFont.lineHeight();
lowerLeft[1] -= lineHeight;
upperRight[1] += lineHeight * 2.0;
upperRight[0] = _.defaultTo<number>(_.max(this.runs.map(run => run.measure)), 0.0);
return glmatrix.vec4.clone([lowerLeft[0], lowerLeft[1], upperRight[0], upperRight[1]]);
}
get totalGlyphCount(): number {
return _.sumBy(this.runs, run => run.glyphIDs.length);
}
get allGlyphIDs(): number[] {
const glyphIDs = [];
for (const run of this.runs)
glyphIDs.push(...run.glyphIDs);
return glyphIDs;
}
}
/// Stores one copy of each glyph.
export class GlyphStore {
readonly font: PathfinderFont;
readonly glyphIDs: number[];
constructor(font: PathfinderFont, glyphIDs: number[]) {
this.font = font;
this.glyphIDs = glyphIDs;
}
partition(): Promise<PartitionResult> {
// Build the partitioning request to the server.
let fontFace;
if (this.font.builtinFontName != null)
fontFace = { Builtin: this.font.builtinFontName };
else
fontFace = { Custom: base64js.fromByteArray(new Uint8Array(this.font.data)) };
const request = {
face: fontFace,
fontIndex: 0,
glyphs: this.glyphIDs.map(id => ({ id: id, transform: [1, 0, 0, 1, 0, 0] })),
pointSize: this.font.opentypeFont.unitsPerEm,
};
// Make the request.
let time = 0;
return window.fetch(PARTITION_FONT_ENDPOINT_URI, {
body: JSON.stringify(request),
headers: {'Content-Type': 'application/json'} as any,
method: 'POST',
}).then(response => {
time = parseServerTiming(response.headers);
return response.arrayBuffer();
}).then(buffer => {
return {
meshes: new PathfinderMeshPack(buffer),
time: time,
};
});
}
indexOfGlyphWithID(glyphID: number): number | null {
const index = _.sortedIndexOf(this.glyphIDs, glyphID);
return index >= 0 ? index : null;
}
}
export class SimpleTextLayout {
readonly textFrame: TextFrame;
constructor(font: PathfinderFont, text: string) {
const lineHeight = font.opentypeFont.lineHeight();
const textRuns: TextRun[] = text.split("\n").map((line, lineNumber) => {
return new TextRun(line, [0.0, -lineHeight * lineNumber], font);
});
this.textFrame = new TextFrame(textRuns, font);
}
layoutRuns() {
this.textFrame.runs.forEach(textRun => textRun.layout());
}
}
export class Hint {
readonly xHeight: number;
readonly hintedXHeight: number;
readonly stemHeight: number;
readonly hintedStemHeight: number;
private useHinting: boolean;
constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) {
useHinting = false;
this.useHinting = useHinting;
const os2Table = font.opentypeFont.tables.os2;
this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0;
this.stemHeight = os2Table.sCapHeight != null ? os2Table.sCapHeight : 0;
if (!useHinting) {
this.hintedXHeight = this.xHeight;
this.hintedStemHeight = this.stemHeight;
} else {
this.hintedXHeight = Math.round(Math.round(this.xHeight * pixelsPerUnit) /
pixelsPerUnit);
this.hintedStemHeight = Math.round(Math.round(this.stemHeight * pixelsPerUnit) /
pixelsPerUnit);
}
}
/// NB: This must match `hintPosition()` in `common.inc.glsl`.
hintPosition(position: glmatrix.vec2): glmatrix.vec2 {
if (!this.useHinting)
return position;
if (position[1] >= this.stemHeight) {
const y = position[1] - this.stemHeight + this.hintedStemHeight;
return glmatrix.vec2.clone([position[0], y]);
}
if (position[1] >= this.xHeight) {
const y = lerp(this.hintedXHeight, this.hintedStemHeight,
(position[1] - this.xHeight) / (this.stemHeight - this.xHeight));
return glmatrix.vec2.clone([position[0], y]);
}
if (position[1] >= 0.0) {
const y = lerp(0.0, this.hintedXHeight, position[1] / this.xHeight);
return glmatrix.vec2.clone([position[0], y]);
}
return position;
}
}
export class UnitMetrics {
left: number;
right: number;
ascent: number;
descent: number;
constructor(metrics: Metrics, rotationAngle: number, emboldenAmount: glmatrix.vec2) {
const left = metrics.xMin;
const bottom = metrics.yMin;
const right = metrics.xMax + emboldenAmount[0] * 2;
const top = metrics.yMax + emboldenAmount[1] * 2;
const transform = glmatrix.mat2.create();
glmatrix.mat2.fromRotation(transform, -rotationAngle);
const lowerLeft = glmatrix.vec2.clone([Infinity, Infinity]);
const upperRight = glmatrix.vec2.clone([-Infinity, -Infinity]);
const points = [[left, bottom], [left, top], [right, top], [right, bottom]];
const transformedPoint = glmatrix.vec2.create();
for (const point of points) {
glmatrix.vec2.transformMat2(transformedPoint, point, transform);
glmatrix.vec2.min(lowerLeft, lowerLeft, transformedPoint);
glmatrix.vec2.max(upperRight, upperRight, transformedPoint);
}
this.left = lowerLeft[0];
this.right = upperRight[0];
this.ascent = upperRight[1];
this.descent = lowerLeft[1];
}
}
export function calculatePixelXMin(metrics: UnitMetrics, pixelsPerUnit: number): number {
return Math.floor(metrics.left * pixelsPerUnit);
}
export function calculatePixelDescent(metrics: UnitMetrics, pixelsPerUnit: number): number {
return Math.floor(metrics.descent * pixelsPerUnit);
}
function calculateSubpixelMetricsForGlyph(metrics: UnitMetrics, pixelsPerUnit: number, hint: Hint):
PixelMetrics {
const ascent = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.ascent))[1];
return {
ascent: ascent * pixelsPerUnit,
descent: metrics.descent * pixelsPerUnit,
left: metrics.left * pixelsPerUnit,
right: metrics.right * pixelsPerUnit,
};
}
export function calculatePixelRectForGlyph(metrics: UnitMetrics,
subpixelOrigin: glmatrix.vec2,
pixelsPerUnit: number,
hint: Hint):
glmatrix.vec4 {
const pixelMetrics = calculateSubpixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
return glmatrix.vec4.clone([Math.floor(subpixelOrigin[0] + pixelMetrics.left),
Math.floor(subpixelOrigin[1] + pixelMetrics.descent),
Math.ceil(subpixelOrigin[0] + pixelMetrics.right),
Math.ceil(subpixelOrigin[1] + pixelMetrics.ascent)]);
}
export function computeStemDarkeningAmount(pixelsPerEm: number, pixelsPerUnit: number):
glmatrix.vec2 {
const amount = glmatrix.vec2.create();
if (pixelsPerEm > MAX_STEM_DARKENING_PIXELS_PER_EM)
return amount;
glmatrix.vec2.scale(amount, STEM_DARKENING_FACTORS, pixelsPerEm);
glmatrix.vec2.min(amount, amount, MAX_STEM_DARKENING_AMOUNT);
glmatrix.vec2.scale(amount, amount, 1.0 / pixelsPerUnit);
return amount;
}

View File

@ -1,85 +0,0 @@
// pathfinder/client/src/utils.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
export const FLOAT32_SIZE: number = 4;
export const UINT16_SIZE: number = 2;
export const UINT8_SIZE: number = 1;
export const UINT32_MAX: number = 0xffffffff;
export const UINT32_SIZE: number = 4;
export const EPSILON: number = 0.001;
export function panic(message: string): never {
throw new PathfinderError(message);
}
export function assert(value: boolean, message: string) {
if (!value)
panic(message);
}
export function expectNotNull<T>(value: T | null, message: string): T {
if (value === null)
throw new PathfinderError(message);
return value;
}
function expectNotUndef<T>(value: T | undefined, message: string): T {
if (value === undefined)
throw new PathfinderError(message);
return value;
}
export function unwrapNull<T>(value: T | null): T {
return expectNotNull(value, "Unexpected null!");
}
export function unwrapUndef<T>(value: T | undefined): T {
return expectNotUndef(value, "Unexpected `undefined`!");
}
export function scaleRect(rect: glmatrix.vec4, scale: number): glmatrix.vec4 {
const upperLeft = glmatrix.vec2.clone([rect[0], rect[1]]);
const lowerRight = glmatrix.vec2.clone([rect[2], rect[3]]);
glmatrix.vec2.scale(upperLeft, upperLeft, scale);
glmatrix.vec2.scale(lowerRight, lowerRight, scale);
return glmatrix.vec4.clone([upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]]);
}
export function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
export class PathfinderError extends Error {
constructor(message?: string | undefined) {
super(message);
}
}
export class Range {
start: number;
end: number;
get isEmpty(): boolean {
return this.start >= this.end;
}
get length(): number {
return this.end - this.start;
}
constructor(start: number, end: number) {
this.start = start;
this.end = end;
}
}

View File

@ -1,427 +0,0 @@
// pathfinder/client/src/view.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {StemDarkeningMode, SubpixelAAType} from "./aa-strategy";
import {AAOptions} from './app-controller';
import PathfinderBufferTexture from './buffer-texture';
import {Camera} from "./camera";
import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
import {Renderer} from './renderer';
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
const QUAD_POSITIONS: Float32Array = new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0,
]);
const QUAD_TEX_COORDS: Float32Array = new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0,
]);
export const TIMINGS: {[name: string]: string} = {
compositing: "Compositing",
rendering: "Rendering",
};
export interface Timings {
compositing: number;
rendering: number;
}
export type ColorAlphaFormat = 'RGBA8' | 'RGB5_A1';
declare class WebGLQuery {}
export abstract class PathfinderView {
canvas: HTMLCanvasElement;
suppressAutomaticRedraw: boolean;
vrDisplayWidth: number | null;
vrDisplayHeight: number | null;
protected abstract get camera(): Camera;
private dirty: boolean;
private pulseHandle: number;
constructor() {
this.dirty = false;
this.pulseHandle = 0;
this.suppressAutomaticRedraw = false;
this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement;
window.addEventListener('resize', () => this.resizeToFit(false), false);
this.vrDisplayHeight = null;
this.vrDisplayWidth = null;
}
setDirty(): void {
if (this.dirty || this.suppressAutomaticRedraw)
return;
this.dirty = true;
window.requestAnimationFrame(() => this.redraw());
}
zoomIn(): void {
this.camera.zoomIn();
}
zoomOut(): void {
this.camera.zoomOut();
}
zoomPulse(): void {
if (this.pulseHandle) {
window.cancelAnimationFrame(this.pulseHandle);
this.pulseHandle = 0;
return;
}
let c = 0;
let d = 0.005;
const self = this;
function tick() {
self.camera.zoom(1 + d);
if (c++ % 200 === 0) {
d *= -1;
}
self.pulseHandle = window.requestAnimationFrame(tick);
}
this.pulseHandle = window.requestAnimationFrame(tick);
}
rotate(newAngle: number): void {
this.camera.rotate(newAngle);
}
redraw(): void {
this.dirty = false;
}
protected resized(): void {
this.setDirty();
}
protected inVR(): boolean {
return false;
}
protected resizeToFit(initialSize: boolean): void {
if (!this.canvas.classList.contains('pf-no-autoresize')) {
if (this.inVR()) {
const width = unwrapNull(this.vrDisplayWidth);
const height = unwrapNull(this.vrDisplayHeight);
// these are already in device pixel units, no need to multiply
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
this.canvas.width = width;
this.canvas.height = height;
} else {
const width = window.innerWidth;
const canvasTop = this.canvas.getBoundingClientRect().top;
const height = window.scrollY + window.innerHeight - canvasTop;
const devicePixelRatio = window.devicePixelRatio;
const canvasSize = new Float32Array([width, height]) as glmatrix.vec2;
glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio);
glmatrix.vec2.round(canvasSize, canvasSize);
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
this.canvas.width = canvasSize[0];
this.canvas.height = canvasSize[1];
}
}
this.resized();
}
}
export abstract class DemoView extends PathfinderView implements RenderContext {
readonly renderer!: Renderer;
gl!: WebGLRenderingContext;
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
areaLUT: HTMLImageElement;
gammaLUT: HTMLImageElement;
instancedArraysExt!: ANGLE_instanced_arrays;
textureHalfFloatExt!: OESTextureHalfFloat;
timerQueryExt!: EXTDisjointTimerQuery | null;
vertexArrayObjectExt!: OESVertexArrayObject;
quadPositionsBuffer!: WebGLBuffer;
quadTexCoordsBuffer!: WebGLBuffer;
quadElementsBuffer!: WebGLBuffer;
atlasRenderingTimerQuery!: WebGLQuery | null;
compositingTimerQuery!: WebGLQuery | null;
meshes: PathfinderPackedMeshBuffers[];
meshData: PathfinderPackedMeshes[];
get colorAlphaFormat(): ColorAlphaFormat {
// On macOS, RGBA framebuffers seem to cause driver stalls when switching between rendering
// and texturing. Work around this by using RGB5A1 instead.
return navigator.platform === 'MacIntel' ? 'RGB5_A1' : 'RGBA8';
}
get renderContext(): RenderContext {
return this;
}
protected colorBufferHalfFloatExt: any;
private wantsScreenshot: boolean;
/// NB: All subclasses are responsible for creating a renderer in their constructors.
constructor(areaLUT: HTMLImageElement,
gammaLUT: HTMLImageElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super();
this.meshes = [];
this.meshData = [];
this.initContext();
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
this.shaderPrograms = this.linkShaders(shaderSource);
this.areaLUT = areaLUT;
this.gammaLUT = gammaLUT;
this.wantsScreenshot = false;
}
attachMeshes(meshes: PathfinderPackedMeshes[]): void {
this.renderer.attachMeshes(meshes);
this.setDirty();
}
initQuadVAO(attributes: any): void {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer);
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer);
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
this.gl.enableVertexAttribArray(attributes.aPosition);
this.gl.enableVertexAttribArray(attributes.aTexCoord);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
}
queueScreenshot(): void {
this.wantsScreenshot = true;
this.setDirty();
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number,
aaOptions: AAOptions):
void {
this.renderer.setAntialiasingOptions(aaType, aaLevel, aaOptions);
}
snapshot(rect: glmatrix.vec4): Uint8Array {
const gl = this.renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const canvasHeight = this.canvas.height;
const width = rect[2] - rect[0], height = rect[3] - rect[1];
const originX = Math.max(rect[0], 0);
const originY = Math.max(canvasHeight - height, 0);
const flippedBuffer = new Uint8Array(width * height * 4);
gl.readPixels(originX, originY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, flippedBuffer);
const buffer = new Uint8Array(width * height * 4);
for (let y = 0; y < height; y++) {
const destRowStart = y * width * 4;
const srcRowStart = (height - y - 1) * width * 4;
buffer.set(flippedBuffer.slice(srcRowStart, srcRowStart + width * 4),
destRowStart);
}
return buffer;
}
enterVR(): void {}
redrawVR(): void {
this.renderer.redraw();
}
redraw(): void {
super.redraw();
if (!this.renderer.meshesAttached)
return;
if (!this.renderer.inVR) {
this.renderer.redraw();
} else {
this.redrawVR();
}
// Invoke the post-render hook.
this.renderingFinished();
// Take a screenshot if desired.
if (this.wantsScreenshot) {
this.wantsScreenshot = false;
this.takeScreenshot();
}
}
protected resized(): void {
super.resized();
this.renderer.canvasResized();
}
protected inVR(): boolean {
return this.renderer.inVR;
}
protected initContext(): void {
// Initialize the OpenGL context.
this.gl = expectNotNull(this.canvas.getContext('webgl', { antialias: false, depth: true }),
"Failed to initialize WebGL! Check that your browser supports it.");
this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float');
this.instancedArraysExt = unwrapNull(this.gl.getExtension('ANGLE_instanced_arrays'));
this.textureHalfFloatExt = unwrapNull(this.gl.getExtension('OES_texture_half_float'));
this.timerQueryExt = this.gl.getExtension('EXT_disjoint_timer_query');
this.vertexArrayObjectExt = unwrapNull(this.gl.getExtension('OES_vertex_array_object'));
this.gl.getExtension('EXT_frag_depth');
this.gl.getExtension('OES_element_index_uint');
this.gl.getExtension('OES_standard_derivatives');
this.gl.getExtension('OES_texture_float');
this.gl.getExtension('WEBGL_depth_texture');
// Upload quad buffers.
this.quadPositionsBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, QUAD_POSITIONS, this.gl.STATIC_DRAW);
this.quadTexCoordsBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, QUAD_TEX_COORDS, this.gl.STATIC_DRAW);
this.quadElementsBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, QUAD_ELEMENTS, this.gl.STATIC_DRAW);
// Set up our timer queries for profiling.
if (this.timerQueryExt != null) {
this.atlasRenderingTimerQuery = this.timerQueryExt.createQueryEXT();
this.compositingTimerQuery = this.timerQueryExt.createQueryEXT();
} else {
this.atlasRenderingTimerQuery = null;
this.compositingTimerQuery = null;
}
}
protected renderingFinished(): void {}
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
ShaderMap<UnlinkedShaderProgram> {
const shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
for (const shaderKey of SHADER_NAMES) {
for (const typeName of ['vertex', 'fragment'] as Array<'vertex' | 'fragment'>) {
const type = {
fragment: this.gl.FRAGMENT_SHADER,
vertex: this.gl.VERTEX_SHADER,
}[typeName];
const source = shaderSources[shaderKey][typeName];
const shader = this.gl.createShader(type);
if (shader == null)
throw new PathfinderError("Failed to create shader!");
this.gl.shaderSource(shader, commonSource + "\n#line 1\n" + source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
const infoLog = this.gl.getShaderInfoLog(shader);
throw new PathfinderError(`Failed to compile ${typeName} shader ` +
`"${shaderKey}":\n${infoLog}`);
}
if (shaders[shaderKey] == null)
shaders[shaderKey] = {};
shaders[shaderKey]![typeName] = shader;
}
}
return shaders as ShaderMap<UnlinkedShaderProgram>;
}
private linkShaders(shaders: ShaderMap<UnlinkedShaderProgram>):
ShaderMap<PathfinderShaderProgram> {
const shaderProgramMap: Partial<ShaderMap<PathfinderShaderProgram>> = {};
for (const shaderName of Object.keys(shaders) as Array<keyof ShaderMap<string>>) {
shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl,
shaderName,
shaders[shaderName]);
}
return shaderProgramMap as ShaderMap<PathfinderShaderProgram>;
}
private takeScreenshot(): void {
const width = this.canvas.width, height = this.canvas.height;
const scratchCanvas = document.createElement('canvas');
scratchCanvas.width = width;
scratchCanvas.height = height;
const scratch2DContext = unwrapNull(scratchCanvas.getContext('2d'));
scratch2DContext.drawImage(this.canvas, 0, 0, width, height);
const scratchLink = document.createElement('a');
scratchLink.download = 'pathfinder-screenshot.png';
scratchLink.href = scratchCanvas.toDataURL();
scratchLink.style.position = 'absolute';
document.body.appendChild(scratchLink);
scratchLink.click();
document.body.removeChild(scratchLink);
}
}
export interface RenderContext {
/// The OpenGL context.
readonly gl: WebGLRenderingContext;
readonly instancedArraysExt: ANGLEInstancedArrays;
readonly textureHalfFloatExt: OESTextureHalfFloat;
readonly timerQueryExt: EXTDisjointTimerQuery | null;
readonly vertexArrayObjectExt: OESVertexArrayObject;
readonly colorAlphaFormat: ColorAlphaFormat;
readonly shaderPrograms: ShaderMap<PathfinderShaderProgram>;
readonly areaLUT: HTMLImageElement;
readonly gammaLUT: HTMLImageElement;
readonly quadPositionsBuffer: WebGLBuffer;
readonly quadElementsBuffer: WebGLBuffer;
readonly atlasRenderingTimerQuery: WebGLQuery | null;
readonly compositingTimerQuery: WebGLQuery | null;
initQuadVAO(attributes: any): void;
setDirty(): void;
}

View File

@ -1,886 +0,0 @@
// pathfinder/demo/client/src/xcaa-strategy.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {AntialiasingStrategy, DirectRenderingMode, SubpixelAAType} from './aa-strategy';
import PathfinderBufferTexture from './buffer-texture';
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
import {WebGLVertexArrayObject} from './gl-utils';
import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} from './meshes';
import {Renderer} from './renderer';
import {PathfinderShaderProgram, ShaderMap} from './shader-loader';
import {computeStemDarkeningAmount} from './text';
import {assert, FLOAT32_SIZE, lerp, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull} from './utils';
import {unwrapUndef} from './utils';
import {RenderContext} from './view';
interface FastEdgeVAOs {
upper: WebGLVertexArrayObject;
lower: WebGLVertexArrayObject;
}
type Direction = 'upper' | 'lower';
const DIRECTIONS: Direction[] = ['upper', 'lower'];
const PATCH_VERTICES: Float32Array = new Float32Array([
0.0, 0.0,
0.5, 0.0,
1.0, 0.0,
0.0, 1.0,
0.5, 1.0,
1.0, 1.0,
]);
const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]);
export type TransformType = 'affine' | '3d';
export abstract class XCAAStrategy extends AntialiasingStrategy {
abstract readonly directRenderingMode: DirectRenderingMode;
protected patchVertexBuffer: WebGLBuffer | null = null;
protected patchIndexBuffer: WebGLBuffer | null = null;
get passCount(): number {
return 1;
}
protected abstract get transformType(): TransformType;
protected abstract get patchIndices(): Uint8Array;
protected pathBoundsBufferTextures: PathfinderBufferTexture[];
protected supersampledFramebufferSize: glmatrix.vec2;
protected destFramebufferSize: glmatrix.vec2;
protected resolveVAO: WebGLVertexArrayObject | null;
protected aaAlphaTexture: WebGLTexture | null = null;
protected aaDepthTexture: WebGLTexture | null = null;
protected aaFramebuffer: WebGLFramebuffer | null = null;
protected abstract get mightUseAAFramebuffer(): boolean;
constructor(level: number, subpixelAA: SubpixelAAType) {
super(subpixelAA);
this.supersampledFramebufferSize = glmatrix.vec2.create();
this.destFramebufferSize = glmatrix.vec2.create();
this.pathBoundsBufferTextures = [];
}
init(renderer: Renderer): void {
super.init(renderer);
}
attachMeshes(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
this.createResolveVAO(renderer);
this.pathBoundsBufferTextures = [];
this.patchVertexBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, PATCH_VERTICES.buffer as ArrayBuffer, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
this.patchIndexBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
this.patchIndices.buffer as ArrayBuffer,
gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
setFramebufferSize(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
this.destFramebufferSize = glmatrix.vec2.clone(renderer.destAllocatedSize);
glmatrix.vec2.mul(this.supersampledFramebufferSize,
this.destFramebufferSize,
this.supersampleScale);
this.initAAAlphaFramebuffer(renderer);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
prepareForRendering(renderer: Renderer): void {}
prepareForDirectRendering(renderer: Renderer): void {}
finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
this.initResolveFramebufferForObject(renderer, objectIndex);
if (!this.usesAAFramebuffer(renderer))
return;
const usedSize = this.supersampledUsedSize(renderer);
gl.scissor(0, 0, usedSize[0], usedSize[1]);
gl.enable(gl.SCISSOR_TEST);
// Clear out the color and depth textures.
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clearDepth(0.0);
gl.depthMask(true);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
prepareToRenderObject(renderer: Renderer, objectIndex: number): void {}
finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {
// TODO(pcwalton)
}
antialiasObject(renderer: Renderer, objectIndex: number): void {
// Perform early preparations.
this.createPathBoundsBufferTextureForObjectIfNecessary(renderer, objectIndex);
// Set up antialiasing.
this.prepareAA(renderer);
// Clear.
this.clearForAA(renderer);
}
resolveAAForObject(renderer: Renderer, objectIndex: number): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
if (!this.usesAAFramebuffer(renderer))
return;
const resolveProgram = this.getResolveProgram(renderer);
if (resolveProgram == null)
return;
// Set state for XCAA resolve.
const usedSize = renderer.destUsedSize;
gl.scissor(0, 0, usedSize[0], usedSize[1]);
gl.enable(gl.SCISSOR_TEST);
this.setDepthAndBlendModeForResolve(renderContext);
// Clear out the resolve buffer, if necessary.
this.clearForResolve(renderer);
// Resolve.
gl.useProgram(resolveProgram.program);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO);
gl.uniform2i(resolveProgram.uniforms.uFramebufferSize,
this.destFramebufferSize[0],
this.destFramebufferSize[1]);
gl.activeTexture(renderContext.gl.TEXTURE0);
gl.bindTexture(renderContext.gl.TEXTURE_2D, this.aaAlphaTexture);
gl.uniform1i(resolveProgram.uniforms.uAAAlpha, 0);
gl.uniform2i(resolveProgram.uniforms.uAAAlphaDimensions,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1]);
if (renderer.bgColor != null)
gl.uniform4fv(resolveProgram.uniforms.uBGColor, renderer.bgColor);
if (renderer.fgColor != null)
gl.uniform4fv(resolveProgram.uniforms.uFGColor, renderer.fgColor);
renderer.setTransformSTAndTexScaleUniformsForDest(resolveProgram.uniforms);
this.setSubpixelAAKernelUniform(renderer, resolveProgram.uniforms);
this.setAdditionalStateForResolveIfNecessary(renderer, resolveProgram, 1);
gl.drawElements(renderContext.gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
resolve(pass: number, renderer: Renderer): void {}
get transform(): glmatrix.mat4 {
return glmatrix.mat4.create();
}
protected abstract usesAAFramebuffer(renderer: Renderer): boolean;
protected supersampledUsedSize(renderer: Renderer): glmatrix.vec2 {
const usedSize = glmatrix.vec2.create();
glmatrix.vec2.mul(usedSize, renderer.destUsedSize, this.supersampleScale);
return usedSize;
}
protected prepareAA(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
// Set state for antialiasing.
const usedSize = this.supersampledUsedSize(renderer);
if (this.usesAAFramebuffer(renderer))
gl.bindFramebuffer(gl.FRAMEBUFFER, this.aaFramebuffer);
gl.viewport(0,
0,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1]);
gl.scissor(0, 0, usedSize[0], usedSize[1]);
gl.enable(gl.SCISSOR_TEST);
}
protected setAAState(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const usedSize = this.supersampledUsedSize(renderer);
if (this.usesAAFramebuffer(renderer))
gl.bindFramebuffer(gl.FRAMEBUFFER, this.aaFramebuffer);
gl.viewport(0,
0,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1]);
gl.scissor(0, 0, usedSize[0], usedSize[1]);
gl.enable(gl.SCISSOR_TEST);
this.setAADepthState(renderer);
}
protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
switch (this.transformType) {
case 'affine':
renderer.setTransformAffineUniforms(uniforms, 0);
break;
case '3d':
renderer.setTransformUniform(uniforms, 0, 0);
break;
}
gl.uniform2i(uniforms.uFramebufferSize,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1]);
renderer.pathTransformBufferTextures[0].ext.bind(gl, uniforms, 0);
renderer.pathTransformBufferTextures[0].st.bind(gl, uniforms, 1);
this.pathBoundsBufferTextures[objectIndex].bind(gl, uniforms, 2);
renderer.setHintsUniform(uniforms);
renderer.bindAreaLUT(4, uniforms);
}
protected setDepthAndBlendModeForResolve(renderContext: RenderContext): void {
const gl = renderContext.gl;
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.BLEND);
}
protected setAdditionalStateForResolveIfNecessary(renderer: Renderer,
resolveProgram: PathfinderShaderProgram,
firstFreeTextureUnit: number):
void {}
protected abstract clearForAA(renderer: Renderer): void;
protected abstract getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null;
protected abstract setAADepthState(renderer: Renderer): void;
protected abstract clearForResolve(renderer: Renderer): void;
private initResolveFramebufferForObject(renderer: Renderer, objectIndex: number): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
renderer.setDrawViewport();
gl.disable(gl.SCISSOR_TEST);
}
private initAAAlphaFramebuffer(renderer: Renderer): void {
if (!this.mightUseAAFramebuffer) {
this.aaAlphaTexture = null;
this.aaDepthTexture = null;
this.aaFramebuffer = null;
return;
}
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
this.aaAlphaTexture = unwrapNull(gl.createTexture());
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.aaAlphaTexture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGB,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1],
0,
gl.RGB,
renderContext.textureHalfFloatExt.HALF_FLOAT_OES,
null);
setTextureParameters(gl, gl.NEAREST);
this.aaDepthTexture = createFramebufferDepthTexture(gl, this.supersampledFramebufferSize);
this.aaFramebuffer = createFramebuffer(gl, this.aaAlphaTexture, this.aaDepthTexture);
}
private createPathBoundsBufferTextureForObjectIfNecessary(renderer: Renderer,
objectIndex: number):
void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const pathBounds = renderer.pathBoundingRects(objectIndex);
if (this.pathBoundsBufferTextures[objectIndex] == null) {
this.pathBoundsBufferTextures[objectIndex] =
new PathfinderBufferTexture(gl, 'uPathBounds');
}
this.pathBoundsBufferTextures[objectIndex].upload(gl, pathBounds);
}
private createResolveVAO(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const resolveProgram = this.getResolveProgram(renderer);
if (resolveProgram == null)
return;
this.resolveVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES();
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO);
gl.useProgram(resolveProgram.program);
renderContext.initQuadVAO(resolveProgram.attributes);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
protected get directDepthTexture(): WebGLTexture | null {
return null;
}
protected get supersampleScale(): glmatrix.vec2 {
return glmatrix.vec2.fromValues(this.subpixelAA !== 'none' ? 3.0 : 1.0, 1.0);
}
}
export class MCAAStrategy extends XCAAStrategy {
protected vao: WebGLVertexArrayObject | null;
protected get patchIndices(): Uint8Array {
return MCAA_PATCH_INDICES;
}
protected get transformType(): TransformType {
return 'affine';
}
protected get mightUseAAFramebuffer(): boolean {
return true;
}
attachMeshes(renderer: Renderer): void {
super.attachMeshes(renderer);
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES();
}
antialiasObject(renderer: Renderer, objectIndex: number): void {
super.antialiasObject(renderer, objectIndex);
const shaderProgram = this.edgeProgram(renderer);
this.antialiasEdgesOfObjectWithProgram(renderer, objectIndex, shaderProgram);
}
protected usesAAFramebuffer(renderer: Renderer): boolean {
return !renderer.isMulticolor;
}
protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null {
const renderContext = renderer.renderContext;
if (renderer.isMulticolor)
return null;
if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA)
return renderContext.shaderPrograms.xcaaMonoSubpixelResolve;
return renderContext.shaderPrograms.xcaaMonoResolve;
}
protected clearForAA(renderer: Renderer): void {
if (!this.usesAAFramebuffer(renderer))
return;
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
protected setAADepthState(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
if (this.directRenderingMode !== 'conservative') {
gl.disable(gl.DEPTH_TEST);
return;
}
gl.depthFunc(gl.GREATER);
gl.depthMask(false);
gl.enable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
}
protected clearForResolve(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
if (!renderer.isMulticolor) {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
}
protected setBlendModeForAA(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
if (renderer.isMulticolor)
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
else
gl.blendFunc(gl.ONE, gl.ONE);
gl.blendEquation(gl.FUNC_ADD);
gl.enable(gl.BLEND);
}
protected prepareAA(renderer: Renderer): void {
super.prepareAA(renderer);
this.setBlendModeForAA(renderer);
}
protected initVAOForObject(renderer: Renderer, objectIndex: number): void {
if (renderer.meshBuffers == null || renderer.meshes == null)
return;
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const pathRange = renderer.pathRangeForObject(objectIndex);
const meshIndex = renderer.meshIndexForObject(objectIndex);
const shaderProgram = this.edgeProgram(renderer);
const attributes = shaderProgram.attributes;
// FIXME(pcwalton): Refactor.
const vao = this.vao;
renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
const bBoxRanges = renderer.meshes[meshIndex].bBoxPathRanges;
const offset = calculateStartFromIndexRanges(pathRange, bBoxRanges);
gl.useProgram(shaderProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.renderContext.quadPositionsBuffer);
gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, FLOAT32_SIZE * 2, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshBuffers[meshIndex].bBoxes);
gl.vertexAttribPointer(attributes.aRect,
4,
gl.FLOAT,
false,
FLOAT32_SIZE * 20,
FLOAT32_SIZE * 0 + offset * FLOAT32_SIZE * 20);
gl.vertexAttribPointer(attributes.aUV,
4,
gl.FLOAT,
false,
FLOAT32_SIZE * 20,
FLOAT32_SIZE * 4 + offset * FLOAT32_SIZE * 20);
gl.vertexAttribPointer(attributes.aDUVDX,
4,
gl.FLOAT,
false,
FLOAT32_SIZE * 20,
FLOAT32_SIZE * 8 + offset * FLOAT32_SIZE * 20);
gl.vertexAttribPointer(attributes.aDUVDY,
4,
gl.FLOAT,
false,
FLOAT32_SIZE * 20,
FLOAT32_SIZE * 12 + offset * FLOAT32_SIZE * 20);
gl.vertexAttribPointer(attributes.aSignMode,
4,
gl.FLOAT,
false,
FLOAT32_SIZE * 20,
FLOAT32_SIZE * 16 + offset * FLOAT32_SIZE * 20);
gl.enableVertexAttribArray(attributes.aTessCoord);
gl.enableVertexAttribArray(attributes.aRect);
gl.enableVertexAttribArray(attributes.aUV);
gl.enableVertexAttribArray(attributes.aDUVDX);
gl.enableVertexAttribArray(attributes.aDUVDY);
gl.enableVertexAttribArray(attributes.aSignMode);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRect, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aUV, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDX, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDY, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aSignMode, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshBuffers[meshIndex].bBoxPathIDs);
gl.vertexAttribPointer(attributes.aPathID,
1,
gl.UNSIGNED_SHORT,
false,
UINT16_SIZE,
offset * UINT16_SIZE);
gl.enableVertexAttribArray(attributes.aPathID);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderer.renderContext.quadElementsBuffer);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
protected edgeProgram(renderer: Renderer): PathfinderShaderProgram {
return renderer.renderContext.shaderPrograms.mcaa;
}
protected antialiasEdgesOfObjectWithProgram(renderer: Renderer,
objectIndex: number,
shaderProgram: PathfinderShaderProgram):
void {
if (renderer.meshBuffers == null || renderer.meshes == null)
return;
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const pathRange = renderer.pathRangeForObject(objectIndex);
const meshIndex = renderer.meshIndexForObject(objectIndex);
this.initVAOForObject(renderer, objectIndex);
gl.useProgram(shaderProgram.program);
const uniforms = shaderProgram.uniforms;
this.setAAUniforms(renderer, uniforms, objectIndex);
// FIXME(pcwalton): Refactor.
const vao = this.vao;
renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
this.setBlendModeForAA(renderer);
this.setAADepthState(renderer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
const bBoxRanges = renderer.meshes[meshIndex].bBoxPathRanges;
const count = calculateCountFromIndexRanges(pathRange, bBoxRanges);
renderContext.instancedArraysExt
.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
}
get directRenderingMode(): DirectRenderingMode {
// FIXME(pcwalton): Only in multicolor mode?
return 'conservative';
}
protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void {
super.setAAUniforms(renderer, uniforms, objectIndex);
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
renderer.setPathColorsUniform(0, uniforms, 3);
gl.uniform1i(uniforms.uMulticolor, renderer.isMulticolor ? 1 : 0);
}
}
export class StencilAAAStrategy extends XCAAStrategy {
directRenderingMode: DirectRenderingMode = 'none';
protected transformType: TransformType = 'affine';
protected patchIndices: Uint8Array = MCAA_PATCH_INDICES;
protected mightUseAAFramebuffer: boolean = true;
private vao: WebGLVertexArrayObject;
attachMeshes(renderer: Renderer): void {
super.attachMeshes(renderer);
this.createVAO(renderer);
}
antialiasObject(renderer: Renderer, objectIndex: number): void {
super.antialiasObject(renderer, objectIndex);
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
if (renderer.meshes == null)
return;
// Antialias.
const shaderPrograms = renderer.renderContext.shaderPrograms;
this.setAAState(renderer);
this.setBlendModeForAA(renderer);
const program = renderContext.shaderPrograms.stencilAAA;
gl.useProgram(program.program);
const uniforms = program.uniforms;
this.setAAUniforms(renderer, uniforms, objectIndex);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao);
// FIXME(pcwalton): Only render the appropriate instances.
const count = renderer.meshes[0].count('stencilSegments');
for (let side = 0; side < 2; side++) {
gl.uniform1i(uniforms.uSide, side);
renderContext.instancedArraysExt
.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
}
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
protected usesAAFramebuffer(renderer: Renderer): boolean {
return true;
}
protected clearForAA(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clearDepth(0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null {
const renderContext = renderer.renderContext;
if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA)
return renderContext.shaderPrograms.xcaaMonoSubpixelResolve;
return renderContext.shaderPrograms.xcaaMonoResolve;
}
protected setAADepthState(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
}
protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number):
void {
super.setAAUniforms(renderer, uniforms, objectIndex);
renderer.setEmboldenAmountUniform(objectIndex, uniforms);
}
protected clearForResolve(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
private createVAO(renderer: Renderer): void {
if (renderer.meshBuffers == null || renderer.meshes == null)
return;
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
const program = renderContext.shaderPrograms.stencilAAA;
const attributes = program.attributes;
this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES();
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao);
const vertexPositionsBuffer = renderer.meshBuffers[0].stencilSegments;
const vertexNormalsBuffer = renderer.meshBuffers[0].stencilNormals;
const pathIDsBuffer = renderer.meshBuffers[0].stencilSegmentPathIDs;
gl.useProgram(program.program);
gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer);
gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionsBuffer);
gl.vertexAttribPointer(attributes.aFromPosition, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0);
gl.vertexAttribPointer(attributes.aCtrlPosition,
2,
gl.FLOAT,
false,
FLOAT32_SIZE * 6,
FLOAT32_SIZE * 2);
gl.vertexAttribPointer(attributes.aToPosition,
2,
gl.FLOAT,
false,
FLOAT32_SIZE * 6,
FLOAT32_SIZE * 4);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalsBuffer);
gl.vertexAttribPointer(attributes.aFromNormal, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0);
gl.vertexAttribPointer(attributes.aCtrlNormal,
2,
gl.FLOAT,
false,
FLOAT32_SIZE * 6,
FLOAT32_SIZE * 2);
gl.vertexAttribPointer(attributes.aToNormal,
2,
gl.FLOAT,
false,
FLOAT32_SIZE * 6,
FLOAT32_SIZE * 4);
gl.bindBuffer(gl.ARRAY_BUFFER, pathIDsBuffer);
gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0);
gl.enableVertexAttribArray(attributes.aTessCoord);
gl.enableVertexAttribArray(attributes.aFromPosition);
gl.enableVertexAttribArray(attributes.aCtrlPosition);
gl.enableVertexAttribArray(attributes.aToPosition);
gl.enableVertexAttribArray(attributes.aFromNormal);
gl.enableVertexAttribArray(attributes.aCtrlNormal);
gl.enableVertexAttribArray(attributes.aToNormal);
gl.enableVertexAttribArray(attributes.aPathID);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromPosition, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlPosition, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToPosition, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromNormal, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlNormal, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToNormal, 1);
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
private setBlendModeForAA(renderer: Renderer): void {
const renderContext = renderer.renderContext;
const gl = renderContext.gl;
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
}
}
/// Switches between mesh-based and stencil-based analytic antialiasing depending on whether stem
/// darkening is enabled.
///
/// FIXME(pcwalton): Share textures and FBOs between the two strategies.
export class AdaptiveStencilMeshAAAStrategy extends AntialiasingStrategy {
private meshStrategy: MCAAStrategy;
private stencilStrategy: StencilAAAStrategy;
get directRenderingMode(): DirectRenderingMode {
return 'none';
}
get passCount(): number {
return 1;
}
constructor(level: number, subpixelAA: SubpixelAAType) {
super(subpixelAA);
this.meshStrategy = new MCAAStrategy(level, subpixelAA);
this.stencilStrategy = new StencilAAAStrategy(level, subpixelAA);
}
init(renderer: Renderer): void {
this.meshStrategy.init(renderer);
this.stencilStrategy.init(renderer);
}
attachMeshes(renderer: Renderer): void {
this.meshStrategy.attachMeshes(renderer);
this.stencilStrategy.attachMeshes(renderer);
}
setFramebufferSize(renderer: Renderer): void {
this.meshStrategy.setFramebufferSize(renderer);
this.stencilStrategy.setFramebufferSize(renderer);
}
get transform(): glmatrix.mat4 {
return this.meshStrategy.transform;
}
prepareForRendering(renderer: Renderer): void {
this.getAppropriateStrategy(renderer).prepareForRendering(renderer);
}
prepareForDirectRendering(renderer: Renderer): void {
this.getAppropriateStrategy(renderer).prepareForDirectRendering(renderer);
}
finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {
this.getAppropriateStrategy(renderer).finishAntialiasingObject(renderer, objectIndex);
}
prepareToRenderObject(renderer: Renderer, objectIndex: number): void {
this.getAppropriateStrategy(renderer).prepareToRenderObject(renderer, objectIndex);
}
finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {
this.getAppropriateStrategy(renderer).finishDirectlyRenderingObject(renderer, objectIndex);
}
antialiasObject(renderer: Renderer, objectIndex: number): void {
this.getAppropriateStrategy(renderer).antialiasObject(renderer, objectIndex);
}
resolveAAForObject(renderer: Renderer, objectIndex: number): void {
this.getAppropriateStrategy(renderer).resolveAAForObject(renderer, objectIndex);
}
resolve(pass: number, renderer: Renderer): void {
this.getAppropriateStrategy(renderer).resolve(pass, renderer);
}
worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
return glmatrix.mat4.create();
}
private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy {
return renderer.needsStencil ? this.stencilStrategy : this.meshStrategy;
}
}
function calculateStartFromIndexRanges(pathRange: Range, indexRanges: Range[]): number {
return indexRanges.length === 0 ? 0 : indexRanges[pathRange.start - 1].start;
}
function calculateCountFromIndexRanges(pathRange: Range, indexRanges: Range[]): number {
if (indexRanges.length === 0)
return 0;
let lastIndex;
if (pathRange.end - 1 >= indexRanges.length)
lastIndex = unwrapUndef(_.last(indexRanges)).end;
else
lastIndex = indexRanges[pathRange.end - 1].start;
const firstIndex = indexRanges[pathRange.start - 1].start;
return lastIndex - firstIndex;
}

View File

@ -1,26 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"types": [
"base64-js",
"gl-matrix",
"lodash",
"node",
"opentype.js",
"webgl-ext"
],
"typeRoots": [
"node_modules/@types"
],
"sourceMap": true,
"strict": true,
"allowUnreachableCode": true
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}

View File

@ -1,28 +0,0 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {
"quotemark": false
},
"rules": {
"quotemark": false,
"interface-name": false,
"curly": false,
"member-access": false,
"max-classes-per-file": false,
"arrow-parens": false,
"one-variable-per-declaration": false,
"object-literal-shorthand": false,
"no-empty": false,
"variable-name": false,
"no-bitwise": false,
"new-parens": false,
"no-conditional-assignment": false,
"no-shadowed-variable": false,
"no-var-requires": false,
"max-line-length": [true, 100]
},
"rulesDirectory": []
}

View File

@ -1,64 +0,0 @@
const Handlebars = require('handlebars');
const HandlebarsPlugin = require('handlebars-webpack-plugin');
const RustdocPlugin = require('rustdoc-webpack-plugin');
const fs = require('fs');
const path = require('path');
const cwd = fs.realpathSync(".");
module.exports = {
devtool: 'inline-source-map',
entry: {
'3d-demo': "./src/3d-demo.ts",
'svg-demo': "./src/svg-demo.ts",
'text-demo': "./src/text-demo.ts",
'reference-test': "./src/reference-test.ts",
'benchmark': "./src/benchmark.ts",
'mesh-debugger': "./src/mesh-debugger.ts",
},
module: {
rules: [
{
test: /src(\/|\\)[a-zA-Z0-9_-]+\.tsx?$/,
enforce: 'pre',
loader: 'tslint-loader',
exclude: /node_modules/,
options: {
configFile: "tslint.json",
},
},
{
test: /src(\/|\\)[a-zA-Z0-9_-]+\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
]
},
resolve: {
extensions: [".tsx", ".ts", ".html", ".js"],
},
output: {
filename: "[name].js",
path: __dirname,
},
plugins: [
new HandlebarsPlugin({
entry: "html/*.hbs",
output: "./[name]",
partials: ["html/partials/*.hbs"],
helpers: {
octicon: function(iconName) {
const svg = fs.readFileSync(`node_modules/octicons/build/svg/${iconName}.svg`);
return new Handlebars.SafeString(svg);
}
},
}),
new RustdocPlugin({
directories: [fs.realpathSync("../..")],
flags: {
'html-in-header': path.join(cwd, "doc-header.html"),
'html-before-content': path.join(cwd, "doc-before-content.html"),
},
}),
]
}

37
demo/common/Cargo.toml Normal file
View File

@ -0,0 +1,37 @@
[package]
name = "pathfinder_demo"
version = "0.1.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
clap = "2.32"
gl = "0.6"
rayon = "1.0"
usvg = "0.4"
[dependencies.image]
version = "0.21"
default-features = false
features = ["png_codec"]
[dependencies.pathfinder_geometry]
path = "../../geometry"
[dependencies.pathfinder_gl]
path = "../../gl"
[dependencies.pathfinder_gpu]
path = "../../gpu"
[dependencies.pathfinder_renderer]
path = "../../renderer"
[dependencies.pathfinder_simd]
path = "../../simd"
[dependencies.pathfinder_svg]
path = "../../svg"
[dependencies.pathfinder_ui]
path = "../../ui"

90
demo/common/src/device.rs Normal file
View File

@ -0,0 +1,90 @@
// pathfinder/demo/common/src/device.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! GPU rendering code specifically for the demo.
use crate::GRIDLINE_COUNT;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BufferData, BufferTarget, BufferUploadMode, Device, VertexAttrType};
pub struct GroundProgram<D> where D: Device {
pub program: D::Program,
pub transform_uniform: D::Uniform,
pub color_uniform: D::Uniform,
}
impl<D> GroundProgram<D> where D: Device {
pub fn new(device: &D, resources: &dyn ResourceLoader) -> GroundProgram<D> {
let program = device.create_program(resources, "demo_ground");
let transform_uniform = device.get_uniform(&program, "Transform");
let color_uniform = device.get_uniform(&program, "Color");
GroundProgram { program, transform_uniform, color_uniform }
}
}
pub struct GroundSolidVertexArray<D> where D: Device {
pub vertex_array: D::VertexArray,
}
impl<D> GroundSolidVertexArray<D> where D: Device {
pub fn new(device: &D,
ground_program: &GroundProgram<D>,
quad_vertex_positions_buffer: &D::Buffer)
-> GroundSolidVertexArray<D> {
let vertex_array = device.create_vertex_array();
let position_attr = device.get_vertex_attr(&ground_program.program, "Position");
device.bind_vertex_array(&vertex_array);
device.use_program(&ground_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
GroundSolidVertexArray { vertex_array }
}
}
pub struct GroundLineVertexArray<D> where D: Device {
pub vertex_array: D::VertexArray,
#[allow(dead_code)]
grid_vertex_positions_buffer: D::Buffer,
}
impl<D> GroundLineVertexArray<D> where D: Device {
pub fn new(device: &D, ground_program: &GroundProgram<D>) -> GroundLineVertexArray<D> {
let grid_vertex_positions_buffer = device.create_buffer();
device.allocate_buffer(&grid_vertex_positions_buffer,
BufferData::Memory(&create_grid_vertex_positions()),
BufferTarget::Vertex,
BufferUploadMode::Static);
let vertex_array = device.create_vertex_array();
let position_attr = device.get_vertex_attr(&ground_program.program, "Position");
device.bind_vertex_array(&vertex_array);
device.use_program(&ground_program.program);
device.bind_buffer(&grid_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
GroundLineVertexArray { vertex_array, grid_vertex_positions_buffer }
}
}
fn create_grid_vertex_positions() -> Vec<(u8, u8)> {
let mut positions = vec![];
for index in 0..(GRIDLINE_COUNT + 1) {
positions.extend_from_slice(&[
(0, index), (GRIDLINE_COUNT, index),
(index, 0), (index, GRIDLINE_COUNT),
]);
}
positions
}

1245
demo/common/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

407
demo/common/src/ui.rs Normal file
View File

@ -0,0 +1,407 @@
// pathfinder/demo/src/ui.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::{BackgroundColor, Mode, Options};
use crate::window::Window;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::Device;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_renderer::gpu::debug::DebugUI;
use pathfinder_ui::{BUTTON_HEIGHT, BUTTON_TEXT_OFFSET, BUTTON_WIDTH, FONT_ASCENT, PADDING};
use pathfinder_ui::{TEXT_COLOR, TOOLTIP_HEIGHT, WINDOW_COLOR};
use std::f32::consts::PI;
use std::path::PathBuf;
const SLIDER_WIDTH: i32 = 360;
const SLIDER_HEIGHT: i32 = 48;
const SLIDER_TRACK_HEIGHT: i32 = 24;
const SLIDER_KNOB_WIDTH: i32 = 12;
const SLIDER_KNOB_HEIGHT: i32 = 48;
const EFFECTS_PANEL_WIDTH: i32 = 550;
const EFFECTS_PANEL_HEIGHT: i32 = BUTTON_HEIGHT * 3 + PADDING * 4;
const BACKGROUND_PANEL_WIDTH: i32 = 250;
const BACKGROUND_PANEL_HEIGHT: i32 = BUTTON_HEIGHT * 3;
const ROTATE_PANEL_WIDTH: i32 = SLIDER_WIDTH + PADDING * 2;
const ROTATE_PANEL_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT;
static EFFECTS_PNG_NAME: &'static str = "demo-effects";
static OPEN_PNG_NAME: &'static str = "demo-open";
static ROTATE_PNG_NAME: &'static str = "demo-rotate";
static ZOOM_IN_PNG_NAME: &'static str = "demo-zoom-in";
static ZOOM_OUT_PNG_NAME: &'static str = "demo-zoom-out";
static BACKGROUND_PNG_NAME: &'static str = "demo-background";
static SCREENSHOT_PNG_NAME: &'static str = "demo-screenshot";
pub struct DemoUI<D> where D: Device {
effects_texture: D::Texture,
open_texture: D::Texture,
rotate_texture: D::Texture,
zoom_in_texture: D::Texture,
zoom_out_texture: D::Texture,
background_texture: D::Texture,
screenshot_texture: D::Texture,
effects_panel_visible: bool,
background_panel_visible: bool,
rotate_panel_visible: bool,
// FIXME(pcwalton): Factor the below out into a model class.
pub mode: Mode,
pub background_color: BackgroundColor,
pub gamma_correction_effect_enabled: bool,
pub stem_darkening_effect_enabled: bool,
pub subpixel_aa_effect_enabled: bool,
pub rotation: i32,
pub message: String,
pub show_text_effects: bool,
}
impl<D> DemoUI<D> where D: Device {
pub fn new(device: &D, resources: &dyn ResourceLoader, options: Options) -> DemoUI<D> {
let effects_texture = device.create_texture_from_png(resources, EFFECTS_PNG_NAME);
let open_texture = device.create_texture_from_png(resources, OPEN_PNG_NAME);
let rotate_texture = device.create_texture_from_png(resources, ROTATE_PNG_NAME);
let zoom_in_texture = device.create_texture_from_png(resources, ZOOM_IN_PNG_NAME);
let zoom_out_texture = device.create_texture_from_png(resources, ZOOM_OUT_PNG_NAME);
let background_texture = device.create_texture_from_png(resources, BACKGROUND_PNG_NAME);
let screenshot_texture = device.create_texture_from_png(resources, SCREENSHOT_PNG_NAME);
DemoUI {
effects_texture,
open_texture,
rotate_texture,
zoom_in_texture,
zoom_out_texture,
background_texture,
screenshot_texture,
effects_panel_visible: false,
background_panel_visible: false,
rotate_panel_visible: false,
mode: options.mode,
background_color: options.background_color,
gamma_correction_effect_enabled: false,
stem_darkening_effect_enabled: false,
subpixel_aa_effect_enabled: false,
rotation: SLIDER_WIDTH / 2,
message: String::new(),
show_text_effects: true,
}
}
fn rotation(&self) -> f32 {
(self.rotation as f32 / SLIDER_WIDTH as f32 * 2.0 - 1.0) * PI
}
pub fn update<W>(&mut self,
device: &D,
window: &mut W,
debug_ui: &mut DebugUI<D>,
action: &mut UIAction)
where W: Window {
// Draw message text.
self.draw_message_text(device, debug_ui);
// Draw button strip.
let bottom = debug_ui.ui.framebuffer_size().y() - PADDING;
let mut position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT);
let button_size = Point2DI32::new(BUTTON_WIDTH, BUTTON_HEIGHT);
// Draw text effects button.
if self.show_text_effects {
if debug_ui.ui.draw_button(device, position, &self.effects_texture) {
self.effects_panel_visible = !self.effects_panel_visible;
}
if !self.effects_panel_visible {
debug_ui.ui.draw_tooltip(device,
"Text Effects",
RectI32::new(position, button_size));
}
position += Point2DI32::new(button_size.x() + PADDING, 0);
}
// Draw open button.
if debug_ui.ui.draw_button(device, position, &self.open_texture) {
// FIXME(pcwalton): This is not sufficient for Android, where we will need to take in
// the contents of the file.
window.present_open_svg_dialog();
}
debug_ui.ui.draw_tooltip(device, "Open SVG", RectI32::new(position, button_size));
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
// Draw screenshot button.
if debug_ui.ui.draw_button(device, position, &self.screenshot_texture) {
// FIXME(pcwalton): This is not sufficient for Android, where we will need to take in
// the contents of the file.
if let Ok(file) = window.run_save_dialog("png") {
*action = UIAction::TakeScreenshot(file);
}
}
debug_ui.ui.draw_tooltip(device, "Take Screenshot", RectI32::new(position, button_size));
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
// Draw mode switch.
let new_mode = debug_ui.ui.draw_text_switch(device,
position,
&["2D", "3D", "VR"],
self.mode as u8);
if new_mode != self.mode as u8 {
self.mode = match new_mode {
0 => Mode::TwoD,
1 => Mode::ThreeD,
_ => Mode::VR,
};
*action = UIAction::ModelChanged;
}
let mode_switch_width = debug_ui.ui.measure_switch(3);
let mode_switch_size = Point2DI32::new(mode_switch_width, BUTTON_HEIGHT);
debug_ui.ui.draw_tooltip(device,
"2D/3D/VR Mode",
RectI32::new(position, mode_switch_size));
position += Point2DI32::new(mode_switch_width + PADDING, 0);
// Draw background switch.
if debug_ui.ui.draw_button(device, position, &self.background_texture) {
self.background_panel_visible = !self.background_panel_visible;
}
if !self.background_panel_visible {
debug_ui.ui.draw_tooltip(device,
"Background Color",
RectI32::new(position, button_size));
}
// Draw background panel, if necessary.
self.draw_background_panel(device, debug_ui, position.x(), action);
position += Point2DI32::new(button_size.x() + PADDING, 0);
// Draw effects panel, if necessary.
self.draw_effects_panel(device, debug_ui);
// Draw rotate and zoom buttons, if applicable.
if self.mode != Mode::TwoD {
return;
}
if debug_ui.ui.draw_button(device, position, &self.rotate_texture) {
self.rotate_panel_visible = !self.rotate_panel_visible;
}
if !self.rotate_panel_visible {
debug_ui.ui.draw_tooltip(device, "Rotate", RectI32::new(position, button_size));
}
self.draw_rotate_panel(device, debug_ui, position.x(), action);
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
if debug_ui.ui.draw_button(device, position, &self.zoom_in_texture) {
*action = UIAction::ZoomIn;
}
debug_ui.ui.draw_tooltip(device, "Zoom In", RectI32::new(position, button_size));
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
if debug_ui.ui.draw_button(device, position, &self.zoom_out_texture) {
*action = UIAction::ZoomOut;
}
debug_ui.ui.draw_tooltip(device, "Zoom Out", RectI32::new(position, button_size));
position += Point2DI32::new(BUTTON_WIDTH + PADDING, 0);
}
fn draw_message_text(&mut self, device: &D, debug_ui: &mut DebugUI<D>) {
if self.message.is_empty() {
return;
}
let message_size = debug_ui.ui.measure_text(&self.message);
let window_origin = Point2DI32::new(PADDING, PADDING);
let window_size = Point2DI32::new(PADDING * 2 + message_size, TOOLTIP_HEIGHT);
debug_ui.ui.draw_solid_rounded_rect(device,
RectI32::new(window_origin, window_size),
WINDOW_COLOR);
debug_ui.ui.draw_text(device,
&self.message,
window_origin + Point2DI32::new(PADDING, PADDING + FONT_ASCENT),
false);
}
fn draw_effects_panel(&mut self, device: &D, debug_ui: &mut DebugUI<D>) {
if !self.effects_panel_visible {
return;
}
let bottom = debug_ui.ui.framebuffer_size().y() - PADDING;
let effects_panel_y = bottom - (BUTTON_HEIGHT + PADDING + EFFECTS_PANEL_HEIGHT);
debug_ui.ui.draw_solid_rounded_rect(device,
RectI32::new(Point2DI32::new(PADDING, effects_panel_y),
Point2DI32::new(EFFECTS_PANEL_WIDTH,
EFFECTS_PANEL_HEIGHT)),
WINDOW_COLOR);
self.gamma_correction_effect_enabled =
self.draw_effects_switch(device,
debug_ui,
"Gamma Correction",
0,
effects_panel_y,
self.gamma_correction_effect_enabled);
self.stem_darkening_effect_enabled =
self.draw_effects_switch(device,
debug_ui,
"Stem Darkening",
1,
effects_panel_y,
self.stem_darkening_effect_enabled);
self.subpixel_aa_effect_enabled =
self.draw_effects_switch(device,
debug_ui,
"Subpixel AA",
2,
effects_panel_y,
self.subpixel_aa_effect_enabled);
}
fn draw_background_panel(&mut self,
device: &D,
debug_ui: &mut DebugUI<D>,
panel_x: i32,
action: &mut UIAction) {
if !self.background_panel_visible {
return;
}
let bottom = debug_ui.ui.framebuffer_size().y() - PADDING;
let panel_y = bottom - (BUTTON_HEIGHT + PADDING + BACKGROUND_PANEL_HEIGHT);
let panel_position = Point2DI32::new(panel_x, panel_y);
debug_ui.ui.draw_solid_rounded_rect(device,
RectI32::new(panel_position,
Point2DI32::new(BACKGROUND_PANEL_WIDTH,
BACKGROUND_PANEL_HEIGHT)),
WINDOW_COLOR);
self.draw_background_menu_item(device,
debug_ui,
BackgroundColor::Light,
panel_position,
action);
self.draw_background_menu_item(device,
debug_ui,
BackgroundColor::Dark,
panel_position,
action);
self.draw_background_menu_item(device,
debug_ui,
BackgroundColor::Transparent,
panel_position,
action);
}
fn draw_rotate_panel(&mut self,
device: &D,
debug_ui: &mut DebugUI<D>,
rotate_panel_x: i32,
action: &mut UIAction) {
if !self.rotate_panel_visible {
return;
}
let bottom = debug_ui.ui.framebuffer_size().y() - PADDING;
let rotate_panel_y = bottom - (BUTTON_HEIGHT + PADDING + ROTATE_PANEL_HEIGHT);
let rotate_panel_origin = Point2DI32::new(rotate_panel_x, rotate_panel_y);
let rotate_panel_size = Point2DI32::new(ROTATE_PANEL_WIDTH, ROTATE_PANEL_HEIGHT);
debug_ui.ui.draw_solid_rounded_rect(device,
RectI32::new(rotate_panel_origin, rotate_panel_size),
WINDOW_COLOR);
let (widget_x, widget_y) = (rotate_panel_x + PADDING, rotate_panel_y + PADDING);
let widget_rect = RectI32::new(Point2DI32::new(widget_x, widget_y),
Point2DI32::new(SLIDER_WIDTH, SLIDER_KNOB_HEIGHT));
if let Some(position) = debug_ui.ui
.event_queue
.handle_mouse_down_or_dragged_in_rect(widget_rect) {
self.rotation = position.x();
*action = UIAction::Rotate(self.rotation());
}
let slider_track_y = rotate_panel_y + PADDING + SLIDER_KNOB_HEIGHT / 2 -
SLIDER_TRACK_HEIGHT / 2;
let slider_track_rect =
RectI32::new(Point2DI32::new(widget_x, slider_track_y),
Point2DI32::new(SLIDER_WIDTH, SLIDER_TRACK_HEIGHT));
debug_ui.ui.draw_rect_outline(device, slider_track_rect, TEXT_COLOR);
let slider_knob_x = widget_x + self.rotation - SLIDER_KNOB_WIDTH / 2;
let slider_knob_rect =
RectI32::new(Point2DI32::new(slider_knob_x, widget_y),
Point2DI32::new(SLIDER_KNOB_WIDTH, SLIDER_KNOB_HEIGHT));
debug_ui.ui.draw_solid_rect(device, slider_knob_rect, TEXT_COLOR);
}
fn draw_background_menu_item(&mut self,
device: &D,
debug_ui: &mut DebugUI<D>,
color: BackgroundColor,
panel_position: Point2DI32,
action: &mut UIAction) {
let (text, index) = (color.as_str(), color as i32);
let widget_size = Point2DI32::new(BACKGROUND_PANEL_WIDTH, BUTTON_HEIGHT);
let widget_origin = panel_position + Point2DI32::new(0, widget_size.y() * index);
let widget_rect = RectI32::new(widget_origin, widget_size);
if color == self.background_color {
debug_ui.ui.draw_solid_rounded_rect(device, widget_rect, TEXT_COLOR);
}
let (text_x, text_y) = (PADDING * 2, BUTTON_TEXT_OFFSET);
let text_position = widget_origin + Point2DI32::new(text_x, text_y);
debug_ui.ui.draw_text(device, text, text_position, color == self.background_color);
if let Some(_) = debug_ui.ui.event_queue.handle_mouse_down_in_rect(widget_rect) {
self.background_color = color;
*action = UIAction::ModelChanged;
}
}
fn draw_effects_switch(&self,
device: &D,
debug_ui: &mut DebugUI<D>,
text: &str,
index: i32,
window_y: i32,
value: bool)
-> bool {
let text_x = PADDING * 2;
let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index;
debug_ui.ui.draw_text(device, text, Point2DI32::new(text_x, text_y), false);
let switch_width = debug_ui.ui.measure_switch(2);
let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING);
let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index;
let switch_position = Point2DI32::new(switch_x, switch_y);
debug_ui.ui.draw_text_switch(device, switch_position, &["Off", "On"], value as u8) != 0
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum UIAction {
None,
ModelChanged,
TakeScreenshot(PathBuf),
ZoomIn,
ZoomOut,
Rotate(f32),
}

100
demo/common/src/window.rs Normal file
View File

@ -0,0 +1,100 @@
// pathfinder/demo/common/src/window.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 minimal cross-platform windowing layer.
use gl::types::GLuint;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32};
use pathfinder_geometry::distortion::BarrelDistortionCoefficients;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader;
use rayon::ThreadPoolBuilder;
use std::path::PathBuf;
pub trait Window {
fn gl_version(&self) -> GLVersion;
fn gl_default_framebuffer(&self) -> GLuint { 0 }
fn viewport(&self, view: View) -> RectI32;
fn make_current(&mut self, view: View);
fn present(&mut self);
fn resource_loader(&self) -> &dyn ResourceLoader;
fn create_user_event_id(&self) -> u32;
fn push_user_event(message_type: u32, message_data: u32);
fn present_open_svg_dialog(&mut self);
fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()>;
fn adjust_thread_pool_settings(&self, thread_pool_builder: ThreadPoolBuilder) -> ThreadPoolBuilder {
thread_pool_builder
}
#[inline]
fn barrel_distortion_coefficients(&self) -> BarrelDistortionCoefficients {
BarrelDistortionCoefficients::default()
}
}
pub enum Event {
Quit,
WindowResized(WindowSize),
KeyDown(Keycode),
KeyUp(Keycode),
MouseDown(Point2DI32),
MouseMoved(Point2DI32),
MouseDragged(Point2DI32),
Zoom(f32, Point2DI32),
Look { pitch: f32, yaw: f32 },
SetEyeTransforms(Vec<OcularTransform>),
OpenSVG(SVGPath),
User { message_type: u32, message_data: u32 },
}
#[derive(Clone, Copy)]
pub enum Keycode {
Alphanumeric(u8),
Escape,
Tab,
}
#[derive(Clone, Copy, Debug)]
pub struct WindowSize {
pub logical_size: Point2DI32,
pub backing_scale_factor: f32,
}
impl WindowSize {
#[inline]
pub fn device_size(&self) -> Point2DI32 {
self.logical_size.to_f32().scale(self.backing_scale_factor).to_i32()
}
}
#[derive(Clone, Copy, Debug)]
pub enum View {
Mono,
Stereo(u32),
}
#[derive(Clone, Copy, Debug)]
pub struct OcularTransform {
// The perspective which converts from camera coordinates to display coordinates
pub perspective: Perspective,
// The view transform which converts from world coordinates to camera coordinates
pub modelview_to_eye: Transform3DF32,
}
#[derive(Clone)]
pub enum SVGPath {
Default,
Resource(String),
Path(PathBuf),
}

View File

@ -0,0 +1,4 @@
[target.aarch64-linux-android]
linker = "./fake-ld.sh"
ar = "aarch64-linux-android-ar"
rustflags = "-Clinker-flavor=ld"

1
demo/magicleap/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.out/

45
demo/magicleap/Cargo.toml Normal file
View File

@ -0,0 +1,45 @@
[package]
name = "pathfinder_magicleap_demo"
version = "0.1.0"
edition = "2018"
authors = ["Alan Jeffrey <ajeffrey@mozilla.com>"]
[dependencies]
gl = "0.6"
rayon = "1.0"
usvg = "0.4"
egl = "0.2"
log = "0.4"
smallvec = "0.6"
glutin = { version = "0.19", optional = true }
crossbeam-channel = "0.3"
[lib]
crate-type = ["cdylib"]
[features]
mocked = ["glutin"]
[dependencies.pathfinder_demo]
path = "../common"
[dependencies.pathfinder_geometry]
path = "../../geometry"
[dependencies.pathfinder_gl]
path = "../../gl"
[dependencies.pathfinder_gpu]
path = "../../gpu"
[dependencies.pathfinder_renderer]
path = "../../renderer"
[dependencies.pathfinder_simd]
path = "../../simd"
[dependencies.pathfinder_svg]
path = "../../svg"
[dependencies.pathfinder_ui]
path = "../../ui"

15
demo/magicleap/Makefile Normal file
View File

@ -0,0 +1,15 @@
.PHONY: debug release install
debug:
cargo build --target aarch64-linux-android
$(MAGICLEAP_SDK)/mabu -t debug_device PathfinderDemo.package -s $(MLCERT)
release:
cargo build --release --target aarch64-linux-android
$(MAGICLEAP_SDK)/mabu -t release_device PathfinderDemo.package -s $(MLCERT)
install: .out/PathfinderDemo/PathfinderDemo.mpk
$(MAGICLEAP_SDK)/tools/mldb/mldb install -u .out/PathfinderDemo/PathfinderDemo.mpk
run:
${MAGICLEAP_SDK}/tools/mldb/mldb launch -w com.mozilla.pathfinder.demo

View File

@ -0,0 +1,19 @@
KIND = program
SRCS = src/main.cpp
LIBPATHS.debug = \
../../target/aarch64-linux-android/debug
LIBPATHS.release = \
../../target/aarch64-linux-android/release
USES = ml_sdk OpenGL stdc++
STLIBS = \
pathfinder_immersive_demo
SHLIBS = \
ml_privileges
DATAS = \
../../resources/** : resources/

View File

@ -0,0 +1,8 @@
USES = "lre/scenes"
REFS = PathfinderImmersiveDemo PathfinderLandscapeDemo
OPTIONS=package/debuggable/on
DATAS = \
../../target/aarch64-linux-android/release/*.so : bin/

View File

@ -0,0 +1,17 @@
KIND = program
SRCS = src/main.cpp
LIBPATHS.debug = \
../../target/aarch64-linux-android/debug
LIBPATHS.release = \
../../target/aarch64-linux-android/release
USES = ml_sdk OpenGL stdc++
SHLIBS = \
pathfinder_magicleap_demo \
ml_privileges
DATAS = \
../../resources/** : resources/

View File

@ -0,0 +1,19 @@
KIND = program
SRCS = src/landscape.cpp
LIBPATHS.debug = \
../../target/aarch64-linux-android/debug
LIBPATHS.release = \
../../target/aarch64-linux-android/release
INCS = \
src/ \
lre/code/inc/gen/
USES = \
lumin_runtime \
lre/code/srcs
SHLIBS = \
pathfinder_magicleap_demo

25
demo/magicleap/README.md Normal file
View File

@ -0,0 +1,25 @@
# Magic Leap demo
First, install v0.20.0 or later of the Magic Leap SDK. By default this is installed in `MagicLeap/mlsdk/<version>`, for example:
```
export MAGICLEAP_SDK=~/MagicLeap/mlsdk/v0.20.0
```
You will also need a signing certificate.
```
export MLCERT=~/MagicLeap/cert/mycert.cert
```
Now build the pathfinder demo library and `.mpk` archive:
```
cd demo/pathfinder
make release
```
The `.mpk` can be installed:
```
make install
```
and run:
```
make run
```

18
demo/magicleap/fake-ld.sh Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# This shell script strips out the -landroid that is passed by default by rustc to
# the linker on aarch64-linux-android, and adds some entries to the ld search path.
set -o errexit
set -o nounset
set -o pipefail
TARGET=${TARGET:-"aarch64-linux-android"}
LD=${LD:-"${MAGICLEAP_SDK}/tools/toolchains/bin/${TARGET}-ld"}
LDFLAGS=${LDFLAGS:-"--sysroot=${MAGICLEAP_SDK}/lumin -L${MAGICLEAP_SDK}/lumin/usr/lib -L${MAGICLEAP_SDK}/tools/toolchains/lib/gcc/${TARGET}/4.9.x ${MAGICLEAP_SDK}/lumin/usr/lib/crtbegin_so.o"}
# Remove the -landroid flag, grr
ARGS=("$@")
ARGS=${ARGS[@]/-landroid}
${LD} ${LDFLAGS} ${ARGS}

12
demo/magicleap/lre/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.DS_Store
*.log
*.json.dirty
*.json.lock
*.pyc
*.sln
*.vcxproj*
pipeline/cache/intermediate/
.out/
.vscode/
.vs/

View File

@ -0,0 +1,13 @@
KIND = program
INCS = \
code/inc/ \
code/inc/gen/
SRCS = \
code/src/main.cpp \
code/src/PathfinderDemo.cpp
USES = \
lumin_runtime \
code/srcs

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="ASCII"?>
<mlproject:mlproject xmlns:mlproject="http://www.magicleap.com/uidesigner/mlproject" generated="true" srcGenVersion="1">
<designFile path="scenes/PathfinderDemo.design"/>
<pipelineDirectory path="pipeline"/>
<preferences key="srcgen.directories.incgen" value="inc/gen"/>
<preferences key="srcgen.directories.src" value="src"/>
<preferences key="srcgen.directories.srcgen" value="src/gen"/>
<preferences key="srcgen.directories.basedir" value="code"/>
<preferences key="srcgen.directories.inc" value="inc"/>
</mlproject:mlproject>

View File

@ -0,0 +1,3 @@
USES = "scenes" "pipeline/cache/AssetManifest"
REFS = PathfinderDemo

View File

@ -0,0 +1,99 @@
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <lumin/LandscapeApp.h>
#include <lumin/Prism.h>
#include <lumin/event/ServerEvent.h>
#include <SceneDescriptor.h>
#include <PrismSceneManager.h>
/**
* PathfinderDemo Landscape Application
*/
class PathfinderDemo : public lumin::LandscapeApp {
public:
/**
* Constructs the Landscape Application.
*/
PathfinderDemo();
/**
* Destroys the Landscape Application.
*/
virtual ~PathfinderDemo();
/**
* Disallows the copy constructor.
*/
PathfinderDemo(const PathfinderDemo&) = delete;
/**
* Disallows the move constructor.
*/
PathfinderDemo(PathfinderDemo&&) = delete;
/**
* Disallows the copy assignment operator.
*/
PathfinderDemo& operator=(const PathfinderDemo&) = delete;
/**
* Disallows the move assignment operator.
*/
PathfinderDemo& operator=(PathfinderDemo&&) = delete;
protected:
/**
* Initializes the Landscape Application.
* @return - 0 on success, error code on failure.
*/
int init() override;
/**
* Deinitializes the Landscape Application.
* @return - 0 on success, error code on failure.
*/
int deInit() override;
/**
* Returns the initial size of the Prism
* Used in createPrism().
*/
const glm::vec3 getInitialPrismSize() const;
/**
* Creates the prism, updates the private variable prism_ with the created prism.
*/
void createInitialPrism();
/**
* Initializes and creates the scene of all scenes marked as initially instanced
*/
void spawnInitialScenes();
/**
* Run application login
*/
virtual bool updateLoop(float fDelta) override;
/**
* Handle events from the server
*/
virtual bool eventListener(lumin::ServerEvent* event) override;
private:
lumin::Prism* prism_ = nullptr; // represents the bounded space where the App renders.
PrismSceneManager* prismSceneManager_ = nullptr;
};

View File

@ -0,0 +1,45 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
#include <SpawnedSceneBase.h>
#include <SpawnedSceneHandlers.h>
namespace scenes {
namespace PathfinderDemo {
namespace externalNodes {
}
struct SpawnedScene : public SpawnedSceneBase {
SpawnedScene(const SceneDescriptor& sceneDescriptor, lumin::Node* root);
~SpawnedScene();
};
SpawnedSceneBase* createSpawnedScene(const SceneDescriptor& sceneDescriptor, lumin::Node* root);
SpawnedSceneHandlers* createSpawnedSceneHandlers(SpawnedSceneBase& spawnedScene);
extern const SceneDescriptor descriptor;
}
}

View File

@ -0,0 +1,74 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
#include <lumin/Prism.h>
#include <lumin/node/Node.h>
#include <SceneDescriptor.h>
#include <SpawnedSceneBase.h>
#include <SpawnedSceneUserData.h>
#include <scenes.h>
class PrismSceneManager {
public:
typedef std::function<SpawnedSceneUserData*(SpawnedSceneBase&)> (*CreateSpawnedSceneUserData);
static void setUserDataCreator(const SceneDescriptor & sceneDescriptor, CreateSpawnedSceneUserData createSpawnedSceneUserData);
public:
PrismSceneManager(lumin::Prism* prism);
enum class SceneState {
Unloaded,
ResourceModelLoaded,
ResourceAndObjectModelLoaded,
};
void setSceneState(const SceneDescriptor & sceneDescriptor, SceneState sceneState);
SceneState getSceneState(const SceneDescriptor & sceneDescriptor, SceneState sceneState) const;
SpawnedSceneBase* spawnScene(const SceneDescriptor & sceneDescriptor);
lumin::Node* spawn(const SceneDescriptor & sceneDescriptor);
private:
typedef SpawnedSceneBase* (*CreateSpawnedScene)(const SceneDescriptor& sceneDescriptor, lumin::Node* root);
static const CreateSpawnedScene createSpawnedScene[scenes::numberOfExternalScenes];
typedef SpawnedSceneHandlers* (*CreateSpawnedSceneHandlers)(SpawnedSceneBase& spawnedScene);
static const CreateSpawnedSceneHandlers createSpawnedSceneHandlers[scenes::numberOfExternalScenes];
static CreateSpawnedSceneUserData createSpawnedSceneUserData[scenes::numberOfExternalScenes];
private:
lumin::Node* createNodeTree(const SceneDescriptor & sceneDescriptor);
private:
lumin::Prism* prism_;
SceneState sceneStates_[scenes::numberOfExternalScenes];
std::string objectModelNames_[scenes::numberOfExternalScenes];
};

View File

@ -0,0 +1,58 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
#include <string>
#include <map>
// data class
class SceneDescriptor {
public:
typedef std::map<std::string /* externalNodeName */, const std::string& /* externalNodeId */> ExternalNodeReferences;
SceneDescriptor(int index, const char* externalName, const char* id, const char* sceneGraphFilePath, const char* resourceModelFilePath, const ExternalNodeReferences& externalNodeReferences, bool initiallySpawned);
const std::string& getExternalName() const;
const std::string& getId() const;
const std::string& getSceneGraphPath() const;
const std::string& getResourceModelPath() const;
const ExternalNodeReferences & getExternalNodeReferences() const;
bool getInitiallySpawned() const;
private:
friend class PrismSceneManager;
int getIndex() const;
private:
int index_;
std::string externalName_;
std::string id_;
std::string sceneGraphPath_;
std::string resourceModelPath_;
const ExternalNodeReferences& externalNodeReferences_;
bool initiallySpawned_;
};
bool operator<(const SceneDescriptor& a, const SceneDescriptor& b);

View File

@ -0,0 +1,43 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
#include <lumin/node/Node.h>
class SceneDescriptor;
class SpawnedSceneHandlers;
struct SpawnedSceneUserData;
struct SpawnedSceneBase {
SpawnedSceneBase(const SceneDescriptor &sd, lumin::Node* rt);
virtual ~SpawnedSceneBase();
SpawnedSceneBase() = delete;
SpawnedSceneBase(const SpawnedSceneBase&) = delete;
SpawnedSceneBase(const SpawnedSceneBase&&) = delete;
const SceneDescriptor& sceneDescriptor;
lumin::Node* root = nullptr;
SpawnedSceneHandlers* handlers = nullptr;
SpawnedSceneUserData* userData = nullptr;
};

View File

@ -0,0 +1,31 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
struct SpawnedSceneBase;
class SpawnedSceneHandlers {
public:
SpawnedSceneHandlers(SpawnedSceneBase& ssb);
virtual ~SpawnedSceneHandlers();
};

View File

@ -0,0 +1,27 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
struct SpawnedSceneUserData {
virtual ~SpawnedSceneUserData();
};

View File

@ -0,0 +1,33 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#pragma once
#include <SceneDescriptor.h>
#include <map>
namespace scenes {
const int numberOfExternalScenes = 1;
typedef std::map<std::string /* externalName */, const SceneDescriptor& /* sceneDescription */> SceneDescriptorReferences;
extern const SceneDescriptorReferences externalScenes;
}

View File

@ -0,0 +1,102 @@
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <PathfinderDemo.h>
#include <lumin/node/RootNode.h>
#include <lumin/ui/Cursor.h>
#include <ml_logging.h>
#include <scenes.h>
#include <PrismSceneManager.h>
PathfinderDemo::PathfinderDemo() {
ML_LOG(Debug, "PathfinderDemo Constructor.");
// Place your constructor implementation here.
}
PathfinderDemo::~PathfinderDemo() {
ML_LOG(Debug, "PathfinderDemo Destructor.");
// Place your destructor implementation here.
}
const glm::vec3 PathfinderDemo::getInitialPrismSize() const {
return glm::vec3(2.0f, 2.0f, 2.0f);
}
void PathfinderDemo::createInitialPrism() {
prism_ = requestNewPrism(getInitialPrismSize());
if (!prism_) {
ML_LOG(Error, "PathfinderDemo Error creating default prism.");
abort();
}
prismSceneManager_ = new PrismSceneManager(prism_);
}
int PathfinderDemo::init() {
ML_LOG(Debug, "PathfinderDemo Initializing.");
createInitialPrism();
lumin::ui::Cursor::SetScale(prism_, 0.03f);
spawnInitialScenes();
// Place your initialization here.
return 0;
}
int PathfinderDemo::deInit() {
ML_LOG(Debug, "PathfinderDemo Deinitializing.");
// Place your deinitialization here.
return 0;
}
void PathfinderDemo::spawnInitialScenes() {
// Iterate over all the exported scenes
for (auto& exportedSceneEntry : scenes::externalScenes ) {
// If this scene was marked to be instanced at app initialization, do it
const SceneDescriptor &sd = exportedSceneEntry.second;
if (sd.getInitiallySpawned()) {
lumin::Node* const spawnedRoot = prismSceneManager_->spawn(sd);
if (spawnedRoot) {
if (!prism_->getRootNode()->addChild(spawnedRoot)) {
ML_LOG(Error, "PathfinderDemo Failed to add spawnedRoot to the prism root node");
abort();
}
}
}
}
}
bool PathfinderDemo::updateLoop(float fDelta) {
// Place your update here.
// Return true for your app to continue running, false to terminate the app.
return true;
}
bool PathfinderDemo::eventListener(lumin::ServerEvent* event) {
// Place your event handling here.
// Return true if the event is consumed.
return false;
}

View File

@ -0,0 +1,63 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <SceneDescriptor.h>
#include <SpawnedSceneBase.h>
#include <PathfinderDemo/PathfinderDemo.h>
namespace scenes {
namespace PathfinderDemo {
SpawnedScene::SpawnedScene(const SceneDescriptor& sceneDescriptor, lumin::Node* root)
: SpawnedSceneBase(sceneDescriptor, root) {
}
SpawnedScene::~SpawnedScene() {
}
SpawnedSceneBase* createSpawnedScene(const SceneDescriptor& sceneDescriptor, lumin::Node* root) {
using namespace externalNodes;
SpawnedScene* spawnedScene = new SpawnedScene(sceneDescriptor, root);
return spawnedScene;
}
class Handlers : public SpawnedSceneHandlers
{
public:
Handlers(SpawnedScene& ss);
private:
};
Handlers::Handlers(SpawnedScene& ss)
: SpawnedSceneHandlers(ss)
{
}
SpawnedSceneHandlers* createSpawnedSceneHandlers(SpawnedSceneBase& ssb) {
return new Handlers(static_cast<SpawnedScene&>(ssb));
}
}
}

View File

@ -0,0 +1,124 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <PrismSceneManager.h>
#include <ml_logging.h>
PrismSceneManager::CreateSpawnedSceneUserData PrismSceneManager::createSpawnedSceneUserData[scenes::numberOfExternalScenes];
PrismSceneManager::PrismSceneManager(lumin::Prism* prism)
: prism_(prism) {
if (!prism_) {
ML_LOG(Error, "PrismSceneManager nullptr prism");
abort();
}
for (int i = 0; i < sizeof(sceneStates_)/sizeof(sceneStates_[0]); ++i) {
sceneStates_[i] = SceneState::Unloaded;
}
}
void PrismSceneManager::setSceneState(const SceneDescriptor & sceneDescriptor, SceneState newState) {
const int sceneIndex = sceneDescriptor.getIndex();
SceneState& sceneState = sceneStates_[sceneIndex];
std::string& objectModelName = objectModelNames_[sceneIndex];
if (sceneState == SceneState::Unloaded && (newState == SceneState::ResourceModelLoaded || newState == SceneState::ResourceAndObjectModelLoaded)) {
if (!prism_->loadResourceModel(sceneDescriptor.getResourceModelPath())) {
ML_LOG(Error, "PrismSceneManager failed to load resource model");
abort();
}
sceneState = SceneState::ResourceModelLoaded;
}
if (sceneState == SceneState::ResourceModelLoaded && newState == SceneState::ResourceAndObjectModelLoaded) {
std::string& objectModelName = objectModelNames_[sceneIndex];
if (!prism_->loadObjectModel(sceneDescriptor.getSceneGraphPath(), objectModelName)) {
ML_LOG(Error, "PrismSceneManager failed to load object model");
abort();
}
sceneState = SceneState::ResourceAndObjectModelLoaded;
}
if (sceneState == SceneState::ResourceAndObjectModelLoaded && (newState == SceneState::ResourceModelLoaded || newState == SceneState::Unloaded)) {
if (!prism_->unloadObjectModel(objectModelName)) {
ML_LOG(Error, "PrismSceneManager failed to unload object model");
abort();
}
sceneState = SceneState::ResourceModelLoaded;
objectModelName.clear();
}
// Currently there is no effective way to unload the resource model
}
SpawnedSceneBase* PrismSceneManager::spawnScene(const SceneDescriptor & sceneDescriptor) {
lumin::Node* root = createNodeTree(sceneDescriptor);
if (!root) {
return nullptr;
}
const int index = sceneDescriptor.getIndex();
CreateSpawnedScene css = createSpawnedScene[index];
SpawnedSceneBase* const spawnedScene = (*css)(sceneDescriptor, root);
CreateSpawnedSceneHandlers ch = createSpawnedSceneHandlers[index];
SpawnedSceneHandlers* const handlers = (*ch)(*spawnedScene);
spawnedScene->handlers = handlers;
CreateSpawnedSceneUserData cssud = createSpawnedSceneUserData[sceneDescriptor.getIndex()];
if (cssud) {
spawnedScene->userData = (*cssud)(*spawnedScene);
}
return spawnedScene;
}
lumin::Node* PrismSceneManager::spawn(const SceneDescriptor & sceneDescriptor) {
SpawnedSceneBase* spawnedSceneBase = spawnScene(sceneDescriptor);
if (!spawnedSceneBase) {
return nullptr;
}
lumin::Node* root = spawnedSceneBase->root;
return root;
}
lumin::Node* PrismSceneManager::createNodeTree(const SceneDescriptor & sceneDescriptor) {
setSceneState(sceneDescriptor, SceneState::ResourceAndObjectModelLoaded);
const int sceneIndex = sceneDescriptor.getIndex();
std::string& objectModelName = objectModelNames_[sceneIndex];
lumin::Node* root = prism_->createAll(objectModelName);
if (!root) {
ML_LOG(Error, "PrismSceneManager failed to create the scene. Is the scene empty?");
return nullptr;
}
return root;
}
void PrismSceneManager::setUserDataCreator(const SceneDescriptor & sceneDescriptor, CreateSpawnedSceneUserData cssud) {
createSpawnedSceneUserData[sceneDescriptor.getIndex()] = cssud ;
}

View File

@ -0,0 +1,66 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <SceneDescriptor.h>
SceneDescriptor::SceneDescriptor(int index, const char * externalName, const char * id, const char * sceneGraphPath, const char * resourceModelPath, const ExternalNodeReferences& externalNodeReferences, bool initiallySpawned)
:
index_(index),
externalName_(externalName),
id_(id),
sceneGraphPath_(sceneGraphPath),
resourceModelPath_(resourceModelPath),
externalNodeReferences_(externalNodeReferences),
initiallySpawned_(initiallySpawned) {
}
int SceneDescriptor::getIndex() const {
return index_;
}
const std::string & SceneDescriptor::getExternalName() const {
return externalName_;
}
const std::string & SceneDescriptor::getId() const {
return id_;
}
const std::string & SceneDescriptor::getSceneGraphPath() const {
return sceneGraphPath_;
}
const std::string & SceneDescriptor::getResourceModelPath() const {
return resourceModelPath_;
}
const SceneDescriptor::ExternalNodeReferences & SceneDescriptor::getExternalNodeReferences() const {
return externalNodeReferences_;
}
bool SceneDescriptor::getInitiallySpawned() const {
return initiallySpawned_;
}
bool operator<(const SceneDescriptor& a, const SceneDescriptor& b) {
return a.getExternalName() < b.getExternalName();
}

View File

@ -0,0 +1,35 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <SpawnedSceneBase.h>
#include <SpawnedSceneHandlers.h>
#include <SpawnedSceneUserData.h>
SpawnedSceneBase::SpawnedSceneBase(const SceneDescriptor &sd, lumin::Node* rt)
: sceneDescriptor(sd),
root(rt) {
}
SpawnedSceneBase::~SpawnedSceneBase() {
delete handlers;
delete userData;
}

View File

@ -0,0 +1,29 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <SpawnedSceneHandlers.h>
SpawnedSceneHandlers::SpawnedSceneHandlers(SpawnedSceneBase& ssb) {
}
SpawnedSceneHandlers::~SpawnedSceneHandlers() {
}

View File

@ -0,0 +1,26 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <SpawnedSceneUserData.h>
SpawnedSceneUserData::~SpawnedSceneUserData() {
}

View File

@ -0,0 +1,64 @@
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
//
// THE CONTENTS OF THIS FILE IS GENERATED BY CODE AND
// ANY MODIFICATIONS WILL BE OVERWRITTEN
//
// -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING -- WARNING --
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <PrismSceneManager.h>
#include <scenes.h>
#include <PathfinderDemo/PathfinderDemo.h>
namespace scenes {
namespace PathfinderDemo {
namespace externalNodes {
}
const SceneDescriptor::ExternalNodeReferences externalNodesMap = {
};
const SceneDescriptor descriptor(
0,
"PathfinderDemo",
"root",
"/assets/scenes/PathfinderDemo.scene.xml",
"/assets/scenes/PathfinderDemo.scene.res.xml",
externalNodesMap,
true);
}
const SceneDescriptorReferences externalScenes = {
{PathfinderDemo::descriptor.getExternalName(), PathfinderDemo::descriptor}
};
struct VerifyNumberOfExternalScenes {
VerifyNumberOfExternalScenes() { assert(externalScenes.size() == numberOfExternalScenes); }
};
VerifyNumberOfExternalScenes verifyNumberOfExternalScenes;
}
const PrismSceneManager::CreateSpawnedScene PrismSceneManager::createSpawnedScene[scenes::numberOfExternalScenes] = {
::scenes::PathfinderDemo::createSpawnedScene
};
const PrismSceneManager::CreateSpawnedSceneHandlers PrismSceneManager::createSpawnedSceneHandlers[scenes::numberOfExternalScenes] = {
static_cast<CreateSpawnedSceneHandlers>(::scenes::PathfinderDemo::createSpawnedSceneHandlers)
};

View File

@ -0,0 +1,25 @@
// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
//
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
//
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
// %SRC_VERSION%: 1
#include <PathfinderDemo.h>
#include <ml_logging.h>
int main(int argc, char **argv)
{
ML_LOG(Debug, "PathfinderDemo Starting.");
PathfinderDemo myApp;
return myApp.run();
}

View File

@ -0,0 +1,8 @@
SRCS = \
src/gen/scenes.cpp \
src/gen/PathfinderDemo/PathfinderDemo.cpp \
src/gen/PrismSceneManager.cpp \
src/gen/SceneDescriptor.cpp \
src/gen/SpawnedSceneBase.cpp \
src/gen/SpawnedSceneHandlers.cpp \
src/gen/SpawnedSceneUserData.cpp \

View File

@ -0,0 +1,19 @@
<manifest
xmlns:ml="magicleap"
ml:package="com.company.pathfinderdemo"
ml:version_code="1"
ml:version_name="1.0">
<application
ml:visible_name="PathfinderDemo"
ml:sdk_version="1.0">
<component
ml:name=".pathfinderdemo.universe"
ml:visible_name="PathfinderDemo"
ml:binary_name="bin/PathfinderDemo"
ml:type="Universe">
<icon
ml:model_folder="assets/icon/model"
ml:portal_folder="assets/icon/portal" />
</component>
</application>
</manifest>

View File

View File

@ -0,0 +1,47 @@
{
"intermediate-storage": {
"path": "cache",
"kind": "in-tree"
},
"project-schema-version": 4,
"types": {
"assets": [
"lap/types/asset/audio",
"lap/types/asset/material",
"lap/types/asset/model",
"lap/types/asset/outline-font",
"lap/types/asset/texture"
],
"files": [
"lap/types/file/bmp",
"lap/types/file/dds",
"lap/types/file/fbx",
"lap/types/file/files",
"lap/types/file/gltf",
"lap/types/file/jpg",
"lap/types/file/kmat",
"lap/types/file/ogg",
"lap/types/file/otf",
"lap/types/file/png",
"lap/types/file/tga",
"lap/types/file/tiff",
"lap/types/file/wav"
]
},
"checkpoint-hash": "c518bac956221c272b076912359b67d645f00f14a76f2f9cc3dee2f71cbe4aa1f702f8fd2d19a0b3b59af6ab227b83fedcbef788077d6757a9b5e691bd0f4398",
"templates": [
"lap/template/converted_texture_from_bmp",
"lap/template/converted_texture_from_tga",
"lap/template/converted_texture_from_tiff",
"lap/template/passthru_audio_from_ogg",
"lap/template/passthru_audio_from_wav",
"lap/template/passthru_material_from_kmat",
"lap/template/passthru_model_from_fbx",
"lap/template/passthru_model_from_gltf",
"lap/template/passthru_outline_font_from_otf",
"lap/template/passthru_texture_from_dds",
"lap/template/passthru_texture_from_jpg",
"lap/template/passthru_texture_from_png"
],
"nodes": {}
}

View File

@ -0,0 +1,3 @@
DATAS = \
scenes/PathfinderDemo.scene.res.xml : assets/scenes/PathfinderDemo.scene.res.xml \
scenes/PathfinderDemo.scene.xml : assets/scenes/PathfinderDemo.scene.xml

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="ASCII"?>
<design:rootNode xmlns:design="http://www.magicleap.com/uidesigner/rcp/document/design" name="root" nodeTypeId="lumin.root" modelId="lumin" version="1.11.1">
<property id="name" value="root"/>
<property id="sceneName" value="PathfinderDemo"/>
<node name="quad1" nodeTypeId="lumin.quad">
<property id="name" value="quad1"/>
<property id="position">
<property id="x" value="0.15"/>
<property id="y" value="-0.15"/>
<property id="z" value="-0.15"/>
</property>
<property id="rotation">
<property id="y" value="180.0"/>
</property>
<property id="scale"/>
<property id="size">
<property id="x" value="0.3"/>
<property id="y" value="0.3"/>
</property>
<property id="texCoords">
<property id="x">
<property id="y" value="1.0"/>
</property>
<property id="y">
<property id="x" value="1.0"/>
<property id="y" value="1.0"/>
</property>
<property id="z">
<property id="x" value="1.0"/>
</property>
<property id="w"/>
</property>
</node>
<node name="quad2" nodeTypeId="lumin.quad">
<property id="name" value="quad2"/>
<property id="position">
<property id="x" value="-0.15"/>
<property id="y" value="-0.15"/>
<property id="z" value="0.15"/>
</property>
<property id="rotation"/>
<property id="scale"/>
<property id="size">
<property id="x" value="0.3"/>
<property id="y" value="0.3"/>
</property>
<property id="texCoords">
<property id="x">
<property id="y" value="1.0"/>
</property>
<property id="y">
<property id="x" value="1.0"/>
<property id="y" value="1.0"/>
</property>
<property id="z">
<property id="x" value="1.0"/>
</property>
<property id="w"/>
</property>
</node>
<node name="quad3" nodeTypeId="lumin.quad">
<property id="name" value="quad3"/>
<property id="position">
<property id="x" value="0.15"/>
<property id="y" value="-0.15"/>
<property id="z" value="0.15"/>
</property>
<property id="rotation">
<property id="y" value="90.0"/>
</property>
<property id="scale"/>
<property id="size">
<property id="x" value="0.3"/>
<property id="y" value="0.3"/>
</property>
<property id="texCoords">
<property id="x">
<property id="y" value="1.0"/>
</property>
<property id="y">
<property id="x" value="1.0"/>
<property id="y" value="1.0"/>
</property>
<property id="z">
<property id="x" value="1.0"/>
</property>
<property id="w"/>
</property>
</node>
<node name="quad4" nodeTypeId="lumin.quad">
<property id="name" value="quad4"/>
<property id="position">
<property id="x" value="-0.15"/>
<property id="y" value="-0.15"/>
<property id="z" value="-0.15"/>
</property>
<property id="rotation">
<property id="y" value="-90.0"/>
</property>
<property id="scale"/>
<property id="size">
<property id="x" value="0.3"/>
<property id="y" value="0.3"/>
</property>
<property id="texCoords">
<property id="x">
<property id="y" value="1.0"/>
</property>
<property id="y">
<property id="x" value="1.0"/>
<property id="y" value="1.0"/>
</property>
<property id="z">
<property id="x" value="1.0"/>
</property>
<property id="w"/>
</property>
</node>
<node name="quad5" nodeTypeId="lumin.quad">
<property id="name" value="quad5"/>
<property id="position">
<property id="x" value="-0.15"/>
<property id="y" value="-0.15"/>
<property id="z" value="-0.15"/>
</property>
<property id="rotation">
<property id="x" value="90.0"/>
</property>
<property id="scale"/>
<property id="size">
<property id="x" value="0.3"/>
<property id="y" value="0.3"/>
</property>
<property id="texCoords">
<property id="x">
<property id="y" value="1.0"/>
</property>
<property id="y">
<property id="x" value="1.0"/>
<property id="y" value="1.0"/>
</property>
<property id="z">
<property id="x" value="1.0"/>
</property>
<property id="w"/>
</property>
</node>
<node name="quad6" nodeTypeId="lumin.quad">
<property id="name" value="quad6"/>
<property id="position">
<property id="x" value="-0.15"/>
<property id="y" value="0.15"/>
<property id="z" value="0.15"/>
</property>
<property id="rotation">
<property id="x" value="270.0"/>
</property>
<property id="scale"/>
<property id="size">
<property id="x" value="0.3"/>
<property id="y" value="0.3"/>
</property>
<property id="texCoords">
<property id="x">
<property id="y" value="1.0"/>
</property>
<property id="y">
<property id="x" value="1.0"/>
<property id="y" value="1.0"/>
</property>
<property id="z">
<property id="x" value="1.0"/>
</property>
<property id="w"/>
</property>
</node>
</design:rootNode>

View File

@ -0,0 +1 @@
<ResourceModel version="1"></ResourceModel>

View File

@ -0,0 +1,9 @@
<ObjectModel name="PathfinderDemo" version="1">
<TransformNode/>
<QuadNode castShadow="false" name="quad1" pos="0.15,-0.15,-0.15" receiveShadow="false" rot="-0,1,-0,-4.371139e-08" shader="MAX" size="0.300000, 0.300000"/>
<QuadNode castShadow="false" name="quad2" pos="-0.15,-0.15,0.15" receiveShadow="false" shader="MAX" size="0.300000, 0.300000"/>
<QuadNode castShadow="false" name="quad3" pos="0.15,-0.15,0.15" receiveShadow="false" rot="0,0.7071068,0,0.7071068" shader="MAX" size="0.300000, 0.300000"/>
<QuadNode castShadow="false" name="quad4" pos="-0.15,-0.15,-0.15" receiveShadow="false" rot="0,-0.7071068,0,0.7071068" shader="MAX" size="0.300000, 0.300000"/>
<QuadNode castShadow="false" name="quad5" pos="-0.15,-0.15,-0.15" receiveShadow="false" rot="0.7071068,0,0,0.7071068" shader="MAX" size="0.300000, 0.300000"/>
<QuadNode castShadow="false" name="quad6" pos="-0.15,0.15,0.15" receiveShadow="false" rot="0.7071068,0,-0,-0.7071068" shader="MAX" size="0.300000, 0.300000"/>
</ObjectModel>

View File

@ -0,0 +1,34 @@
<manifest
xmlns:ml="magicleap"
ml:package="com.mozilla.pathfinder.demo"
ml:version_code="1"
ml:version_name="1.0">
<application
ml:visible_name="Pathfinder Demo"
ml:sdk_version="0.20.0"
ml:min_api_level="4">
<uses-privilege ml:name="WorldReconstruction"/>
<uses-privilege ml:name="LowLatencyLightwear"/>
<uses-privilege ml:name="ControllerPose"/>
<component
ml:name=".universe"
ml:visible_name="PF Landscape Demo"
ml:binary_name="bin/PathfinderLandscapeDemo"
ml:type="Universe">
<icon
ml:model_folder="assets/icon/model"
ml:portal_folder="assets/icon/portal" />
</component>
<component
ml:name=".fullscreen"
ml:visible_name="PF Immersive Demo"
ml:binary_name="bin/PathfinderImmersiveDemo"
ml:type="Fullscreen">
<mime-type ml:name="image/svg"/>
<file-extension ml:name=".svg"/>
<icon
ml:model_folder="assets/icon/model"
ml:portal_folder="assets/icon/portal" />
</component>
</application>
</manifest>

293
demo/magicleap/src/c_api.rs Normal file
View File

@ -0,0 +1,293 @@
// pathfinder/demo/magicleap/src/c_api.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Bindings to the C MagicLeap API
#![allow(dead_code)]
use gl::types::GLuint;
use std::error::Error;
use std::ffi::CStr;
use std::fmt;
#[cfg(not(feature = "mocked"))]
use std::os::raw::c_char;
use std::os::raw::c_void;
// Types from the MagicLeap C API
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct MLHandle(u64);
impl MLHandle {
pub fn as_gl_uint(self) -> GLuint {
self.0 as GLuint
}
}
impl<T> From<*mut T> for MLHandle {
fn from(ptr: *mut T) -> MLHandle {
MLHandle(ptr as u64)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct MLResult(u32);
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsOptions {
pub graphics_flags: u32,
pub color_format: MLSurfaceFormat,
pub depth_format: MLSurfaceFormat,
}
impl Default for MLGraphicsOptions {
fn default() -> MLGraphicsOptions {
MLGraphicsOptions {
graphics_flags: 0,
color_format: MLSurfaceFormat::RGBA8UNormSRGB,
depth_format: MLSurfaceFormat::D32Float,
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsRenderTargetsInfo {
pub min_clip: f32,
pub max_clip: f32,
pub num_virtual_cameras: u32,
pub buffers: [MLGraphicsRenderBufferInfo; ML_BUFFER_COUNT],
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsRenderBufferInfo {
pub color: MLGraphicsRenderTarget,
pub depth: MLGraphicsRenderTarget,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsRenderTarget {
pub width: u32,
pub height: u32,
pub id: MLHandle,
pub format: MLSurfaceFormat,
}
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum MLSurfaceFormat {
Unknown = 0,
RGBA8UNorm,
RGBA8UNormSRGB,
RGB10A2UNorm,
RGBA16Float,
D32Float,
D24NormS8,
D32FloatS8,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsVirtualCameraInfoArray {
pub num_virtual_cameras: u32,
pub color_id: MLHandle,
pub depth_id: MLHandle,
pub viewport: MLRectf,
pub virtual_cameras: [MLGraphicsVirtualCameraInfo; ML_VIRTUAL_CAMERA_COUNT],
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsVirtualCameraInfo {
pub left_half_angle: f32,
pub right_half_angle: f32,
pub top_half_angle: f32,
pub bottom_half_angle: f32,
pub sync_object: MLHandle,
pub projection: MLMat4f,
pub transform: MLTransform,
pub virtual_camera_name: MLGraphicsVirtualCameraName,
}
#[derive(Clone, Copy, Debug)]
#[repr(i32)]
pub enum MLGraphicsVirtualCameraName {
Combined = -1,
Left = 0,
Right,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsFrameParams {
pub near_clip: f32,
pub far_clip: f32,
pub focus_distance: f32,
pub surface_scale: f32,
pub protected_surface: bool,
pub projection_type: MLGraphicsProjectionType,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLSnapshotPtr(*mut c_void);
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLCoordinateFrameUID {
pub data: [u64; 2],
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLHeadTrackingStaticData {
pub coord_frame_head: MLCoordinateFrameUID,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsClipExtentsInfo {
pub virtual_camera_name: MLGraphicsVirtualCameraName,
pub projection: MLMat4f,
pub transform: MLTransform,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLGraphicsClipExtentsInfoArray {
pub num_virtual_cameras: u32,
pub full_extents: MLGraphicsClipExtentsInfo,
pub virtual_camera_extents: [MLGraphicsClipExtentsInfo; ML_VIRTUAL_CAMERA_COUNT],
}
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum MLGraphicsProjectionType {
SignedZ = 0,
ReversedInfiniteZ = 1,
UnsignedZ = 2,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLTransform {
pub rotation: MLQuaternionf,
pub position: MLVec3f,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLVec3f {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLRectf {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLQuaternionf {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MLMat4f {
pub matrix_colmajor: [f32; 16],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum MLLogLevel {
Fatal = 0,
Error = 1,
Warning = 2,
Info = 3,
Debug = 4,
Verbose = 5,
}
// Constants from the MagicLeap C API
pub const ML_RESULT_OK: MLResult = MLResult(0);
pub const ML_RESULT_TIMEOUT: MLResult = MLResult(2);
pub const ML_RESULT_UNSPECIFIED_FAILURE: MLResult = MLResult(4);
pub const ML_HANDLE_INVALID: MLHandle = MLHandle(0xFFFFFFFFFFFFFFFF);
pub const ML_BUFFER_COUNT: usize = 3;
pub const ML_VIRTUAL_CAMERA_COUNT: usize = 2;
// ML error handling
impl fmt::Display for MLResult {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let cmessage = unsafe { CStr::from_ptr(MLGetResultString(*self)) };
let message = cmessage.to_str().or(Err(fmt::Error))?;
formatter.write_str(message)
}
}
impl MLResult {
pub fn ok(self) -> Result<(), MLResult> {
if self == ML_RESULT_OK {
Ok(())
} else {
Err(self)
}
}
pub fn unwrap(self) {
self.ok().unwrap()
}
}
impl Error for MLResult {
}
// Functions from the MagicLeap C API
#[cfg(not(feature = "mocked"))]
extern "C" {
pub fn MLGraphicsCreateClientGL(options: *const MLGraphicsOptions, gl_context: MLHandle, graphics_client : &mut MLHandle) -> MLResult;
pub fn MLGraphicsDestroyClient(graphics_client: *mut MLHandle) -> MLResult;
pub fn MLHeadTrackingCreate(tracker: *mut MLHandle) -> MLResult;
pub fn MLHeadTrackingGetStaticData(head_tracker: MLHandle, data: *mut MLHeadTrackingStaticData) -> MLResult;
pub fn MLPerceptionGetSnapshot(snapshot: *mut MLSnapshotPtr) -> MLResult;
pub fn MLSnapshotGetTransform(snapshot: MLSnapshotPtr, id: *const MLCoordinateFrameUID, transform: *mut MLTransform) -> MLResult;
pub fn MLPerceptionReleaseSnapshot(snapshot: MLSnapshotPtr) -> MLResult;
pub fn MLLifecycleSetReadyIndication() -> MLResult;
pub fn MLGraphicsGetClipExtents(graphics_client: MLHandle, array: *mut MLGraphicsClipExtentsInfoArray) -> MLResult;
pub fn MLGraphicsGetRenderTargets(graphics_client: MLHandle, targets: *mut MLGraphicsRenderTargetsInfo) -> MLResult;
pub fn MLGraphicsInitFrameParams(params: *mut MLGraphicsFrameParams) -> MLResult;
pub fn MLGraphicsBeginFrame(graphics_client: MLHandle, params: *const MLGraphicsFrameParams, frame_handle: *mut MLHandle, virtual_camera_array: *mut MLGraphicsVirtualCameraInfoArray) -> MLResult;
pub fn MLGraphicsEndFrame(graphics_client: MLHandle, frame_handle: MLHandle) -> MLResult;
pub fn MLGraphicsSignalSyncObjectGL(graphics_client: MLHandle, sync_object: MLHandle) -> MLResult;
pub fn MLGetResultString(result_code: MLResult) -> *const c_char;
pub fn MLLoggingLogLevelIsEnabled(lvl: MLLogLevel) -> bool;
pub fn MLLoggingLog(lvl: MLLogLevel, tag: *const c_char, message: *const c_char);
}
#[cfg(feature = "mocked")]
pub use crate::mocked_c_api::*;

View File

@ -0,0 +1,46 @@
// pathfinder/demo/immersive/display.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 std::error::Error;
use std::io;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::ResourceLoader;
pub trait Display: Sized {
type Error: DisplayError;
type Camera: DisplayCamera<Error = Self::Error>;
fn resource_loader(&self) -> &dyn ResourceLoader;
fn gl_version(&self) -> GLVersion;
fn make_current(&mut self) -> Result<(), Self::Error>;
fn running(&self) -> bool;
fn size(&self) -> Point2DI32;
fn begin_frame(&mut self) -> Result<&mut[Self::Camera], Self::Error>;
fn end_frame(&mut self) -> Result<(), Self::Error>;
}
pub trait DisplayCamera {
type Error: DisplayError;
fn bounds(&self) -> RectI32;
fn view(&self) -> Transform3DF32;
fn perspective(&self) -> Perspective;
fn make_current(&mut self) -> Result<(), Self::Error>;
}
pub trait DisplayError: Error + From<usvg::Error> + From<io::Error>{
}

View File

@ -0,0 +1,254 @@
// pathfinder/demo/immersive/glwindow.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 gl;
use glutin::ContextBuilder;
use glutin::ContextError;
use glutin::CreationError;
use glutin::EventsLoop;
use glutin::Event;
use glutin::WindowEvent;
use glutin::GlContext;
use glutin::GlWindow;
use glutin::WindowBuilder;
use glutin::dpi::LogicalSize;
use crate::display::Display;
use crate::display::DisplayCamera;
use crate::display::DisplayError;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::FilesystemResourceLoader;
use pathfinder_gpu::resources::ResourceLoader;
use std::env;
use std::error::Error;
use std::fmt;
use std::f32::consts::FRAC_PI_4;
use std::io;
use std::rc::Rc;
use std::time::Instant;
use usvg;
pub struct GlWindowDisplay {
events_loop: EventsLoop,
gl_window: Rc<GlWindow>,
running: bool,
cameras: Vec<GlWindowCamera>,
resource_loader: FilesystemResourceLoader,
}
pub struct GlWindowCamera {
eye: Eye,
gl_window: Rc<GlWindow>,
start: Instant,
}
enum Eye {
Left,
Right,
}
#[derive(Debug)]
pub enum GlWindowError {
Creation(CreationError),
Context(ContextError),
SVG(usvg::Error),
IO(io::Error),
}
const DEFAULT_EYE_WIDTH: u32 = 1024;
const DEFAULT_EYE_HEIGHT: u32 = 768;
const CAMERA_DISTANCE: f32 = 3.0;
const NEAR_CLIP_PLANE: f32 = 0.01;
const FAR_CLIP_PLANE: f32 = 10.0;
impl Display for GlWindowDisplay {
type Error = GlWindowError;
type Camera = GlWindowCamera;
fn resource_loader(&self) -> &dyn ResourceLoader {
&self.resource_loader
}
fn gl_version(&self) -> GLVersion {
GLVersion::GL3
}
fn make_current(&mut self) -> Result<(), GlWindowError> {
let size = self.size();
unsafe {
self.gl_window.make_current()?;
gl::Viewport(0, 0, size.x(), size.y());
gl::Scissor(0, 0, size.x(), size.y());
gl::Enable(gl::SCISSOR_TEST);
}
self.handle_events();
Ok(())
}
fn begin_frame(&mut self) -> Result<&mut[GlWindowCamera], GlWindowError> {
self.handle_events();
Ok(&mut self.cameras[..])
}
fn end_frame(&mut self) -> Result<(), GlWindowError> {
self.handle_events();
self.gl_window.swap_buffers()?;
self.handle_events();
Ok(())
}
fn running(&self) -> bool {
self.running
}
fn size(&self) -> Point2DI32 {
window_size(&*self.gl_window)
}
}
impl DisplayCamera for GlWindowCamera {
type Error = GlWindowError;
fn make_current(&mut self) -> Result<(), GlWindowError> {
let bounds = self.bounds();
unsafe {
self.gl_window.make_current()?;
gl::Viewport(bounds.origin().x(), bounds.origin().y(), bounds.size().x(), bounds.size().y());
gl::Scissor(bounds.origin().x(), bounds.origin().y(), bounds.size().x(), bounds.size().y());
}
Ok(())
}
fn bounds(&self) -> RectI32 {
let window_size = window_size(&*self.gl_window);
let eye_size = Point2DI32::new(window_size.x()/2, window_size.y());
let origin = match self.eye {
Eye::Left => Point2DI32::new(0, 0),
Eye::Right => Point2DI32::new(eye_size.x(), 0),
};
RectI32::new(origin, eye_size)
}
fn perspective(&self) -> Perspective {
// TODO: add eye offsets
let bounds = self.bounds();
let aspect = bounds.size().x() as f32 / bounds.size().y() as f32;
let transform = Transform3DF32::from_perspective(FRAC_PI_4, aspect, NEAR_CLIP_PLANE, FAR_CLIP_PLANE);
Perspective::new(&transform, bounds.size())
}
fn view(&self) -> Transform3DF32 {
let duration = Instant::now() - self.start;
let rotation = duration.as_millis() as f32 / 1000.0;
Transform3DF32::from_rotation(rotation, 0.0, 0.0)
.pre_mul(&Transform3DF32::from_translation(0.0, 0.0, -CAMERA_DISTANCE))
}
}
impl GlWindowDisplay {
pub fn new() -> Result<GlWindowDisplay, GlWindowError> {
let resource_loader = FilesystemResourceLoader::locate();
let size = default_window_size();
let events_loop = glutin::EventsLoop::new();
let window = WindowBuilder::new()
.with_title("Pathfinder Immersive Demo")
.with_dimensions(size);
let context = ContextBuilder::new()
.with_vsync(true);
let gl_window = Rc::new(glutin::GlWindow::new(window, context, &events_loop)?);
let start = Instant::now();
let cameras = vec![
GlWindowCamera { gl_window: gl_window.clone(), start, eye: Eye::Left },
GlWindowCamera { gl_window: gl_window.clone(), start, eye: Eye::Right },
];
gl::load_with(|name| gl_window.get_proc_address(name) as *const _);
Ok(GlWindowDisplay {
resource_loader,
events_loop,
gl_window,
cameras,
running: true,
})
}
fn handle_events(&mut self) {
let running = &mut self.running;
self.events_loop.poll_events(|event| {
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } |
Event::WindowEvent { event: WindowEvent::Destroyed, .. } => *running = false,
_ => (),
}
})
}
}
fn window_size(gl_window: &GlWindow) -> Point2DI32 {
let logical = gl_window
.get_inner_size()
.unwrap_or_else(|| default_window_size());
let hidpi = gl_window.get_hidpi_factor();
let physical = logical.to_physical(hidpi);
Point2DI32::new(physical.width as i32, physical.height as i32)
}
fn default_window_size() -> LogicalSize {
LogicalSize::new((DEFAULT_EYE_WIDTH * 2) as f64, DEFAULT_EYE_HEIGHT as f64)
}
impl fmt::Display for GlWindowError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
GlWindowError::Creation(ref err) => err.fmt(formatter),
GlWindowError::Context(ref err) => err.fmt(formatter),
GlWindowError::SVG(ref err) => err.fmt(formatter),
GlWindowError::IO(ref err) => err.fmt(formatter),
}
}
}
impl Error for GlWindowError {
}
impl From<CreationError> for GlWindowError {
fn from(err: CreationError) -> GlWindowError {
GlWindowError::Creation(err)
}
}
impl From<ContextError> for GlWindowError {
fn from(err: ContextError) -> GlWindowError {
GlWindowError::Context(err)
}
}
impl From<usvg::Error> for GlWindowError {
fn from(err: usvg::Error) -> GlWindowError {
GlWindowError::SVG(err)
}
}
impl From<io::Error> for GlWindowError {
fn from(err: io::Error) -> GlWindowError {
GlWindowError::IO(err)
}
}
impl DisplayError for GlWindowError {
}

View File

@ -0,0 +1,129 @@
#![allow(unused_imports)]
#![allow(dead_code)]
use crate::display::Display;
use crate::display::DisplayCamera;
use log::debug;
use pathfinder_demo::Options;
use pathfinder_demo::BuildOptions;
use pathfinder_demo::SceneThreadProxy;
use pathfinder_demo::MainToSceneMsg;
use pathfinder_demo::SceneToMainMsg;
use pathfinder_demo::Camera;
use pathfinder_demo::CameraTransform3D;
use pathfinder_gl::GLDevice;
use pathfinder_renderer::gpu::renderer::Renderer;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::point::Point3DF32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_gpu::Device;
use pathfinder_simd::default::F32x4;
use pathfinder_svg::BuiltSVG;
use pathfinder_renderer::scene::Scene;
use pathfinder_renderer::builder::RenderTransform;
use std::error::Error;
use std::fmt;
use std::path::Path;
use std::path::PathBuf;
use std::time::Instant;
use usvg;
pub struct ImmersiveDemo<D> {
display: D,
renderer: Renderer<GLDevice>,
scene_thread_proxy: SceneThreadProxy,
svg_size: Point2DF32,
svg_to_world: Option<Transform3DF32>,
}
static DEFAULT_SVG_VIRTUAL_PATH: &'static str = "svg/Ghostscript_Tiger.svg";
// SVG dimensions in metres
const MAX_SVG_HEIGHT: f32 = 1.0;
const MAX_SVG_WIDTH: f32 = 1.0;
const DEFAULT_SVG_DISTANCE: f32 = 1.5;
impl<D: Display> ImmersiveDemo<D> {
pub fn new(mut display: D) -> Result<Self, D::Error> {
display.make_current()?;
let resources = display.resource_loader();
let options = Options::get();
let svg_data = resources.slurp(DEFAULT_SVG_VIRTUAL_PATH)?;
let tree = usvg::Tree::from_data(&svg_data[..], &usvg::Options::default())?;
let svg = BuiltSVG::from_tree(tree);
let svg_size = svg.scene.view_box.size();
let scene_thread_proxy = SceneThreadProxy::new(svg.scene, options);
let _ = scene_thread_proxy.sender.send(MainToSceneMsg::SetDrawableSize(display.size()));
let device = GLDevice::new(display.gl_version());
let viewport = RectI32::new(Point2DI32::new(0, 0), display.size());
let renderer = Renderer::new(device, resources, viewport, display.size());
Ok(ImmersiveDemo {
display,
renderer,
scene_thread_proxy,
svg_size,
svg_to_world: None,
})
}
pub fn running(&self) -> bool {
self.display.running()
}
pub fn render_scene(&mut self) -> Result<(), D::Error> {
self.display.make_current()?;
let cameras = self.display.begin_frame()?;
debug!("PF rendering a frame");
let start = Instant::now();
let svg_size = self.svg_size;
let svg_to_world = self.svg_to_world.get_or_insert_with(|| {
let view: Transform3DF32 = cameras[0].view();
let svg_to_world_scale = f32::max(MAX_SVG_WIDTH / svg_size.x(), MAX_SVG_HEIGHT / svg_size.y());
let svg_width = svg_size.x() * svg_to_world_scale;
let svg_height = svg_size.y() * svg_to_world_scale;
Transform3DF32::from_uniform_scale(svg_to_world_scale)
.pre_mul(&Transform3DF32::from_translation(-svg_width / 2.0, -svg_height / 2.0, -DEFAULT_SVG_DISTANCE))
.pre_mul(&Transform3DF32::from_scale(1.0, -1.0, 1.0))
.pre_mul(&view.inverse())
});
let render_transforms = cameras.iter()
.map(|camera| RenderTransform::Perspective(
camera.perspective()
.post_mul(&camera.view())
.post_mul(&svg_to_world)
)).collect();
let msg = MainToSceneMsg::Build(BuildOptions {
render_transforms: render_transforms,
stem_darkening_font_size: None,
});
let _ = self.scene_thread_proxy.sender.send(msg);
if let Ok(reply) = self.scene_thread_proxy.receiver.recv() {
for (camera, scene) in cameras.iter_mut().zip(reply.render_scenes) {
debug!("PF rendering eye after {}ms", (Instant::now() - start).as_millis());
camera.make_current()?;
let bounds = camera.bounds();
let background = F32x4::new(0.0, 0.0, 0.0, 1.0);
self.renderer.device.clear(Some(background), Some(1.0), Some(0));
self.renderer.enable_depth();
self.renderer.set_viewport(bounds);
self.renderer.render_scene(&scene.built_scene);
}
}
debug!("PF rendered frame after {}ms", (Instant::now() - start).as_millis());
self.display.end_frame()?;
Ok(())
}
}

View File

@ -0,0 +1,333 @@
// pathfinder/demo/magicleap/src/landscape.cpp
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 launcher for the ML1 pathfinder demo
#include <landscape.h>
#include <lumin/node/RootNode.h>
#include <lumin/node/QuadNode.h>
#include <lumin/resource/PlanarResource.h>
#include <lumin/ui/node/UiPanel.h>
#include <lumin/ui/Cursor.h>
#include <lumin/input/Raycast.h>
#include <lumin/event/ControlPose6DofInputEventData.h>
#include <lumin/event/GestureInputEventData.h>
#include <lumin/event/RayCastEventData.h>
#include <ml_dispatch.h>
#include <ml_logging.h>
#include <scenes.h>
#include <PrismSceneManager.h>
int main(int argc, char **argv)
{
ML_LOG(Debug, "PathfinderDemo Starting.");
PathfinderDemo myApp;
return myApp.run();
}
const char* QUAD_NAMES[NUM_QUADS] = {
"quad1",
"quad2",
"quad3",
"quad4",
"quad5",
"quad6",
};
const char* SVG_NAMES[NUM_QUADS] = {
"svg/Ghostscript_Tiger.svg",
"svg/nba-notext.svg",
"svg/julius-caesar-with-bg.svg",
"svg/magicleap-quickstart-p03.svg",
"svg/pathfinder_logo.svg",
"svg/pathfinder-magicleap-demo.svg",
};
PathfinderDemo::PathfinderDemo() {
ML_LOG(Debug, "PathfinderDemo Constructor.");
// Place your constructor implementation here.
}
PathfinderDemo::~PathfinderDemo() {
ML_LOG(Debug, "PathfinderDemo Destructor.");
// Place your destructor implementation here.
}
const glm::vec3 PathfinderDemo::getInitialPrismSize() const {
return glm::vec3(0.4f, 0.4f, 0.4f);
}
void PathfinderDemo::createInitialPrism() {
prism_ = requestNewPrism(getInitialPrismSize());
if (!prism_) {
ML_LOG(Error, "PathfinderDemo Error creating default prism.");
abort();
}
prismSceneManager_ = new PrismSceneManager(prism_);
}
int PathfinderDemo::init() {
ML_LOG(Debug, "PathfinderDemo Initializing.");
createInitialPrism();
lumin::ui::Cursor::SetEnabled(prism_, false);
spawnInitialScenes();
// Place your initialization here.
if (checkPrivilege(lumin::PrivilegeId::kControllerPose) != lumin::PrivilegeResult::kGranted) {
ML_LOG(Error, "Pathfinder Failed to get controller access");
abort();
return 1;
}
// Get the root node of the prism
lumin::RootNode* root_node = prism_->getRootNode();
if (!root_node) {
ML_LOG(Error, "Pathfinder Failed to get root node");
abort();
return 1;
}
for (int i=0; i<NUM_QUADS; i++) {
// Get the quad
lumin::QuadNode* quad_node = lumin::QuadNode::CastFrom(prism_->findNode(QUAD_NAMES[i], root_node));
if (!quad_node) {
ML_LOG(Error, "Pathfinder Failed to get quad node %d.", i);
abort();
return 1;
}
quad_nodes_[i] = quad_node->getNodeId();
// Create the EGL surface for it to draw to
lumin::ResourceIDType plane_id = prism_->createPlanarEGLResourceId();
if (!plane_id) {
ML_LOG(Error, "Pathfinder Failed to create EGL resource");
abort();
return 1;
}
quad_node->setRenderResource(plane_id);
renderNode(quad_node->getNodeId());
}
return 0;
}
void PathfinderDemo::renderNode(lumin::NodeIDType node_id) {
if (node_id == lumin::INVALID_NODE_ID) { return; }
lumin::QuadNode* quad_node = static_cast<lumin::QuadNode*>(prism_->getNode(node_id));
if (!quad_node) {
ML_LOG(Error, "Pathfinder Failed to get quad node");
return;
}
lumin::PlanarResource* plane = const_cast<lumin::PlanarResource*>(static_cast<const lumin::PlanarResource*>(quad_node->getRenderResource()));
if (!plane) {
ML_LOG(Error, "Pathfinder Failed to get plane");
return;
}
// Get the EGL context, surface and display.
uint32_t width = plane->getWidth();
uint32_t height = plane->getHeight();
EGLContext ctx = plane->getEGLContext();
EGLSurface surf = plane->getEGLSurface();
EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
// Initialize pathfinder
if (!pathfinder_) {
ML_LOG(Info, "Pathfinder initializing");
pathfinder_ = magicleap_pathfinder_init();
ML_LOG(Info, "Pathfinder initialized");
}
// Get the SVG filename
const char* svg_filename;
for (int i=0; i<NUM_QUADS; i++) {
if (quad_nodes_[i] == node_id) {
svg_filename = SVG_NAMES[i];
break;
}
}
if (!svg_filename) {
ML_LOG(Error, "Pathfinder Failed to get SVG filename");
return;
}
// Set the brightness
float brightness;
if (node_id == highlighted_node_) {
brightness = 0.6;
} else {
brightness = 0.4;
}
// Render the SVG
MagicLeapPathfinderRenderOptions options = {
dpy,
surf,
{ brightness, brightness, brightness, 1.0 },
{ 0, 0, width, height },
svg_filename,
};
eglMakeCurrent(dpy, surf, surf, ctx);
magicleap_pathfinder_render(pathfinder_, &options);
eglSwapBuffers(dpy, surf);
}
int PathfinderDemo::deInit() {
ML_LOG(Debug, "PathfinderDemo Deinitializing.");
// Place your deinitialization here.
magicleap_pathfinder_deinit(pathfinder_);
pathfinder_ = nullptr;
return 0;
}
void PathfinderDemo::spawnInitialScenes() {
// Iterate over all the exported scenes
for (auto& exportedSceneEntry : scenes::externalScenes ) {
// If this scene was marked to be instanced at app initialization, do it
const SceneDescriptor &sd = exportedSceneEntry.second;
if (sd.getInitiallySpawned()) {
lumin::Node* const spawnedRoot = prismSceneManager_->spawn(sd);
if (spawnedRoot) {
if (!prism_->getRootNode()->addChild(spawnedRoot)) {
ML_LOG(Error, "PathfinderDemo Failed to add spawnedRoot to the prism root node");
abort();
}
}
}
}
}
bool PathfinderDemo::updateLoop(float fDelta) {
// Place your update here.
if (focus_node_ != highlighted_node_) {
lumin::NodeIDType old_highlight = highlighted_node_;
highlighted_node_ = focus_node_;
renderNode(old_highlight);
renderNode(highlighted_node_);
}
// Return true for your app to continue running, false to terminate the app.
return true;
}
bool PathfinderDemo::eventListener(lumin::ServerEvent* event) {
// Place your event handling here.
lumin::ServerEventTypeValue typ = event->getServerEventTypeValue();
if (typ == lumin::ControlPose6DofInputEventData::GetServerEventTypeValue()) {
requestWorldRayCast(getHeadposeWorldPosition(), getHeadposeWorldForwardVector(), 0);
return false;
} else if (typ == lumin::RayCastEventData::GetServerEventTypeValue()) {
lumin::RayCastEventData* raycast_event = static_cast<lumin::RayCastEventData*>(event);
std::shared_ptr<lumin::RaycastResult> raycast_result = raycast_event->getHitData();
switch (raycast_result->getType()) {
case lumin::RaycastResultType::kQuadNode: {
std::shared_ptr<lumin::RaycastQuadNodeResult> quad_result = std::static_pointer_cast<lumin::RaycastQuadNodeResult>(raycast_result);
focus_node_ = quad_result->getNodeId();
return false;
}
default: {
focus_node_ = lumin::INVALID_NODE_ID;
return false;
}
}
} else if (typ == lumin::GestureInputEventData::GetServerEventTypeValue()) {
lumin::GestureInputEventData* gesture_event = static_cast<lumin::GestureInputEventData*>(event);
switch (gesture_event->getGesture()) {
case lumin::input::GestureType::TriggerClick: {
return onClick();
}
default: {
return false;
}
}
} else {
return false;
}
}
bool PathfinderDemo::onClick() {
lumin::RootNode* root_node = prism_->getRootNode();
for (int i=0; i<NUM_QUADS; i++) {
lumin::Node* node = prism_->findNode(QUAD_NAMES[i], root_node);
if (node->getNodeId() == focus_node_) {
dispatch(SVG_NAMES[i]);
return true;
}
}
return false;
}
void PathfinderDemo::dispatch(const char* svg_filename) {
ML_LOG(Info, "Dispatching %s", svg_filename);
MLDispatchPacket* dispatcher;
if (MLResult_Ok != MLDispatchAllocateEmptyPacket(&dispatcher)) {
ML_LOG(Error, "Failed to allocate dispatcher");
return;
}
if (MLResult_Ok != MLDispatchAllocateFileInfoList(dispatcher, 1)) {
ML_LOG(Error, "Failed to allocate file info list");
return;
}
MLFileInfo* file_info;
if (MLResult_Ok != MLDispatchGetFileInfoByIndex(dispatcher, 0, &file_info)) {
ML_LOG(Error, "Failed to get file info");
return;
}
if (MLResult_Ok != MLFileInfoSetFileName(file_info, svg_filename)) {
ML_LOG(Error, "Failed to set filename");
return;
}
if (MLResult_Ok != MLFileInfoSetMimeType(file_info, "image/svg")) {
ML_LOG(Error, "Failed to set mime type");
return;
}
if (MLResult_Ok != MLDispatchAddFileInfo(dispatcher, file_info)) {
ML_LOG(Error, "Failed to add file info");
return;
}
MLResult result = MLDispatchTryOpenApplication(dispatcher);
if (MLResult_Ok != result) {
ML_LOG(Error, "Failed to dispatch: %s", MLDispatchGetResultString(result));
return;
}
// https://forum.magicleap.com/hc/en-us/community/posts/360043198492-Calling-MLDispatchReleaseFileInfoList-causes-a-dynamic-link-error
// if (MLResult_Ok != MLDispatchReleaseFileInfoList(dispatcher, false)) {
// ML_LOG(Error, "Failed to deallocate file info list");
// return;
// }
if (MLResult_Ok != MLDispatchReleasePacket(&dispatcher, false, false)) {
ML_LOG(Error, "Failed to deallocate dispatcher");
return;
}
}
extern "C" void init_scene_thread(uint64_t id) {}

View File

@ -0,0 +1,137 @@
// pathfinder/demo/magicleap/src/landscape.h
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 launcher for the ML1 pathfinder demo
// Based on code generated by the Magic Leap Lumin Runtime Editor.
// Original copyright:
// Copyright (c) 2018 Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Creator Agreement, located
// here: https://id.magicleap.com/creator-terms
#include <EGL/egl.h>
#include <lumin/LandscapeApp.h>
#include <lumin/Prism.h>
#include <lumin/event/ServerEvent.h>
#include <SceneDescriptor.h>
#include <PrismSceneManager.h>
const uint32_t NUM_QUADS = 6;
/**
* PathfinderDemo Landscape Application
*/
class PathfinderDemo : public lumin::LandscapeApp {
public:
/**
* Constructs the Landscape Application.
*/
PathfinderDemo();
/**
* Destroys the Landscape Application.
*/
virtual ~PathfinderDemo();
/**
* Disallows the copy constructor.
*/
PathfinderDemo(const PathfinderDemo&) = delete;
/**
* Disallows the move constructor.
*/
PathfinderDemo(PathfinderDemo&&) = delete;
/**
* Disallows the copy assignment operator.
*/
PathfinderDemo& operator=(const PathfinderDemo&) = delete;
/**
* Disallows the move assignment operator.
*/
PathfinderDemo& operator=(PathfinderDemo&&) = delete;
protected:
/**
* Initializes the Landscape Application.
* @return - 0 on success, error code on failure.
*/
int init() override;
/**
* Deinitializes the Landscape Application.
* @return - 0 on success, error code on failure.
*/
int deInit() override;
/**
* Returns the initial size of the Prism
* Used in createPrism().
*/
const glm::vec3 getInitialPrismSize() const;
/**
* Creates the prism, updates the private variable prism_ with the created prism.
*/
void createInitialPrism();
/**
* Initializes and creates the scene of all scenes marked as initially instanced
*/
void spawnInitialScenes();
/**
* Respond to a click
*/
bool onClick();
/**
* Dispatch an SVG file to the immersive app
*/
void dispatch(const char* svg_filename);
/**
* Render a node in the scene
*/
void renderNode(lumin::NodeIDType node_id);
/**
* Run application login
*/
virtual bool updateLoop(float fDelta) override;
/**
* Handle events from the server
*/
virtual bool eventListener(lumin::ServerEvent* event) override;
private:
lumin::Prism* prism_ = nullptr; // represents the bounded space where the App renders.
PrismSceneManager* prismSceneManager_ = nullptr;
void* pathfinder_ = nullptr;
lumin::NodeIDType quad_nodes_[NUM_QUADS] = {lumin::INVALID_NODE_ID};
lumin::NodeIDType highlighted_node_ = lumin::INVALID_NODE_ID;
lumin::NodeIDType focus_node_ = lumin::INVALID_NODE_ID;
};
typedef struct MagicLeapPathfinderRenderOptions {
EGLDisplay display;
EGLSurface surface;
float bg_color[4];
uint32_t viewport[4];
const char* svg_filename;
} MagicLeapPathfinderRenderOptions;
extern "C" void* magicleap_pathfinder_init();
extern "C" void magicleap_pathfinder_render(void*, MagicLeapPathfinderRenderOptions*);
extern "C" void magicleap_pathfinder_deinit(void*);

248
demo/magicleap/src/lib.rs Normal file
View File

@ -0,0 +1,248 @@
// pathfinder/demo/magicleap/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 demo app for Pathfinder on ML1.
use crate::magicleap::MagicLeapLogger;
use crate::magicleap::MagicLeapWindow;
use egl;
use egl::EGLContext;
use egl::EGLDisplay;
use egl::EGLSurface;
use gl::types::GLuint;
use log::info;
use pathfinder_demo::DemoApp;
use pathfinder_demo::Options;
use pathfinder_demo::UIVisibility;
use pathfinder_demo::BackgroundColor;
use pathfinder_demo::Mode;
use pathfinder_demo::window::Event;
use pathfinder_demo::window::SVGPath;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_gl::GLDevice;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::Device;
use pathfinder_gpu::resources::FilesystemResourceLoader;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_renderer::builder::RenderOptions;
use pathfinder_renderer::builder::RenderTransform;
use pathfinder_renderer::builder::SceneBuilder;
use pathfinder_renderer::gpu::renderer::Renderer;
use pathfinder_renderer::gpu::renderer::DestFramebuffer;
use pathfinder_simd::default::F32x4;
use pathfinder_svg::BuiltSVG;
use std::collections::HashMap;
use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::raw::c_void;
use usvg::Options as UsvgOptions;
use usvg::Tree;
mod c_api;
mod magicleap;
#[cfg(feature = "mocked")]
mod mocked_c_api;
struct ImmersiveApp {
sender: crossbeam_channel::Sender<Event>,
receiver: crossbeam_channel::Receiver<Event>,
demo: DemoApp<MagicLeapWindow>,
}
#[no_mangle]
pub extern "C" fn magicleap_pathfinder_demo_init(egl_display: EGLDisplay, egl_context: EGLContext) -> *mut c_void {
unsafe { c_api::MLLoggingLog(c_api::MLLogLevel::Info,
b"Pathfinder Demo\0".as_ptr() as *const _,
b"Initializing\0".as_ptr() as *const _) };
let tag = CString::new("Pathfinder Demo").unwrap();
let level = log::LevelFilter::Warn;
let logger = MagicLeapLogger::new(tag, level);
log::set_boxed_logger(Box::new(logger)).unwrap();
log::set_max_level(level);
info!("Initialized logging");
let window = MagicLeapWindow::new(egl_display, egl_context);
let window_size = window.size();
let mut options = Options::default();
options.ui = UIVisibility::None;
options.background_color = BackgroundColor::Transparent;
options.mode = Mode::VR;
options.jobs = Some(3);
let demo = DemoApp::new(window, window_size, options);
info!("Initialized app");
let (sender, receiver) = crossbeam_channel::unbounded();
Box::into_raw(Box::new(ImmersiveApp { sender, receiver, demo })) as *mut c_void
}
#[no_mangle]
pub unsafe extern "C" fn magicleap_pathfinder_demo_run(app: *mut c_void) {
let app = app as *mut ImmersiveApp;
if let Some(app) = app.as_mut() {
while app.demo.window.running() {
let mut events = Vec::new();
while let Some(event) = app.demo.window.try_get_event() {
events.push(event);
}
while let Ok(event) = app.receiver.try_recv() {
events.push(event);
}
let scene_count = app.demo.prepare_frame(events);
for scene_index in 0..scene_count {
app.demo.draw_scene(scene_index);
}
app.demo.finish_drawing_frame();
}
}
}
#[no_mangle]
pub unsafe extern "C" fn magicleap_pathfinder_demo_load(app: *mut c_void, svg_filename: *const c_char) {
let app = app as *mut ImmersiveApp;
if let Some(app) = app.as_mut() {
let svg_filename = CStr::from_ptr(svg_filename).to_string_lossy().into_owned();
info!("Loading {}.", svg_filename);
let _ = app.sender.send(Event::OpenSVG(SVGPath::Resource(svg_filename)));
}
}
struct MagicLeapPathfinder {
renderers: HashMap<(EGLSurface, EGLDisplay), Renderer<GLDevice>>,
svgs: HashMap<String, BuiltSVG>,
resources: FilesystemResourceLoader,
}
#[repr(C)]
pub struct MagicLeapPathfinderRenderOptions {
display: EGLDisplay,
surface: EGLSurface,
bg_color: [f32; 4],
viewport: [u32; 4],
svg_filename: *const c_char,
}
#[no_mangle]
pub extern "C" fn magicleap_pathfinder_init() -> *mut c_void {
unsafe { c_api::MLLoggingLog(c_api::MLLogLevel::Info,
b"Pathfinder Demo\0".as_ptr() as *const _,
b"Initializing\0".as_ptr() as *const _) };
let tag = CString::new("Pathfinder Demo").unwrap();
let level = log::LevelFilter::Info;
let logger = MagicLeapLogger::new(tag, level);
log::set_boxed_logger(Box::new(logger)).unwrap();
log::set_max_level(level);
info!("Initialized logging");
gl::load_with(|s| egl::get_proc_address(s) as *const c_void);
info!("Initialized gl");
let pf = MagicLeapPathfinder {
renderers: HashMap::new(),
svgs: HashMap::new(),
resources: FilesystemResourceLoader::locate(),
};
info!("Initialized pf");
Box::into_raw(Box::new(pf)) as *mut c_void
}
#[no_mangle]
pub unsafe extern "C" fn magicleap_pathfinder_render(pf: *mut c_void, options: *const MagicLeapPathfinderRenderOptions) {
let pf = pf as *mut MagicLeapPathfinder;
if let (Some(pf), Some(options)) = (pf.as_mut(), options.as_ref()) {
let resources = &pf.resources;
let svg_filename = CStr::from_ptr(options.svg_filename).to_string_lossy().into_owned();
let svg = pf.svgs.entry(svg_filename).or_insert_with(|| {
let svg_filename = CStr::from_ptr(options.svg_filename).to_string_lossy();
let data = resources.slurp(&*svg_filename).unwrap();
let tree = Tree::from_data(&data, &UsvgOptions::default()).unwrap();
BuiltSVG::from_tree(tree)
});
let mut width = 0;
let mut height = 0;
egl::query_surface(options.display, options.surface, egl::EGL_WIDTH, &mut width);
egl::query_surface(options.display, options.surface, egl::EGL_HEIGHT, &mut height);
let size = Point2DI32::new(width, height);
let viewport_origin = Point2DI32::new(options.viewport[0] as i32, options.viewport[1] as i32);
let viewport_size = Point2DI32::new(options.viewport[2] as i32, options.viewport[3] as i32);
let viewport = RectI32::new(viewport_origin, viewport_size);
let dest_framebuffer = DestFramebuffer::Default { viewport, window_size: size };
let bg_color = F32x4::new(options.bg_color[0], options.bg_color[1], options.bg_color[2], options.bg_color[3]);
let renderer = pf.renderers.entry((options.display, options.surface)).or_insert_with(|| {
let mut fbo = 0;
gl::GetIntegerv(gl::DRAW_FRAMEBUFFER_BINDING, &mut fbo);
let device = GLDevice::new(GLVersion::GLES3, fbo as GLuint);
let dest_framebuffer = DestFramebuffer::Default { viewport, window_size: size };
Renderer::new(device, resources, dest_framebuffer)
});
renderer.set_main_framebuffer_size(size);
renderer.set_dest_framebuffer(dest_framebuffer);
renderer.device.bind_default_framebuffer(viewport);
renderer.device.clear(Some(bg_color), Some(1.0), Some(0));
renderer.disable_depth();
svg.scene.view_box = viewport.to_f32();
let scale = i32::min(viewport_size.x(), viewport_size.y()) as f32 /
f32::max(svg.scene.bounds.size().x(), svg.scene.bounds.size().y());
let transform = Transform2DF32::from_translation(&svg.scene.bounds.size().scale(-0.5))
.post_mul(&Transform2DF32::from_scale(&Point2DF32::splat(scale)))
.post_mul(&Transform2DF32::from_translation(&viewport_size.to_f32().scale(0.5)));
let render_options = RenderOptions {
transform: RenderTransform::Transform2D(transform),
dilation: Point2DF32::default(),
barrel_distortion: None,
subpixel_aa_enabled: false,
};
let built_options = render_options.prepare(svg.scene.bounds);
let (command_sender, command_receiver) = crossbeam_channel::unbounded();
let command_sender_clone = command_sender.clone();
SceneBuilder::new(&svg.scene, &built_options, Box::new(move |command| { let _ = command_sender.send(Some(command)); }))
.build_sequentially();
let _ = command_sender_clone.send(None);
renderer.begin_scene(&svg.scene.build_descriptor(&built_options));
while let Ok(Some(command)) = command_receiver.recv() {
renderer.render_command(&command);
}
renderer.end_scene();
}
}
#[no_mangle]
pub unsafe extern "C" fn magicleap_pathfinder_deinit(pf: *mut c_void) {
Box::from_raw(pf as *mut MagicLeapPathfinder);
}

View File

@ -0,0 +1,406 @@
// pathfinder/demo/magicleap/src/magicleap.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::c_api::MLGraphicsBeginFrame;
use crate::c_api::MLGraphicsCreateClientGL;
use crate::c_api::MLGraphicsDestroyClient;
use crate::c_api::MLGraphicsEndFrame;
use crate::c_api::MLGraphicsGetRenderTargets;
use crate::c_api::MLGraphicsInitFrameParams;
use crate::c_api::MLGraphicsOptions;
use crate::c_api::MLGraphicsSignalSyncObjectGL;
use crate::c_api::MLGraphicsVirtualCameraInfoArray;
use crate::c_api::MLHandle;
use crate::c_api::MLHeadTrackingCreate;
use crate::c_api::MLLifecycleSetReadyIndication;
use crate::c_api::MLLogLevel;
use crate::c_api::MLLoggingLog;
use crate::c_api::MLMat4f;
use crate::c_api::MLQuaternionf;
use crate::c_api::MLRectf;
use crate::c_api::MLTransform;
use crate::c_api::MLVec3f;
use crate::c_api::ML_HANDLE_INVALID;
use crate::c_api::ML_RESULT_TIMEOUT;
use crate::c_api::ML_VIRTUAL_CAMERA_COUNT;
use egl;
use egl::EGL_NO_SURFACE;
use egl::EGLContext;
use egl::EGLDisplay;
use gl;
use gl::types::GLuint;
use log;
use log::debug;
use log::info;
use pathfinder_demo::window::CameraTransform;
use pathfinder_demo::window::Event;
use pathfinder_demo::window::View;
use pathfinder_demo::window::Window;
use pathfinder_demo::window::WindowSize;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_geometry::distortion::BarrelDistortionCoefficients;
use pathfinder_geometry::util;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::FilesystemResourceLoader;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_simd::default::F32x4;
use rayon::ThreadPoolBuilder;
use smallvec::SmallVec;
use std::ffi::CString;
use std::io::Write;
use std::mem;
use std::os::raw::c_void;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
pub struct MagicLeapWindow {
framebuffer_id: GLuint,
graphics_client: MLHandle,
size: Point2DI32,
virtual_camera_array: MLGraphicsVirtualCameraInfoArray,
initial_camera_transform: Option<Transform3DF32>,
frame_handle: MLHandle,
resource_loader: FilesystemResourceLoader,
pose_event: Option<Vec<CameraTransform>>,
running: bool,
in_frame: bool,
}
impl Window for MagicLeapWindow {
fn resource_loader(&self) -> &dyn ResourceLoader {
&self.resource_loader
}
fn gl_version(&self) -> GLVersion {
GLVersion::GL3
}
fn gl_default_framebuffer(&self) -> GLuint {
self.framebuffer_id
}
fn adjust_thread_pool_settings(&self, thread_pool_builder: ThreadPoolBuilder) -> ThreadPoolBuilder {
thread_pool_builder.start_handler(|id| unsafe { init_scene_thread(id) })
}
fn mouse_position(&self) -> Point2DI32 {
Point2DI32::new(0, 0)
}
fn create_user_event_id (&self) -> u32 {
0
}
fn push_user_event(_: u32, _: u32) {
}
fn present_open_svg_dialog(&mut self) {
}
fn run_save_dialog(&self, _: &str) -> Result<PathBuf, ()> {
Err(())
}
fn viewport(&self, _view: View) -> RectI32 {
RectI32::new(Point2DI32::default(), self.size)
}
fn barrel_distortion_coefficients(&self) -> BarrelDistortionCoefficients {
BarrelDistortionCoefficients { k0: 0.0, k1: 0.0 }
}
fn make_current(&mut self, view: View) {
self.begin_frame();
let eye = match view {
View::Stereo(eye) if (eye as usize) < ML_VIRTUAL_CAMERA_COUNT => eye as usize,
_ => { debug!("Asked for unexpected view: {:?}", view); 0 }
};
debug!("Making {} current.", eye);
let viewport = self.virtual_camera_array.viewport;
let color_id = self.virtual_camera_array.color_id.as_gl_uint();
let depth_id = self.virtual_camera_array.depth_id.as_gl_uint();
let virtual_camera = self.virtual_camera_array.virtual_cameras[eye];
let layer_id = virtual_camera.virtual_camera_name as i32;
unsafe {
gl::FramebufferTextureLayer(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, color_id, 0, layer_id);
gl::FramebufferTextureLayer(gl::FRAMEBUFFER, gl::DEPTH_ATTACHMENT, depth_id, 0, layer_id);
gl::Viewport(viewport.x as i32, viewport.y as i32, viewport.w as i32, viewport.h as i32);
}
debug!("Made {} current.", eye);
}
fn present(&mut self) {
self.end_frame();
self.begin_frame();
}
}
extern "C" {
fn init_scene_thread(id: usize);
}
fn get_proc_address(s: &str) -> *const c_void {
egl::get_proc_address(s) as *const c_void
}
impl MagicLeapWindow {
pub fn new(egl_display: EGLDisplay, egl_context: EGLContext) -> MagicLeapWindow {
debug!("Creating MagicLeapWindow");
let mut framebuffer_id = 0;
let graphics_options = MLGraphicsOptions::default();
let mut graphics_client = unsafe { mem::zeroed() };
let mut head_tracker = unsafe { mem::zeroed() };
let mut targets = unsafe { mem::zeroed() };
let virtual_camera_array = unsafe { mem::zeroed() };
let handle = MLHandle::from(egl_context);
unsafe {
egl::make_current(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
gl::load_with(get_proc_address);
gl::GenFramebuffers(1, &mut framebuffer_id);
MLGraphicsCreateClientGL(&graphics_options, handle, &mut graphics_client).unwrap();
MLLifecycleSetReadyIndication().unwrap();
MLHeadTrackingCreate(&mut head_tracker).unwrap();
MLGraphicsGetRenderTargets(graphics_client, &mut targets).unwrap();
}
let (max_width, max_height) = targets.buffers.iter().map(|buffer| buffer.color)
.chain(targets.buffers.iter().map(|buffer| buffer.depth))
.map(|target| (target.width as i32, target.height as i32))
.max()
.unwrap_or_default();
let resource_loader = FilesystemResourceLoader::locate();
debug!("Created MagicLeapWindow");
MagicLeapWindow {
framebuffer_id,
graphics_client,
size: Point2DI32::new(max_width, max_height),
frame_handle: ML_HANDLE_INVALID,
virtual_camera_array,
initial_camera_transform: None,
resource_loader,
pose_event: None,
running: true,
in_frame: false,
}
}
pub fn size(&self) -> WindowSize {
WindowSize {
logical_size: self.size,
backing_scale_factor: 1.0,
}
}
pub fn running(&self) -> bool {
self.running
}
pub fn try_get_event(&mut self) -> Option<Event> {
self.pose_event.take().map(Event::CameraTransforms)
}
fn begin_frame(&mut self) {
if !self.in_frame {
debug!("PF beginning frame");
let mut params = unsafe { mem::zeroed() };
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer_id);
MLGraphicsInitFrameParams(&mut params).unwrap();
let mut result = MLGraphicsBeginFrame(self.graphics_client, &params, &mut self.frame_handle, &mut self.virtual_camera_array);
if result == ML_RESULT_TIMEOUT {
info!("PF frame timeout");
let mut sleep = Duration::from_millis(1);
let max_sleep = Duration::from_secs(5);
while result == ML_RESULT_TIMEOUT {
sleep = (sleep * 2).min(max_sleep);
info!("PF exponential backoff {}ms", sleep.as_millis());
thread::sleep(sleep);
result = MLGraphicsBeginFrame(self.graphics_client, &params, &mut self.frame_handle, &mut self.virtual_camera_array);
}
info!("PF frame finished timeout");
}
result.unwrap();
}
let virtual_camera_array = &self.virtual_camera_array;
let initial_camera = self.initial_camera_transform.get_or_insert_with(|| {
let initial_offset = Transform3DF32::from_translation(0.0, 0.0, 1.0);
let mut camera = virtual_camera_array.virtual_cameras[0].transform;
for i in 1..virtual_camera_array.num_virtual_cameras {
let next = virtual_camera_array.virtual_cameras[i as usize].transform;
camera = camera.lerp(next, 1.0 / (i as f32 + 1.0));
}
Transform3DF32::from(camera).post_mul(&initial_offset)
});
let camera_transforms = (0..virtual_camera_array.num_virtual_cameras)
.map(|i| {
let camera = &virtual_camera_array.virtual_cameras[i as usize];
let projection = Transform3DF32::from(camera.projection);
let size = RectI32::from(virtual_camera_array.viewport).size();
let perspective = Perspective::new(&projection, size);
let view = Transform3DF32::from(camera.transform).inverse().post_mul(initial_camera);
CameraTransform { perspective, view }
})
.collect();
self.in_frame = true;
self.pose_event = Some(camera_transforms);
debug!("PF begun frame");
}
}
fn end_frame(&mut self) {
if self.in_frame {
debug!("PF ending frame");
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
for i in 0..self.virtual_camera_array.num_virtual_cameras {
let virtual_camera = &self.virtual_camera_array.virtual_cameras[i as usize];
MLGraphicsSignalSyncObjectGL(self.graphics_client, virtual_camera.sync_object).unwrap();
}
MLGraphicsEndFrame(self.graphics_client, self.frame_handle).unwrap();
}
self.in_frame = false;
debug!("PF ended frame");
}
}
}
impl Drop for MagicLeapWindow {
fn drop(&mut self) {
self.end_frame();
unsafe {
gl::DeleteFramebuffers(1, &self.framebuffer_id);
MLGraphicsDestroyClient(&mut self.graphics_client);
}
}
}
// Logging
pub struct MagicLeapLogger {
tag: CString,
level_filter: log::LevelFilter,
}
impl log::Log for MagicLeapLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.level_filter
}
fn log(&self, record: &log::Record) {
let lvl = match record.level() {
log::Level::Error => MLLogLevel::Error,
log::Level::Warn => MLLogLevel::Warning,
log::Level::Info => MLLogLevel::Info,
log::Level::Debug => MLLogLevel::Debug,
log::Level::Trace => MLLogLevel::Verbose,
};
let mut msg = SmallVec::<[u8; 128]>::new();
write!(msg, "{}\0", record.args()).unwrap();
unsafe {
MLLoggingLog(lvl, self.tag.as_ptr(), &msg[0] as *const _ as _);
}
}
fn flush(&self) {}
}
impl MagicLeapLogger {
pub fn new(tag: CString, level_filter: log::LevelFilter) -> Self {
MagicLeapLogger { tag, level_filter }
}
}
// Linear interpolation
impl MLVec3f {
fn lerp(&self, other: MLVec3f, t: f32) -> MLVec3f {
MLVec3f {
x: util::lerp(self.x, other.x, t),
y: util::lerp(self.y, other.y, t),
z: util::lerp(self.z, other.z, t),
}
}
}
impl MLQuaternionf {
fn lerp(&self, other: MLQuaternionf, t: f32) -> MLQuaternionf {
MLQuaternionf {
x: util::lerp(self.x, other.x, t),
y: util::lerp(self.y, other.y, t),
z: util::lerp(self.z, other.z, t),
w: util::lerp(self.w, other.w, t),
}
}
}
impl MLTransform {
fn lerp(&self, other: MLTransform, t: f32) -> MLTransform {
MLTransform {
rotation: self.rotation.lerp(other.rotation, t),
position: self.position.lerp(other.position, t),
}
}
}
// Impl pathfinder traits for c-api types
impl From<MLTransform> for Transform3DF32 {
fn from(mat: MLTransform) -> Self {
Transform3DF32::from(mat.rotation)
.pre_mul(&Transform3DF32::from(mat.position))
}
}
impl From<MLVec3f> for Transform3DF32 {
fn from(v: MLVec3f) -> Self {
Transform3DF32::from_translation(v.x, v.y, v.z)
}
}
impl From<MLRectf> for RectF32 {
fn from(r: MLRectf) -> Self {
RectF32::new(Point2DF32::new(r.x, r.y), Point2DF32::new(r.w, r.h))
}
}
impl From<MLRectf> for RectI32 {
fn from(r: MLRectf) -> Self {
RectF32::from(r).to_i32()
}
}
impl From<MLQuaternionf> for Transform3DF32 {
fn from(q: MLQuaternionf) -> Self {
Transform3DF32::from_rotation_quaternion(F32x4::new(q.x, q.y, q.z, q.w))
}
}
impl From<MLMat4f> for Transform3DF32 {
fn from(mat: MLMat4f) -> Self {
let a = mat.matrix_colmajor;
Transform3DF32::row_major(a[0], a[4], a[8], a[12],
a[1], a[5], a[9], a[13],
a[2], a[6], a[10], a[14],
a[3], a[7], a[11], a[15])
}
}

257
demo/magicleap/src/main.cpp Normal file
View File

@ -0,0 +1,257 @@
// pathfinder/demo/magicleap/src/main.cpp
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
// The immersive mode pathfinder magicleap demo
#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <cmath>
#include <unistd.h>
#include <sys/syscall.h>
#ifndef EGL_EGLEXT_PROTOTYPES
#define EGL_EGLEXT_PROTOTYPES
#endif
#include <EGL/egl.h>
#include <EGL/eglext.h>
#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#endif
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <ml_graphics.h>
#include <ml_head_tracking.h>
#include <ml_perception.h>
#include <ml_fileinfo.h>
#include <ml_lifecycle.h>
#include <ml_logging.h>
#include <ml_privileges.h>
// Entry points to the Rust code
extern "C" void* magicleap_pathfinder_demo_init(EGLDisplay egl_display, EGLContext egl_context);
extern "C" void magicleap_pathfinder_demo_load(void* app, const char* file_name);
extern "C" void magicleap_pathfinder_demo_run(void* app);
// Initialization of the scene thread
extern "C" void init_scene_thread(uint64_t id) {
// https://forum.magicleap.com/hc/en-us/community/posts/360043120832-How-many-CPUs-does-an-immersive-app-have-access-to-?page=1#community_comment_360005035691
// We pin scene thread 0 to the Denver core.
if (id < 3) {
uint32_t DenverCoreAffinityMask = 1 << (2 + id); // Denver core is CPU2, A57s are CPU3 and 4.
pid_t ThreadId = gettid();
syscall(__NR_sched_setaffinity, ThreadId, sizeof(DenverCoreAffinityMask), &DenverCoreAffinityMask);
}
}
// Constants
const char application_name[] = "com.mozilla.pathfinder.demo";
// Structures
struct graphics_context_t {
EGLDisplay egl_display;
EGLContext egl_context;
GLuint framebuffer_id;
GLuint vertex_shader_id;
GLuint fragment_shader_id;
GLuint program_id;
graphics_context_t();
~graphics_context_t();
void makeCurrent();
void swapBuffers();
void unmakeCurrent();
};
graphics_context_t::graphics_context_t() {
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major = 4;
EGLint minor = 0;
eglInitialize(egl_display, &major, &minor);
eglBindAPI(EGL_OPENGL_API);
EGLint config_attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_NONE
};
EGLConfig egl_config = nullptr;
EGLint config_size = 0;
eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &config_size);
EGLint context_attribs[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
EGL_CONTEXT_MINOR_VERSION_KHR, 0,
EGL_NONE
};
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs);
}
void graphics_context_t::makeCurrent() {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
}
void graphics_context_t::unmakeCurrent() {
eglMakeCurrent(NULL, EGL_NO_SURFACE, EGL_NO_SURFACE, NULL);
}
void graphics_context_t::swapBuffers() {
// buffer swapping is implicit on device (MLGraphicsEndFrame)
}
graphics_context_t::~graphics_context_t() {
eglDestroyContext(egl_display, egl_context);
eglTerminate(egl_display);
}
// Callbacks
static void onStop(void* app)
{
ML_LOG(Info, "%s: On stop called.", application_name);
}
static void onPause(void* app)
{
ML_LOG(Info, "%s: On pause called.", application_name);
}
static void onResume(void* app)
{
ML_LOG(Info, "%s: On resume called.", application_name);
}
static void onNewInitArg(void* app)
{
ML_LOG(Info, "%s: On new init arg called.", application_name);
// Get the file argument if there is one
MLLifecycleInitArgList* arg_list = nullptr;
const MLLifecycleInitArg* arg = nullptr;
const MLFileInfo* file_info = nullptr;
const char* file_name = nullptr;
int64_t arg_list_len = 0;
int64_t file_list_len = 0;
if (MLResult_Ok != MLLifecycleGetInitArgList(&arg_list)) {
ML_LOG(Error, "%s: Failed to get init args.", application_name);
return;
}
if (MLResult_Ok != MLLifecycleGetInitArgListLength(arg_list, &arg_list_len)) {
ML_LOG(Error, "%s: Failed to get init arg length.", application_name);
return;
}
if (arg_list_len) {
if (MLResult_Ok != MLLifecycleGetInitArgByIndex(arg_list, 0, &arg)) {
ML_LOG(Error, "%s: Failed to get init arg.", application_name);
return;
}
if (MLResult_Ok != MLLifecycleGetFileInfoListLength(arg, &file_list_len)) {
ML_LOG(Error, "%s: Failed to get file list length.", application_name);
return;
}
}
if (file_list_len) {
if (MLResult_Ok != MLLifecycleGetFileInfoByIndex(arg, 0, &file_info)) {
ML_LOG(Error, "%s: Failed to get file info.", application_name);
return;
}
if (MLResult_Ok != MLFileInfoGetFileName(file_info, &file_name)) {
ML_LOG(Error, "%s: Failed to get file name.", application_name);
return;
}
}
// Tell pathfinder to load the file
magicleap_pathfinder_demo_load(app, file_name);
MLLifecycleFreeInitArgList(&arg_list);
}
extern "C" void logMessage(MLLogLevel lvl, char* msg) {
if (MLLoggingLogLevelIsEnabled(lvl)) {
MLLoggingLog(lvl, ML_DEFAULT_LOG_TAG, msg);
}
}
int main() {
// set up host-specific graphics surface
graphics_context_t graphics_context;
// Check privileges
if (MLResult_Ok != MLPrivilegesStartup()) {
ML_LOG(Error, "%s: Failed to initialize privileges.", application_name);
return -1;
}
if (MLPrivilegesRequestPrivilege(MLPrivilegeID_WorldReconstruction) != MLPrivilegesResult_Granted) {
ML_LOG(Error, "Privilege %d denied.", MLPrivilegeID_WorldReconstruction);
return -1;
}
if (MLPrivilegesRequestPrivilege(MLPrivilegeID_LowLatencyLightwear) != MLPrivilegesResult_Granted) {
ML_LOG(Error, "Privilege %d denied.", MLPrivilegeID_LowLatencyLightwear);
return -1;
}
// initialize perception system
MLPerceptionSettings perception_settings;
if (MLResult_Ok != MLPerceptionInitSettings(&perception_settings)) {
ML_LOG(Error, "%s: Failed to initialize perception.", application_name);
}
if (MLResult_Ok != MLPerceptionStartup(&perception_settings)) {
ML_LOG(Error, "%s: Failed to startup perception.", application_name);
return -1;
}
// Initialize pathfinder
void* app = magicleap_pathfinder_demo_init(graphics_context.egl_display, graphics_context.egl_context);
// let system know our app has started
MLLifecycleCallbacks lifecycle_callbacks = {};
lifecycle_callbacks.on_stop = onStop;
lifecycle_callbacks.on_pause = onPause;
lifecycle_callbacks.on_resume = onResume;
lifecycle_callbacks.on_new_initarg = onNewInitArg;
if (MLResult_Ok != MLLifecycleInit(&lifecycle_callbacks, app)) {
ML_LOG(Error, "%s: Failed to initialize lifecycle.", application_name);
return -1;
}
// Get the initial argument if there is one.
onNewInitArg(app);
// Run the demo!
ML_LOG(Info, "%s: Begin demo.", application_name);
magicleap_pathfinder_demo_run(app);
ML_LOG(Info, "%s: End demo.", application_name);
// Shut down
MLPerceptionShutdown();
return 0;
}

View File

@ -0,0 +1,98 @@
// pathfinder/demo/magicleap/src/mocked_c_api.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 mocked Rust implementation of the Magic Leap C API, to allow it to build without the ML SDK
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(non_snake_case)]
use crate::c_api::MLCoordinateFrameUID;
use crate::c_api::MLGraphicsClipExtentsInfoArray;
use crate::c_api::MLGraphicsFrameParams;
use crate::c_api::MLGraphicsOptions;
use crate::c_api::MLGraphicsRenderTargetsInfo;
use crate::c_api::MLGraphicsVirtualCameraInfoArray;
use crate::c_api::MLHandle;
use crate::c_api::MLHeadTrackingStaticData;
use crate::c_api::MLLogLevel;
use crate::c_api::MLResult;
use crate::c_api::MLSnapshotPtr;
use crate::c_api::MLTransform;
use std::os::raw::c_char;
pub unsafe fn MLGraphicsCreateClientGL(options: *const MLGraphicsOptions, gl_context: MLHandle, graphics_client : &mut MLHandle) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsDestroyClient(graphics_client: *mut MLHandle) -> MLResult {
unimplemented!()
}
pub unsafe fn MLHeadTrackingCreate(tracker: *mut MLHandle) -> MLResult {
unimplemented!()
}
pub unsafe fn MLHeadTrackingGetStaticData(head_tracker: MLHandle, data: *mut MLHeadTrackingStaticData) -> MLResult {
unimplemented!()
}
pub unsafe fn MLPerceptionGetSnapshot(snapshot: *mut MLSnapshotPtr) -> MLResult {
unimplemented!()
}
pub unsafe fn MLSnapshotGetTransform(snapshot: MLSnapshotPtr, id: *const MLCoordinateFrameUID, transform: *mut MLTransform) -> MLResult {
unimplemented!()
}
pub unsafe fn MLPerceptionReleaseSnapshot(snapshot: MLSnapshotPtr) -> MLResult {
unimplemented!()
}
pub unsafe fn MLLifecycleSetReadyIndication() -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsGetClipExtents(graphics_client: MLHandle, array: *mut MLGraphicsClipExtentsInfoArray) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsGetRenderTargets(graphics_client: MLHandle, targets: *mut MLGraphicsRenderTargetsInfo) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsInitFrameParams(params: *mut MLGraphicsFrameParams) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsBeginFrame(graphics_client: MLHandle, params: *const MLGraphicsFrameParams, frame_handle: *mut MLHandle, virtual_camera_array: *mut MLGraphicsVirtualCameraInfoArray) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsEndFrame(graphics_client: MLHandle, frame_handle: MLHandle) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGraphicsSignalSyncObjectGL(graphics_client: MLHandle, sync_object: MLHandle) -> MLResult {
unimplemented!()
}
pub unsafe fn MLGetResultString(result_code: MLResult) -> *const c_char {
unimplemented!()
}
pub unsafe fn MLLoggingLogLevelIsEnabled(lvl: MLLogLevel) -> bool {
unimplemented!()
}
pub unsafe fn MLLoggingLog(lvl: MLLogLevel, tag: *const c_char, message: *const c_char) {
unimplemented!()
}

31
demo/native/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
[package]
name = "demo"
version = "0.1.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[features]
pf-no-simd = ["pathfinder_simd/pf-no-simd"]
[dependencies]
color-backtrace = "0.1"
gl = "0.6"
jemallocator = "0.1"
nfd = "0.0.4"
sdl2 = "0.32"
sdl2-sys = "0.32"
[dependencies.pathfinder_demo]
path = "../common"
[dependencies.pathfinder_geometry]
path = "../../geometry"
[dependencies.pathfinder_gl]
path = "../../gl"
[dependencies.pathfinder_gpu]
path = "../../gpu"
[dependencies.pathfinder_simd]
path = "../../simd"

261
demo/native/src/main.rs Normal file
View File

@ -0,0 +1,261 @@
// pathfinder/demo/native/src/main.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 demo app for Pathfinder using SDL 2.
use jemallocator;
use nfd::Response;
use pathfinder_demo::DemoApp;
use pathfinder_demo::Options;
use pathfinder_demo::window::{Event, Keycode, SVGPath, View, Window, WindowSize};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gl::GLVersion;
use pathfinder_gpu::resources::{FilesystemResourceLoader, ResourceLoader};
use sdl2::{EventPump, EventSubsystem, Sdl, VideoSubsystem};
use sdl2::event::{Event as SDLEvent, WindowEvent};
use sdl2::keyboard::Keycode as SDLKeycode;
use sdl2::video::{GLContext, GLProfile, Window as SDLWindow};
use sdl2_sys::{SDL_Event, SDL_UserEvent};
use std::path::PathBuf;
use std::ptr;
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
const DEFAULT_WINDOW_WIDTH: u32 = 1067;
const DEFAULT_WINDOW_HEIGHT: u32 = 800;
fn main() {
color_backtrace::install();
let window = WindowImpl::new();
let window_size = window.size();
let options = Options::default();
let mut app = DemoApp::new(window, window_size, options);
while !app.should_exit {
let mut events = vec![];
if !app.dirty {
events.push(app.window.get_event());
}
while let Some(event) = app.window.try_get_event() {
events.push(event);
}
let scene_count = app.prepare_frame(events);
app.draw_scene();
for scene_index in 0..scene_count {
app.composite_scene(scene_index);
}
app.finish_drawing_frame();
}
}
thread_local! {
static SDL_CONTEXT: Sdl = sdl2::init().unwrap();
static SDL_VIDEO: VideoSubsystem = SDL_CONTEXT.with(|context| context.video().unwrap());
static SDL_EVENT: EventSubsystem = SDL_CONTEXT.with(|context| context.event().unwrap());
}
struct WindowImpl {
window: SDLWindow,
event_pump: EventPump,
#[allow(dead_code)]
gl_context: GLContext,
resource_loader: FilesystemResourceLoader,
selected_file: Option<PathBuf>,
open_svg_message_type: u32,
}
impl Window for WindowImpl {
fn gl_version(&self) -> GLVersion {
GLVersion::GL3
}
fn viewport(&self, view: View) -> RectI32 {
let (width, height) = self.window.drawable_size();
let mut width = width as i32;
let height = height as i32;
let mut x_offset = 0;
if let View::Stereo(index) = view {
width = width / 2;
x_offset = width * (index as i32);
}
RectI32::new(Point2DI32::new(x_offset, 0), Point2DI32::new(width, height))
}
fn make_current(&mut self, _view: View) {
self.window.gl_make_current(&self.gl_context).unwrap();
}
fn present(&mut self) {
self.window.gl_swap_window();
}
fn resource_loader(&self) -> &dyn ResourceLoader {
&self.resource_loader
}
fn create_user_event_id(&self) -> u32 {
SDL_EVENT.with(|sdl_event| unsafe { sdl_event.register_event().unwrap() })
}
fn push_user_event(message_type: u32, message_data: u32) {
unsafe {
let mut user_event = SDL_UserEvent {
timestamp: 0,
windowID: 0,
type_: message_type,
code: message_data as i32,
data1: ptr::null_mut(),
data2: ptr::null_mut(),
};
sdl2_sys::SDL_PushEvent(&mut user_event as *mut SDL_UserEvent as *mut SDL_Event);
}
}
fn present_open_svg_dialog(&mut self) {
if let Ok(Response::Okay(path)) = nfd::open_file_dialog(Some("svg"), None) {
self.selected_file = Some(PathBuf::from(path));
WindowImpl::push_user_event(self.open_svg_message_type, 0);
}
}
fn run_save_dialog(&self, extension: &str) -> Result<PathBuf, ()> {
match nfd::open_save_dialog(Some(extension), None) {
Ok(Response::Okay(file)) => Ok(PathBuf::from(file)),
_ => Err(()),
}
}
}
impl WindowImpl {
fn new() -> WindowImpl {
SDL_VIDEO.with(|sdl_video| {
SDL_EVENT.with(|sdl_event| {
let (window, gl_context, event_pump);
let gl_attributes = sdl_video.gl_attr();
gl_attributes.set_context_profile(GLProfile::Core);
gl_attributes.set_context_version(3, 3);
gl_attributes.set_depth_size(24);
gl_attributes.set_stencil_size(8);
window = sdl_video.window("Pathfinder Demo",
DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT)
.opengl()
.resizable()
.allow_highdpi()
.build()
.unwrap();
gl_context = window.gl_create_context().unwrap();
gl::load_with(|name| sdl_video.gl_get_proc_address(name) as *const _);
event_pump = SDL_CONTEXT.with(|sdl_context| sdl_context.event_pump().unwrap());
let resource_loader = FilesystemResourceLoader::locate();
let open_svg_message_type = unsafe {
sdl_event.register_event().unwrap()
};
WindowImpl {
window,
event_pump,
gl_context,
resource_loader,
open_svg_message_type,
selected_file: None,
}
})
})
}
fn size(&self) -> WindowSize {
let (logical_width, logical_height) = self.window.size();
let (drawable_width, _) = self.window.drawable_size();
WindowSize {
logical_size: Point2DI32::new(logical_width as i32, logical_height as i32),
backing_scale_factor: drawable_width as f32 / logical_width as f32,
}
}
fn get_event(&mut self) -> Event {
loop {
let sdl_event = self.event_pump.wait_event();
if let Some(event) = self.convert_sdl_event(sdl_event) {
return event;
}
}
}
fn try_get_event(&mut self) -> Option<Event> {
loop {
let sdl_event = self.event_pump.poll_event()?;
if let Some(event) = self.convert_sdl_event(sdl_event) {
return Some(event);
}
}
}
fn convert_sdl_event(&self, sdl_event: SDLEvent) -> Option<Event> {
match sdl_event {
SDLEvent::User { type_, .. } if type_ == self.open_svg_message_type => {
Some(Event::OpenSVG(SVGPath::Path(self.selected_file.clone().unwrap())))
}
SDLEvent::User { type_, code, .. } => {
Some(Event::User { message_type: type_, message_data: code as u32 })
}
SDLEvent::MouseButtonDown { x, y, .. } => {
Some(Event::MouseDown(Point2DI32::new(x, y)))
}
SDLEvent::MouseMotion { x, y, mousestate, .. } => {
let position = Point2DI32::new(x, y);
if mousestate.left() {
Some(Event::MouseDragged(position))
} else {
Some(Event::MouseMoved(position))
}
}
SDLEvent::Quit { .. } => Some(Event::Quit),
SDLEvent::Window { win_event: WindowEvent::SizeChanged(..), .. } => {
Some(Event::WindowResized(self.size()))
}
SDLEvent::KeyDown { keycode: Some(sdl_keycode), .. } => {
self.convert_sdl_keycode(sdl_keycode).map(Event::KeyDown)
}
SDLEvent::KeyUp { keycode: Some(sdl_keycode), .. } => {
self.convert_sdl_keycode(sdl_keycode).map(Event::KeyUp)
}
SDLEvent::MultiGesture { d_dist, .. } => {
let mouse_state = self.event_pump.mouse_state();
let center = Point2DI32::new(mouse_state.x(), mouse_state.y());
Some(Event::Zoom(d_dist, center))
}
_ => None,
}
}
fn convert_sdl_keycode(&self, sdl_keycode: SDLKeycode) -> Option<Keycode> {
match sdl_keycode {
SDLKeycode::Escape => Some(Keycode::Escape),
SDLKeycode::Tab => Some(Keycode::Tab),
sdl_keycode if sdl_keycode as i32 >= SDLKeycode::A as i32 &&
sdl_keycode as i32 <= SDLKeycode::Z as i32 => {
let offset = (sdl_keycode as i32 - SDLKeycode::A as i32) as u8;
Some(Keycode::Alphanumeric(offset + b'a'))
}
_ => None,
}
}
}

View File

@ -1,52 +0,0 @@
[package]
name = "pathfinder_server"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[features]
default = []
freetype = ["font-kit/loader-freetype-default"]
reftests = ["rsvg", "cairo-rs", "font-kit/loader-freetype-default"]
[dependencies]
app_units = "0.7"
base64 = "0.6"
bincode = "1.0"
env_logger = "0.6"
euclid = "0.19"
image = "0.19"
lazy_static = "0.2"
log = "0.3"
lru-cache = "0.1"
lyon_geom = "0.12"
lyon_path = "0.12"
rocket = "0.3"
rocket_codegen = "0.3"
rocket_contrib = "0.3"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
[dependencies.cairo-rs]
version = "0.3"
features = ["png"]
optional = true
[dependencies.rsvg]
version = "0.3"
optional = true
[dependencies.fontsan]
git = "https://github.com/servo/fontsan.git"
[dependencies.font-kit]
git = "https://github.com/pcwalton/font-kit"
[dependencies.pathfinder_partitioner]
path = "../../partitioner"
[dependencies.pathfinder_path_utils]
path = "../../path-utils"
[patch.crates-io]
ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" }

View File

@ -1,3 +0,0 @@
{
"lockfileVersion": 1
}

View File

@ -1,824 +0,0 @@
// pathfinder/demo/server/main.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
#![feature(decl_macro, plugin)]
#![plugin(rocket_codegen)]
extern crate app_units;
extern crate base64;
extern crate env_logger;
extern crate euclid;
extern crate font_kit;
extern crate fontsan;
extern crate image;
extern crate lru_cache;
extern crate lyon_geom;
extern crate lyon_path;
extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "reftests")]
extern crate cairo;
#[cfg(feature = "reftests")]
extern crate rsvg;
use euclid::{Point2D, Transform2D};
use font_kit::canvas::Format as FontKitFormat;
use font_kit::canvas::{Canvas, RasterizationOptions};
use font_kit::font::Font;
use font_kit::hinting::HintingOptions;
use font_kit::loaders;
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
use lru_cache::LruCache;
use lyon_path::PathEvent;
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
use lyon_path::iterator::PathIter;
use pathfinder_partitioner::FillRule;
use pathfinder_partitioner::mesh_pack::MeshPack;
use pathfinder_partitioner::partitioner::Partitioner;
use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
use pathfinder_path_utils::transform::Transform2DPathIter;
use rocket::http::{ContentType, Header, Status};
use rocket::request::Request;
use rocket::response::{NamedFile, Redirect, Responder, Response};
use rocket_contrib::json::Json;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::{self, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::u32;
#[cfg(feature = "reftests")]
use euclid::Size2D;
#[cfg(feature = "reftests")]
use cairo::{Format, ImageSurface};
#[cfg(feature = "reftests")]
use rsvg::{Handle, HandleExt};
const SUGGESTED_JSON_SIZE_LIMIT: u64 = 32 * 1024 * 1024;
const MESH_PACK_CACHE_SIZE: usize = 16;
const CUBIC_TO_QUADRATIC_APPROX_TOLERANCE: f32 = 5.0;
lazy_static! {
static ref MESH_PACK_CACHE: Mutex<LruCache<MeshPackCacheKey, PartitionResponder>> = {
Mutex::new(LruCache::new(MESH_PACK_CACHE_SIZE))
};
}
static STATIC_INDEX_PATH: &'static str = "../client/index.html";
static STATIC_TEXT_DEMO_PATH: &'static str = "../client/text-demo.html";
static STATIC_SVG_DEMO_PATH: &'static str = "../client/svg-demo.html";
static STATIC_3D_DEMO_PATH: &'static str = "../client/3d-demo.html";
static STATIC_TOOLS_BENCHMARK_PATH: &'static str = "../client/benchmark.html";
static STATIC_TOOLS_REFERENCE_TEST_PATH: &'static str = "../client/reference-test.html";
static STATIC_TOOLS_MESH_DEBUGGER_PATH: &'static str = "../client/mesh-debugger.html";
static STATIC_DOC_API_PATH: &'static str = "../../target/doc";
static STATIC_CSS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/css";
static STATIC_CSS_PATH: &'static str = "../client/css";
static STATIC_JS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/js";
static STATIC_JS_JQUERY_PATH: &'static str = "../client/node_modules/jquery/dist";
static STATIC_JS_POPPER_JS_PATH: &'static str = "../client/node_modules/popper.js/dist/umd";
static STATIC_JS_PATHFINDER_PATH: &'static str = "../client";
static STATIC_WOFF2_INTER_UI_PATH: &'static str = "../../resources/fonts/inter-ui";
static STATIC_WOFF2_MATERIAL_ICONS_PATH: &'static str = "../../resources/fonts/material-icons";
static STATIC_GLSL_PATH: &'static str = "../../shaders";
static STATIC_DATA_PATH: &'static str = "../../resources/data";
static STATIC_TEST_DATA_PATH: &'static str = "../../resources/tests";
static STATIC_TEXTURES_PATH: &'static str = "../../resources/textures";
static STATIC_DOC_API_INDEX_URI: &'static str = "/doc/api/pathfinder/index.html";
static BUILTIN_FONTS: [(&'static str, &'static str); 4] = [
("open-sans", "../../resources/fonts/open-sans/OpenSans-Regular.ttf"),
("nimbus-sans", "../../resources/fonts/nimbus-sans/NimbusSanL-Regu.ttf"),
("eb-garamond", "../../resources/fonts/eb-garamond/EBGaramond12-Regular.ttf"),
("inter-ui", "../../resources/fonts/inter-ui/Inter-UI-Regular.ttf"),
];
static BUILTIN_SVGS: [(&'static str, &'static str); 4] = [
("tiger", "../../resources/svg/Ghostscript_Tiger.svg"),
("logo", "../../resources/svg/pathfinder_logo.svg"),
("icons", "../../resources/svg/material_design_icons.svg"),
("logo-bw", "../../resources/svg/pathfinder_logo_bw.svg"),
];
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct MeshPackCacheKey {
builtin_font_name: String,
glyph_ids: Vec<u32>,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionFontRequest {
face: FontRequestFace,
#[serde(rename = "fontIndex")]
font_index: u32,
glyphs: Vec<PartitionGlyph>,
#[serde(rename = "pointSize")]
point_size: f64,
}
#[derive(Clone, Serialize, Deserialize)]
enum FontRequestFace {
/// One of the builtin fonts in `BUILTIN_FONTS`.
Builtin(String),
/// Base64-encoded OTF data.
Custom(String),
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum ReferenceTextRenderer {
#[serde(rename = "freetype")]
FreeType,
#[serde(rename = "core-graphics")]
#[cfg(target_os = "macos")]
CoreGraphics,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum ReferenceSvgRenderer {
#[serde(rename = "pixman")]
Pixman,
}
#[derive(Clone, Serialize, Deserialize)]
struct RenderTextReferenceRequest {
face: FontRequestFace,
#[serde(rename = "fontIndex")]
font_index: u32,
glyph: u32,
#[serde(rename = "pointSize")]
point_size: f64,
renderer: ReferenceTextRenderer,
}
#[derive(Clone, Serialize, Deserialize)]
struct RenderSvgReferenceRequest {
name: String,
scale: f64,
renderer: ReferenceSvgRenderer,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
struct PartitionGlyph {
id: u32,
transform: Transform2D<f32>,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionFontResponse {
#[serde(rename = "pathData")]
path_data: String,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum FontError {
UnknownBuiltinFont,
Base64DecodingFailed,
FontSanitizationFailed,
FontLoadingFailed,
RasterizationFailed,
ReferenceRasterizerUnavailable,
Unimplemented,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum SvgError {
ReftestsDisabled,
UnknownBuiltinSvg,
LoadingFailed,
ImageWritingFailed,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum PartitionSvgPathsError {
UnknownSvgPathCommandType,
Unimplemented,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPathsRequest {
paths: Vec<PartitionSvgPath>,
#[serde(rename = "viewBoxWidth")]
view_box_width: f32,
#[serde(rename = "viewBoxHeight")]
view_box_height: f32,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPath {
segments: Vec<PartitionSvgPathCommand>,
kind: PartitionSvgPathKind,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum PartitionSvgPathKind {
Fill(PartitionSvgFillRule),
Stroke(f32),
}
#[derive(Clone, Copy, Serialize, Deserialize)]
enum PartitionSvgFillRule {
Winding,
EvenOdd,
}
impl PartitionSvgFillRule {
fn to_fill_rule(self) -> FillRule {
match self {
PartitionSvgFillRule::Winding => FillRule::Winding,
PartitionSvgFillRule::EvenOdd => FillRule::EvenOdd,
}
}
}
#[derive(Clone)]
struct PathDescriptor {
path_index: usize,
fill_rule: FillRule,
}
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPathCommand {
#[serde(rename = "type")]
kind: char,
values: Vec<f64>,
}
struct PathPartitioningResult {
encoded_data: Arc<Vec<u8>>,
time: Duration,
}
impl PathPartitioningResult {
fn compute(pack: &mut MeshPack,
path_descriptors: &[PathDescriptor],
paths: &[Vec<PathEvent>],
approx_tolerance: Option<f32>)
-> PathPartitioningResult {
let timestamp_before = Instant::now();
for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
let mut partitioner = Partitioner::new();
if let Some(tolerance) = approx_tolerance {
partitioner.builder_mut().set_approx_tolerance(tolerance);
}
path.iter().for_each(|event| partitioner.builder_mut().path_event(*event));
partitioner.partition(path_descriptor.fill_rule);
partitioner.builder_mut().build_and_reset();
partitioner.mesh_mut().push_stencil_segments(
CubicToQuadraticTransformer::new(path.iter().cloned(),
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
partitioner.mesh_mut().push_stencil_normals(
CubicToQuadraticTransformer::new(path.iter().cloned(),
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
pack.push(partitioner.into_mesh());
}
let time_elapsed = timestamp_before.elapsed();
let mut data_buffer = Cursor::new(vec![]);
drop(pack.serialize_into(&mut data_buffer));
PathPartitioningResult {
encoded_data: Arc::new(data_buffer.into_inner()),
time: time_elapsed,
}
}
fn elapsed_ms(&self) -> f64 {
self.time.as_secs() as f64 * 1000.0 + self.time.subsec_nanos() as f64 * 1e-6
}
}
#[derive(Clone)]
struct PartitionResponder {
data: Arc<Vec<u8>>,
time: f64,
}
impl<'r> Responder<'r> for PartitionResponder {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
let mut builder = Response::build();
builder.header(ContentType::new("application", "vnd.mozilla.pfml"));
builder.header(Header::new("Server-Timing", format!("Partitioning={}", self.time)));
// FIXME(pcwalton): Don't clone! Requires a `Cursor` implementation for `Arc<Vec<u8>>`…
builder.sized_body(Cursor::new((*self.data).clone()));
builder.ok()
}
}
#[derive(Clone)]
struct ReferenceImage {
image: DynamicImage,
}
impl<'r> Responder<'r> for ReferenceImage {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
let mut builder = Response::build();
builder.header(ContentType::PNG);
let mut bytes = vec![];
try!(self.image
.write_to(&mut bytes, ImageFormat::PNG)
.map_err(|_| Status::InternalServerError));
builder.sized_body(Cursor::new(bytes));
builder.ok()
}
}
// Fetches the OTF data.
fn otf_data_from_request(face: &FontRequestFace) -> Result<Arc<Vec<u8>>, FontError> {
match *face {
FontRequestFace::Builtin(ref builtin_font_name) => {
// Read in the builtin font.
match BUILTIN_FONTS.iter().filter(|& &(name, _)| name == builtin_font_name).next() {
Some(&(_, path)) => {
let mut data = vec![];
File::open(path).expect("Couldn't find builtin font!")
.read_to_end(&mut data)
.expect("Couldn't read builtin font!");
Ok(Arc::new(data))
}
None => return Err(FontError::UnknownBuiltinFont),
}
}
FontRequestFace::Custom(ref encoded_data) => {
// Decode Base64-encoded OTF data.
let unsafe_otf_data = match base64::decode(encoded_data) {
Ok(unsafe_otf_data) => unsafe_otf_data,
Err(_) => return Err(FontError::Base64DecodingFailed),
};
// Sanitize.
match fontsan::process(&unsafe_otf_data) {
Ok(otf_data) => Ok(Arc::new(otf_data)),
Err(_) => return Err(FontError::FontSanitizationFailed),
}
}
}
}
// Fetches the SVG data.
#[cfg(feature = "reftests")]
fn svg_data_from_request(builtin_svg_name: &str) -> Result<Arc<Vec<u8>>, SvgError> {
// Read in the builtin SVG.
match BUILTIN_SVGS.iter().filter(|& &(name, _)| name == builtin_svg_name).next() {
Some(&(_, path)) => {
let mut data = vec![];
File::open(path).expect("Couldn't find builtin SVG!")
.read_to_end(&mut data)
.expect("Couldn't read builtin SVG!");
Ok(Arc::new(data))
}
None => return Err(SvgError::UnknownBuiltinSvg),
}
}
#[post("/partition-font", format = "application/json", data = "<request>")]
fn partition_font(request: Json<PartitionFontRequest>) -> Result<PartitionResponder, FontError> {
// Check the cache.
let cache_key = match request.face {
FontRequestFace::Builtin(ref builtin_font_name) => {
Some(MeshPackCacheKey {
builtin_font_name: (*builtin_font_name).clone(),
glyph_ids: request.glyphs.iter().map(|glyph| glyph.id).collect(),
})
}
_ => None,
};
if let Some(ref cache_key) = cache_key {
if let Ok(mut mesh_library_cache) = MESH_PACK_CACHE.lock() {
if let Some(cache_entry) = mesh_library_cache.get_mut(cache_key) {
return Ok((*cache_entry).clone())
}
}
}
// Parse glyph data.
let otf_data = try!(otf_data_from_request(&request.face));
let font = match Font::from_bytes(otf_data, request.font_index) {
Ok(font) => font,
Err(_) => return Err(FontError::FontLoadingFailed),
};
// Read glyph info.
let mut paths: Vec<Vec<PathEvent>> = vec![];
let mut path_descriptors = vec![];
for (glyph_index, glyph) in request.glyphs.iter().enumerate() {
// This might fail; if so, just leave it blank.
// FIXME(pcwalton): Should we add first-class support for transforms to `font-kit`?
let mut path_builder = lyon_path::default::Path::builder();
match font.outline(glyph.id, HintingOptions::None, &mut path_builder) {
Ok(()) => {
paths.push(Transform2DPathIter::new(path_builder.build().into_iter(),
&glyph.transform).collect())
}
Err(_) => paths.push(vec![]),
};
path_descriptors.push(PathDescriptor {
path_index: glyph_index,
fill_rule: FillRule::Winding,
})
}
// Partition the decoded glyph outlines.
let mut pack = MeshPack::new();
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
&path_descriptors,
&paths,
None);
// Build the response.
let elapsed_ms = path_partitioning_result.elapsed_ms();
let responder = PartitionResponder {
data: path_partitioning_result.encoded_data,
time: elapsed_ms,
};
if let Some(cache_key) = cache_key {
if let Ok(mut mesh_library_cache) = MESH_PACK_CACHE.lock() {
mesh_library_cache.insert(cache_key, responder.clone());
}
}
Ok(responder)
}
#[post("/partition-svg-paths", format = "application/json", data = "<request>")]
fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
-> Result<PartitionResponder, PartitionSvgPathsError> {
// Parse the SVG path.
//
// The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z`
// commands.
let mut paths = vec![];
let mut path_descriptors = vec![];
let mut pack = MeshPack::new();
let mut path_index = 0;
for path in &request.paths {
let mut stream = vec![];
for segment in &path.segments {
match segment.kind {
'M' => {
stream.push(PathEvent::MoveTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32)))
}
'L' => {
stream.push(PathEvent::LineTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32)))
}
'C' => {
stream.push(PathEvent::CubicTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32),
Point2D::new(segment.values[2] as f32,
segment.values[3] as f32),
Point2D::new(segment.values[4] as f32,
segment.values[5] as f32)))
}
'Z' => stream.push(PathEvent::Close),
_ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType),
}
}
let fill_rule = match path.kind {
PartitionSvgPathKind::Fill(fill_rule) => fill_rule.to_fill_rule(),
PartitionSvgPathKind::Stroke(_) => FillRule::Winding,
};
path_descriptors.push(PathDescriptor {
path_index: path_index,
fill_rule: fill_rule,
});
match path.kind {
PartitionSvgPathKind::Fill(_) => paths.push(stream),
PartitionSvgPathKind::Stroke(stroke_width) => {
let iterator = PathIter::new(stream.into_iter());
let stroke_style = StrokeStyle::new(stroke_width);
let path: Vec<_> = StrokeToFillIter::new(iterator, stroke_style).collect();
paths.push(path);
}
}
path_index += 1;
}
// Compute approximation tolerance.
let tolerance = f32::max(request.view_box_width, request.view_box_height) * 0.001;
// Partition the paths.
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
&path_descriptors,
&paths,
Some(tolerance));
// Return the response.
let elapsed_ms = path_partitioning_result.elapsed_ms();
Ok(PartitionResponder {
data: path_partitioning_result.encoded_data,
time: elapsed_ms,
})
}
#[post("/render-reference/text", format = "application/json", data = "<request>")]
fn render_reference_text(request: Json<RenderTextReferenceRequest>)
-> Result<ReferenceImage, FontError> {
let otf_data = try!(otf_data_from_request(&request.face));
// Rasterize the glyph using the right rasterizer.
let mut canvas;
match request.renderer {
ReferenceTextRenderer::FreeType => {
let loader = match Font::from_bytes(otf_data, request.font_index) {
Ok(loader) => loader,
Err(_) => return Err(FontError::FontLoadingFailed),
};
let glyph_rect = try!(loader.raster_bounds(request.glyph,
request.point_size as f32,
&Point2D::zero(),
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
let glyph_dimensions = glyph_rect.size.to_u32();
canvas = Canvas::new(&glyph_dimensions, FontKitFormat::Rgba32);
let origin = Point2D::new(-glyph_rect.origin.x, -glyph_rect.origin.y).to_f32();
try!(loader.rasterize_glyph(&mut canvas,
request.glyph,
request.point_size as f32,
&origin,
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
}
#[cfg(target_os = "macos")]
ReferenceTextRenderer::CoreGraphics => {
let loader = match loaders::core_text::Font::from_bytes(otf_data, request.font_index) {
Ok(loader) => loader,
Err(_) => return Err(FontError::FontLoadingFailed),
};
let glyph_rect = try!(loader.raster_bounds(request.glyph,
request.point_size as f32,
&Point2D::zero(),
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
let glyph_dimensions = glyph_rect.size.to_u32();
canvas = Canvas::new(&glyph_dimensions, FontKitFormat::Rgba32);
let origin = Point2D::new(-glyph_rect.origin.x, -glyph_rect.origin.y).to_f32();
try!(loader.rasterize_glyph(&mut canvas,
request.glyph,
request.point_size as f32,
&origin,
HintingOptions::None,
RasterizationOptions::SubpixelAa)
.map_err(|_| FontError::RasterizationFailed));
}
};
let image_buffer = ImageBuffer::from_raw(canvas.size.width,
canvas.size.height,
canvas.pixels).unwrap();
let reference_image = ReferenceImage {
image: ImageRgba8(image_buffer),
};
Ok(reference_image)
}
#[cfg(feature = "reftests")]
#[post("/render-reference/svg", format = "application/json", data = "<request>")]
fn render_reference_svg(request: Json<RenderSvgReferenceRequest>)
-> Result<ReferenceImage, SvgError> {
let svg_data = try!(svg_data_from_request(&request.name));
let svg_string = String::from_utf8_lossy(&*svg_data);
let svg_handle = try!(Handle::new_from_str(&svg_string).map_err(|_| SvgError::LoadingFailed));
let svg_dimensions = svg_handle.get_dimensions();
let mut image_size = Size2D::new(svg_dimensions.width as f64, svg_dimensions.height as f64);
image_size = (image_size * request.scale).ceil();
// Rasterize the SVG using the appropriate rasterizer.
let mut surface = ImageSurface::create(Format::ARgb32,
image_size.width as i32,
image_size.height as i32).unwrap();
{
let cairo_context = cairo::Context::new(&surface);
cairo_context.scale(request.scale, request.scale);
svg_handle.render_cairo(&cairo_context);
}
let mut image_data = (*surface.get_data().unwrap()).to_vec();
image_data.chunks_mut(4).for_each(|color| color.swap(0, 2));
let image_buffer = match ImageBuffer::from_raw(image_size.width as u32,
image_size.height as u32,
image_data) {
None => return Err(SvgError::ImageWritingFailed),
Some(image_buffer) => image_buffer,
};
Ok(ReferenceImage {
image: ImageRgba8(image_buffer),
})
}
#[cfg(not(feature = "reftests"))]
#[post("/render-reference/svg", format = "application/json", data = "<request>")]
#[allow(unused_variables)]
fn render_reference_svg(request: Json<RenderSvgReferenceRequest>)
-> Result<ReferenceImage, SvgError> {
Err(SvgError::ReftestsDisabled)
}
// Static files
#[get("/")]
fn static_index() -> io::Result<NamedFile> {
NamedFile::open(STATIC_INDEX_PATH)
}
#[get("/demo/text")]
fn static_demo_text() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TEXT_DEMO_PATH)
}
#[get("/demo/svg")]
fn static_demo_svg() -> io::Result<NamedFile> {
NamedFile::open(STATIC_SVG_DEMO_PATH)
}
#[get("/demo/3d")]
fn static_demo_3d() -> io::Result<NamedFile> {
NamedFile::open(STATIC_3D_DEMO_PATH)
}
#[get("/tools/benchmark")]
fn static_tools_benchmark() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TOOLS_BENCHMARK_PATH)
}
#[get("/tools/reference-test")]
fn static_tools_reference_test() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TOOLS_REFERENCE_TEST_PATH)
}
#[get("/tools/mesh-debugger")]
fn static_tools_mesh_debugger() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TOOLS_MESH_DEBUGGER_PATH)
}
#[get("/doc/api")]
fn static_doc_api_index() -> Redirect {
Redirect::to(STATIC_DOC_API_INDEX_URI)
}
#[get("/doc/api/<file..>")]
fn static_doc_api(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_DOC_API_PATH).join(file)).ok()
}
#[get("/css/bootstrap/<file..>")]
fn static_css_bootstrap(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_CSS_BOOTSTRAP_PATH).join(file)).ok()
}
#[get("/css/<file>")]
fn static_css(file: String) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_CSS_PATH).join(file)).ok()
}
#[get("/js/bootstrap/<file..>")]
fn static_js_bootstrap(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_BOOTSTRAP_PATH).join(file)).ok()
}
#[get("/js/jquery/<file..>")]
fn static_js_jquery(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_JQUERY_PATH).join(file)).ok()
}
#[get("/js/popper.js/<file..>")]
fn static_js_popper_js(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_POPPER_JS_PATH).join(file)).ok()
}
#[get("/js/pathfinder/<file..>")]
fn static_js_pathfinder(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_JS_PATHFINDER_PATH).join(file)).ok()
}
#[get("/woff2/inter-ui/<file..>")]
fn static_woff2_inter_ui(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_WOFF2_INTER_UI_PATH).join(file)).ok()
}
#[get("/woff2/material-icons/<file..>")]
fn static_woff2_material_icons(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_WOFF2_MATERIAL_ICONS_PATH).join(file)).ok()
}
#[get("/glsl/<file..>")]
fn static_glsl(file: PathBuf) -> Option<Shader> {
Shader::open(path::Path::new(STATIC_GLSL_PATH).join(file)).ok()
}
#[get("/otf/demo/<font_name>")]
fn static_otf_demo(font_name: String) -> Option<NamedFile> {
BUILTIN_FONTS.iter()
.filter(|& &(name, _)| name == font_name)
.next()
.and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
}
#[get("/svg/demo/<svg_name>")]
fn static_svg_demo(svg_name: String) -> Option<NamedFile> {
BUILTIN_SVGS.iter()
.filter(|& &(name, _)| name == svg_name)
.next()
.and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
}
#[get("/data/<file..>")]
fn static_data(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_DATA_PATH).join(file)).ok()
}
#[get("/test-data/<file..>")]
fn static_test_data(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_TEST_DATA_PATH).join(file)).ok()
}
#[get("/textures/<file..>")]
fn static_textures(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(path::Path::new(STATIC_TEXTURES_PATH).join(file)).ok()
}
struct Shader {
file: File,
}
impl Shader {
fn open(path: PathBuf) -> io::Result<Shader> {
File::open(path).map(|file| Shader {
file: file,
})
}
}
impl<'a> Responder<'a> for Shader {
fn respond_to(self, _: &Request) -> Result<Response<'a>, Status> {
Response::build().header(ContentType::Plain).streamed_body(self.file).ok()
}
}
fn main() {
drop(env_logger::init());
let rocket = rocket::ignite();
match rocket.config().limits.get("json") {
Some(size) if size >= SUGGESTED_JSON_SIZE_LIMIT => {}
None | Some(_) => {
eprintln!("warning: the JSON size limit is small; many SVGs will not upload properly");
eprintln!("warning: adding the following to `Rocket.toml` is suggested:");
eprintln!("warning: [development]");
eprintln!("warning: limits = {{ json = 33554432 }}");
}
}
rocket.mount("/", routes![
partition_font,
partition_svg_paths,
render_reference_text,
render_reference_svg,
static_index,
static_demo_text,
static_demo_svg,
static_demo_3d,
static_tools_benchmark,
static_tools_reference_test,
static_tools_mesh_debugger,
static_doc_api_index,
static_doc_api,
static_css,
static_css_bootstrap,
static_js_bootstrap,
static_js_jquery,
static_js_popper_js,
static_js_pathfinder,
static_woff2_inter_ui,
static_woff2_material_icons,
static_glsl,
static_otf_demo,
static_svg_demo,
static_data,
static_test_data,
static_textures,
]).launch();
}

18
doc/architecture.dot Normal file
View File

@ -0,0 +1,18 @@
digraph G {
graph[rankdir=LR,fontname="Source Sans Pro"];
node[shape=box,fontname="Source Sans Pro"];
edge[arrowhead=vee];
subgraph cluster_CPU {
Simplify -> Tile -> Cull -> Pack;
label="CPU";
}
subgraph clusterGPU {
Fill -> Shade;
label="GPU";
}
Pack -> Fill;
Paths[shape=none];
Image[shape=none];
Paths -> Simplify;
Shade -> Image;
}

67
doc/architecture.md Normal file
View File

@ -0,0 +1,67 @@
# Pathfinder 3 Architecture
## Rendering pipeline
![](architecture.svg)
Quoted timings are for the Ghostscript tiger at 1600×1600 pixels, 8-bit RGBA destination, 2017
MacBook Pro, Intel Core i7-7920HQ CPU @ 3.10GHz, (quad-core with hyperthreading), Intel HD Graphics
630 1536 MB.
### CPU
#### Simplify
Across all paths in parallel:
* Apply transforms.
* Convert strokes to fills.
* Make curves monotonic.
#### Tile
Across all paths in parallel:
* Cut paths into 16×16 tiles.
* Approximate curves with lines.
* Flag tiles that consist entirely of a solid color.
#### Cull
Sequentially (<span style="text-transform: lowercase; font-variant: small-caps">TODO</span>: in parallel):
* Cull tiles occluded by solid-color tiles.
#### Pack
Sequentially:
* Gather up and compress per-instance data produced by the tile pass into batches.
* Upload to GPU.
Approximate CPU time for the tile, cull, and pack passes: 2.2 ms.
### GPU
#### Fill
In parallel:
* Rasterize all edges to an alpha coverage framebuffer (16-bit single-channel floating point).
Approximate GPU time: 2.6 ms.
#### Shade
In parallel:
* Draw solid-color tiles. (Z-buffer is not needed because we did occlusion culling in software.)
* Shade tiles back-to-front using the alpha mask generated during the fill step.
Approximate GPU time: 2.3 ms.

111
doc/architecture.svg Normal file
View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: G Pages: 1 -->
<svg width="702pt" height="102pt"
viewBox="0.00 0.00 701.94 102.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 98)">
<title>G</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-98 697.936,-98 697.936,4 -4,4"/>
<g id="clust1" class="cluster">
<title>cluster_CPU</title>
<polygon fill="none" stroke="#000000" points="82,-8 82,-86 431.936,-86 431.936,-8 82,-8"/>
<text text-anchor="middle" x="256.968" y="-70.8" font-family="Source Sans Pro" font-size="14.00" fill="#000000">CPU</text>
</g>
<g id="clust2" class="cluster">
<title>clusterGPU</title>
<polygon fill="none" stroke="#000000" points="451.936,-8 451.936,-86 611.936,-86 611.936,-8 451.936,-8"/>
<text text-anchor="middle" x="531.936" y="-70.8" font-family="Source Sans Pro" font-size="14.00" fill="#000000">GPU</text>
</g>
<!-- Simplify -->
<g id="node1" class="node">
<title>Simplify</title>
<polygon fill="none" stroke="#000000" points="153.904,-52 90.032,-52 90.032,-16 153.904,-16 153.904,-52"/>
<text text-anchor="middle" x="121.968" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Simplify</text>
</g>
<!-- Tile -->
<g id="node2" class="node">
<title>Tile</title>
<polygon fill="none" stroke="#000000" points="243.936,-52 189.936,-52 189.936,-16 243.936,-16 243.936,-52"/>
<text text-anchor="middle" x="216.936" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Tile</text>
</g>
<!-- Simplify&#45;&gt;Tile -->
<g id="edge1" class="edge">
<title>Simplify&#45;&gt;Tile</title>
<path fill="none" stroke="#000000" d="M154.1198,-34C162.3831,-34 171.3403,-34 179.8319,-34"/>
<polygon fill="#000000" stroke="#000000" points="189.8808,-34 179.8809,-38.5001 184.8808,-34 179.8808,-34.0001 179.8808,-34.0001 179.8808,-34.0001 184.8808,-34 179.8808,-29.5001 189.8808,-34 189.8808,-34"/>
</g>
<!-- Cull -->
<g id="node3" class="node">
<title>Cull</title>
<polygon fill="none" stroke="#000000" points="333.936,-52 279.936,-52 279.936,-16 333.936,-16 333.936,-52"/>
<text text-anchor="middle" x="306.936" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Cull</text>
</g>
<!-- Tile&#45;&gt;Cull -->
<g id="edge2" class="edge">
<title>Tile&#45;&gt;Cull</title>
<path fill="none" stroke="#000000" d="M243.939,-34C251.9637,-34 260.9025,-34 269.4669,-34"/>
<polygon fill="#000000" stroke="#000000" points="279.641,-34 269.6411,-38.5001 274.641,-34 269.641,-34.0001 269.641,-34.0001 269.641,-34.0001 274.641,-34 269.641,-29.5001 279.641,-34 279.641,-34"/>
</g>
<!-- Pack -->
<g id="node4" class="node">
<title>Pack</title>
<polygon fill="none" stroke="#000000" points="423.936,-52 369.936,-52 369.936,-16 423.936,-16 423.936,-52"/>
<text text-anchor="middle" x="396.936" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Pack</text>
</g>
<!-- Cull&#45;&gt;Pack -->
<g id="edge3" class="edge">
<title>Cull&#45;&gt;Pack</title>
<path fill="none" stroke="#000000" d="M333.939,-34C341.9637,-34 350.9025,-34 359.4669,-34"/>
<polygon fill="#000000" stroke="#000000" points="369.641,-34 359.6411,-38.5001 364.641,-34 359.641,-34.0001 359.641,-34.0001 359.641,-34.0001 364.641,-34 359.641,-29.5001 369.641,-34 369.641,-34"/>
</g>
<!-- Fill -->
<g id="node5" class="node">
<title>Fill</title>
<polygon fill="none" stroke="#000000" points="513.936,-52 459.936,-52 459.936,-16 513.936,-16 513.936,-52"/>
<text text-anchor="middle" x="486.936" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Fill</text>
</g>
<!-- Pack&#45;&gt;Fill -->
<g id="edge5" class="edge">
<title>Pack&#45;&gt;Fill</title>
<path fill="none" stroke="#000000" d="M423.939,-34C431.9637,-34 440.9025,-34 449.4669,-34"/>
<polygon fill="#000000" stroke="#000000" points="459.641,-34 449.6411,-38.5001 454.641,-34 449.641,-34.0001 449.641,-34.0001 449.641,-34.0001 454.641,-34 449.641,-29.5001 459.641,-34 459.641,-34"/>
</g>
<!-- Shade -->
<g id="node6" class="node">
<title>Shade</title>
<polygon fill="none" stroke="#000000" points="603.936,-52 549.936,-52 549.936,-16 603.936,-16 603.936,-52"/>
<text text-anchor="middle" x="576.936" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Shade</text>
</g>
<!-- Fill&#45;&gt;Shade -->
<g id="edge4" class="edge">
<title>Fill&#45;&gt;Shade</title>
<path fill="none" stroke="#000000" d="M513.939,-34C521.9637,-34 530.9025,-34 539.4669,-34"/>
<polygon fill="#000000" stroke="#000000" points="549.641,-34 539.6411,-38.5001 544.641,-34 539.641,-34.0001 539.641,-34.0001 539.641,-34.0001 544.641,-34 539.641,-29.5001 549.641,-34 549.641,-34"/>
</g>
<!-- Image -->
<g id="node8" class="node">
<title>Image</title>
<text text-anchor="middle" x="666.936" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Image</text>
</g>
<!-- Shade&#45;&gt;Image -->
<g id="edge7" class="edge">
<title>Shade&#45;&gt;Image</title>
<path fill="none" stroke="#000000" d="M603.939,-34C611.9637,-34 620.9025,-34 629.4669,-34"/>
<polygon fill="#000000" stroke="#000000" points="639.641,-34 629.6411,-38.5001 634.641,-34 629.641,-34.0001 629.641,-34.0001 629.641,-34.0001 634.641,-34 629.641,-29.5001 639.641,-34 639.641,-34"/>
</g>
<!-- Paths -->
<g id="node7" class="node">
<title>Paths</title>
<text text-anchor="middle" x="27" y="-31.599" font-family="Source Sans Pro" font-size="14.00" fill="#000000">Paths</text>
</g>
<!-- Paths&#45;&gt;Simplify -->
<g id="edge6" class="edge">
<title>Paths&#45;&gt;Simplify</title>
<path fill="none" stroke="#000000" d="M54.214,-34C62.0853,-34 70.8714,-34 79.4399,-34"/>
<polygon fill="#000000" stroke="#000000" points="89.7075,-34 79.7075,-38.5001 84.7075,-34 79.7075,-34.0001 79.7075,-34.0001 79.7075,-34.0001 84.7075,-34 79.7074,-29.5001 89.7075,-34 89.7075,-34"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

15
geometry/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "pathfinder_geometry"
version = "0.3.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
arrayvec = "0.4"
bitflags = "1.0"
serde = "1.0"
serde_derive = "1.0"
smallvec = "0.6"
[dependencies.pathfinder_simd]
path = "../simd"

View File

@ -0,0 +1,283 @@
// pathfinder/geometry/src/basic/line_segment.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Line segment types, optimized with SIMD.
use crate::basic::point::Point2DF32;
use crate::util;
use pathfinder_simd::default::F32x4;
use std::ops::{Add, Sub};
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct LineSegmentF32(pub F32x4);
impl LineSegmentF32 {
#[inline]
pub fn new(from: &Point2DF32, to: &Point2DF32) -> LineSegmentF32 {
LineSegmentF32(from.0.concat_xy_xy(to.0))
}
#[inline]
pub fn from(&self) -> Point2DF32 {
Point2DF32(self.0)
}
#[inline]
pub fn to(&self) -> Point2DF32 {
Point2DF32(self.0.zwxy())
}
#[inline]
pub fn set_from(&mut self, point: &Point2DF32) {
self.0 = point.0.concat_xy_zw(self.0)
}
#[inline]
pub fn set_to(&mut self, point: &Point2DF32) {
self.0 = self.0.concat_xy_xy(point.0)
}
#[allow(clippy::wrong_self_convention)]
#[inline]
pub fn from_x(&self) -> f32 {
self.0[0]
}
#[allow(clippy::wrong_self_convention)]
#[inline]
pub fn from_y(&self) -> f32 {
self.0[1]
}
#[inline]
pub fn to_x(&self) -> f32 {
self.0[2]
}
#[inline]
pub fn to_y(&self) -> f32 {
self.0[3]
}
#[inline]
pub fn set_from_x(&mut self, x: f32) {
self.0[0] = x
}
#[inline]
pub fn set_from_y(&mut self, y: f32) {
self.0[1] = y
}
#[inline]
pub fn set_to_x(&mut self, x: f32) {
self.0[2] = x
}
#[inline]
pub fn set_to_y(&mut self, y: f32) {
self.0[3] = y
}
#[inline]
pub fn scale(&self, factor: f32) -> LineSegmentF32 {
LineSegmentF32(self.0 * F32x4::splat(factor))
}
#[inline]
pub fn split(&self, t: f32) -> (LineSegmentF32, LineSegmentF32) {
debug_assert!(t >= 0.0 && t <= 1.0);
let (from_from, to_to) = (self.0.xyxy(), self.0.zwzw());
let d_d = to_to - from_from;
let mid_mid = from_from + d_d * F32x4::splat(t);
(LineSegmentF32(from_from.concat_xy_xy(mid_mid)),
LineSegmentF32(mid_mid.concat_xy_xy(to_to)))
}
// Returns the left segment first, followed by the right segment.
#[inline]
pub fn split_at_x(&self, x: f32) -> (LineSegmentF32, LineSegmentF32) {
let (min_part, max_part) = self.split(self.solve_t_for_x(x));
if min_part.from_x() < max_part.from_x() {
(min_part, max_part)
} else {
(max_part, min_part)
}
}
// Returns the upper segment first, followed by the lower segment.
#[inline]
pub fn split_at_y(&self, y: f32) -> (LineSegmentF32, LineSegmentF32) {
let (min_part, max_part) = self.split(self.solve_t_for_y(y));
// Make sure we compare `from_y` and `to_y` to properly handle the case in which one of the
// two segments is zero-length.
if min_part.from_y() < max_part.to_y() {
(min_part, max_part)
} else {
(max_part, min_part)
}
}
#[inline]
pub fn solve_t_for_x(&self, x: f32) -> f32 {
(x - self.from_x()) / (self.to_x() - self.from_x())
}
#[inline]
pub fn solve_t_for_y(&self, y: f32) -> f32 {
(y - self.from_y()) / (self.to_y() - self.from_y())
}
#[inline]
pub fn solve_x_for_y(&self, y: f32) -> f32 {
util::lerp(self.from_x(), self.to_x(), self.solve_t_for_y(y))
}
#[inline]
pub fn solve_y_for_x(&self, x: f32) -> f32 {
util::lerp(self.from_y(), self.to_y(), self.solve_t_for_x(x))
}
#[inline]
pub fn reversed(&self) -> LineSegmentF32 {
LineSegmentF32(self.0.zwxy())
}
#[inline]
pub fn upper_point(&self) -> Point2DF32 {
if self.from_y() < self.to_y() {
self.from()
} else {
self.to()
}
}
#[inline]
pub fn min_x(&self) -> f32 {
f32::min(self.from_x(), self.to_x())
}
#[inline]
pub fn max_x(&self) -> f32 {
f32::max(self.from_x(), self.to_x())
}
#[inline]
pub fn min_y(&self) -> f32 {
f32::min(self.from_y(), self.to_y())
}
#[inline]
pub fn max_y(&self) -> f32 {
f32::max(self.from_y(), self.to_y())
}
#[inline]
pub fn y_winding(&self) -> i32 {
if self.from_y() < self.to_y() {
1
} else {
-1
}
}
// Reverses if necessary so that the from point is above the to point. Calling this method
// again will undo the transformation.
#[inline]
pub fn orient(&self, y_winding: i32) -> LineSegmentF32 {
if y_winding >= 0 {
*self
} else {
self.reversed()
}
}
// TODO(pcwalton): Optimize with SIMD.
#[inline]
pub fn square_length(&self) -> f32 {
let (dx, dy) = (self.to_x() - self.from_x(), self.to_y() - self.from_y());
dx * dx + dy * dy
}
// Given a line equation of the form `ax + by + c = 0`, returns a vector of the form
// `[a, b, c, 0]`.
//
// TODO(pcwalton): Optimize.
#[inline]
pub fn line_coords(&self) -> F32x4 {
let from = F32x4::new(self.0[0], self.0[1], 1.0, 0.0);
let to = F32x4::new(self.0[2], self.0[3], 1.0, 0.0);
from.cross(to)
}
#[inline]
pub fn vector(&self) -> Point2DF32 {
self.to() - self.from()
}
// http://www.cs.swan.ac.uk/~cssimon/line_intersection.html
pub fn intersection_t(&self, other: &LineSegmentF32) -> Option<f32> {
let d0d1 = self.vector().0.concat_xy_xy(other.vector().0);
let offset = other.from() - self.from();
let factors = d0d1.concat_wz_yx(offset.0);
let terms = d0d1 * factors;
let denom = terms[0] - terms[1];
if f32::abs(denom) < EPSILON {
return None;
}
return Some((terms[3] - terms[2]) / denom);
const EPSILON: f32 = 0.0001;
}
#[inline]
pub fn sample(&self, t: f32) -> Point2DF32 {
self.from() + self.vector().scale(t)
}
#[inline]
pub fn offset(&self, distance: f32) -> LineSegmentF32 {
if self.is_zero_length() {
*self
} else {
*self + self.vector().yx().normalize().scale_xy(Point2DF32::new(-distance, distance))
}
}
#[inline]
pub fn is_zero_length(&self) -> bool {
self.vector().is_zero()
}
}
impl Add<Point2DF32> for LineSegmentF32 {
type Output = LineSegmentF32;
#[inline]
fn add(self, point: Point2DF32) -> LineSegmentF32 {
LineSegmentF32(self.0 + point.0.xyxy())
}
}
impl Sub<Point2DF32> for LineSegmentF32 {
type Output = LineSegmentF32;
#[inline]
fn sub(self, point: Point2DF32) -> LineSegmentF32 {
LineSegmentF32(self.0 - point.0.xyxy())
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct LineSegmentU4(pub u16);
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct LineSegmentU8(pub u32);

17
geometry/src/basic/mod.rs Normal file
View File

@ -0,0 +1,17 @@
// pathfinder/geometry/src/basic/mod.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Basic geometry and linear algebra primitives, optimized with SIMD.
pub mod line_segment;
pub mod point;
pub mod rect;
pub mod transform2d;
pub mod transform3d;

387
geometry/src/basic/point.rs Normal file
View File

@ -0,0 +1,387 @@
// pathfinder/geometry/src/basic/point.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 SIMD-optimized point type.
use pathfinder_simd::default::{F32x4, I32x4};
use std::ops::{Add, AddAssign, Mul, Neg, Sub};
/// 2D points with 32-bit floating point coordinates.
#[derive(Clone, Copy, Debug, Default)]
pub struct Point2DF32(pub F32x4);
impl Point2DF32 {
#[inline]
pub fn new(x: f32, y: f32) -> Point2DF32 {
Point2DF32(F32x4::new(x, y, 0.0, 0.0))
}
#[inline]
pub fn splat(value: f32) -> Point2DF32 {
Point2DF32(F32x4::splat(value))
}
#[inline]
pub fn to_3d(self) -> Point3DF32 {
Point3DF32(self.0.concat_xy_xy(F32x4::new(0.0, 1.0, 0.0, 0.0)))
}
#[inline]
pub fn x(&self) -> f32 {
self.0[0]
}
#[inline]
pub fn y(&self) -> f32 {
self.0[1]
}
#[inline]
pub fn set_x(&mut self, x: f32) {
self.0[0] = x;
}
#[inline]
pub fn set_y(&mut self, y: f32) {
self.0[1] = y;
}
#[inline]
pub fn min(&self, other: Point2DF32) -> Point2DF32 {
Point2DF32(self.0.min(other.0))
}
#[inline]
pub fn max(&self, other: Point2DF32) -> Point2DF32 {
Point2DF32(self.0.max(other.0))
}
#[inline]
pub fn clamp(&self, min_val: Point2DF32, max_val: Point2DF32) -> Point2DF32 {
self.max(min_val).min(max_val)
}
#[inline]
pub fn det(&self, other: Point2DF32) -> f32 {
self.x() * other.y() - self.y() * other.x()
}
#[inline]
pub fn dot(&self, other: Point2DF32) -> f32 {
let xy = self.0 * other.0;
xy.x() + xy.y()
}
#[inline]
pub fn scale(&self, x: f32) -> Point2DF32 {
Point2DF32(self.0 * F32x4::splat(x))
}
#[inline]
pub fn scale_xy(&self, factors: Point2DF32) -> Point2DF32 {
Point2DF32(self.0 * factors.0)
}
#[inline]
pub fn floor(&self) -> Point2DF32 {
Point2DF32(self.0.floor())
}
#[inline]
pub fn ceil(&self) -> Point2DF32 {
Point2DF32(self.0.ceil())
}
/// Treats this point as a vector and calculates its squared length.
#[inline]
pub fn square_length(&self) -> f32 {
let squared = self.0 * self.0;
squared[0] + squared[1]
}
/// Treats this point as a vector and calculates its length.
#[inline]
pub fn length(&self) -> f32 {
f32::sqrt(self.square_length())
}
/// Treats this point as a vector and normalizes it.
#[inline]
pub fn normalize(&self) -> Point2DF32 {
self.scale(1.0 / self.length())
}
/// Swaps y and x.
#[inline]
pub fn yx(&self) -> Point2DF32 {
Point2DF32(self.0.yxwz())
}
#[inline]
pub fn is_zero(&self) -> bool {
*self == Point2DF32::default()
}
#[inline]
pub fn lerp(&self, other: Point2DF32, t: f32) -> Point2DF32 {
*self + (other - *self).scale(t)
}
#[inline]
pub fn to_i32(&self) -> Point2DI32 {
Point2DI32(self.0.to_i32x4())
}
}
impl PartialEq for Point2DF32 {
#[inline]
fn eq(&self, other: &Point2DF32) -> bool {
let results = self.0.packed_eq(other.0);
results[0] != 0 && results[1] != 0
}
}
impl Add<Point2DF32> for Point2DF32 {
type Output = Point2DF32;
#[inline]
fn add(self, other: Point2DF32) -> Point2DF32 {
Point2DF32(self.0 + other.0)
}
}
impl Sub<Point2DF32> for Point2DF32 {
type Output = Point2DF32;
#[inline]
fn sub(self, other: Point2DF32) -> Point2DF32 {
Point2DF32(self.0 - other.0)
}
}
impl Mul<Point2DF32> for Point2DF32 {
type Output = Point2DF32;
#[inline]
fn mul(self, other: Point2DF32) -> Point2DF32 {
Point2DF32(self.0 * other.0)
}
}
impl Neg for Point2DF32 {
type Output = Point2DF32;
#[inline]
fn neg(self) -> Point2DF32 {
Point2DF32(-self.0)
}
}
/// 2D points with 32-bit signed integer coordinates.
#[derive(Clone, Copy, Debug, Default)]
pub struct Point2DI32(pub I32x4);
impl Point2DI32 {
#[inline]
pub fn new(x: i32, y: i32) -> Point2DI32 {
Point2DI32(I32x4::new(x, y, 0, 0))
}
#[inline]
pub fn splat(value: i32) -> Point2DI32 {
Point2DI32(I32x4::splat(value))
}
#[inline]
pub fn x(&self) -> i32 {
self.0[0]
}
#[inline]
pub fn y(&self) -> i32 {
self.0[1]
}
#[inline]
pub fn set_x(&mut self, x: i32) {
self.0[0] = x;
}
#[inline]
pub fn set_y(&mut self, y: i32) {
self.0[1] = y;
}
#[inline]
pub fn scale(&self, factor: i32) -> Point2DI32 {
Point2DI32(self.0 * I32x4::splat(factor))
}
#[inline]
pub fn scale_xy(&self, factors: Point2DI32) -> Point2DI32 {
Point2DI32(self.0 * factors.0)
}
#[inline]
pub fn to_f32(&self) -> Point2DF32 {
Point2DF32(self.0.to_f32x4())
}
}
impl Add<Point2DI32> for Point2DI32 {
type Output = Point2DI32;
#[inline]
fn add(self, other: Point2DI32) -> Point2DI32 {
Point2DI32(self.0 + other.0)
}
}
impl AddAssign<Point2DI32> for Point2DI32 {
#[inline]
fn add_assign(&mut self, other: Point2DI32) {
self.0 += other.0
}
}
impl Sub<Point2DI32> for Point2DI32 {
type Output = Point2DI32;
#[inline]
fn sub(self, other: Point2DI32) -> Point2DI32 {
Point2DI32(self.0 - other.0)
}
}
impl PartialEq for Point2DI32 {
#[inline]
fn eq(&self, other: &Point2DI32) -> bool {
let results = self.0.packed_eq(other.0);
results[0] != 0 && results[1] != 0
}
}
/// 3D homogeneous points.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point3DF32(pub F32x4);
impl Point3DF32 {
#[inline]
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Point3DF32 {
Point3DF32(F32x4::new(x, y, z, w))
}
#[inline]
pub fn splat(value: f32) -> Point3DF32 {
Point3DF32(F32x4::splat(value))
}
#[inline]
pub fn to_2d(self) -> Point2DF32 {
Point2DF32(self.0)
}
#[inline]
pub fn x(self) -> f32 {
self.0[0]
}
#[inline]
pub fn y(self) -> f32 {
self.0[1]
}
#[inline]
pub fn z(self) -> f32 {
self.0[2]
}
#[inline]
pub fn w(self) -> f32 {
self.0[3]
}
#[inline]
pub fn scale(&self, x: f32) -> Point3DF32 {
let mut factors = F32x4::splat(x);
factors[3] = 1.0;
Point3DF32(self.0 * factors)
}
#[inline]
pub fn set_x(&mut self, x: f32) {
self.0[0] = x
}
#[inline]
pub fn set_y(&mut self, y: f32) {
self.0[1] = y
}
#[inline]
pub fn set_z(&mut self, z: f32) {
self.0[2] = z
}
#[inline]
pub fn set_w(&mut self, w: f32) {
self.0[3] = w
}
#[inline]
pub fn perspective_divide(self) -> Point3DF32 {
Point3DF32(self.0 * F32x4::splat(1.0 / self.w()))
}
#[inline]
pub fn approx_eq(&self, other: &Point3DF32, epsilon: f32) -> bool {
self.0.approx_eq(other.0, epsilon)
}
/// Checks to see whether this *homogeneous* coordinate equals zero.
///
/// Note that since this treats the coordinate as a homogeneous coordinate, the `w` is ignored.
// TODO(pcwalton): Optimize with SIMD.
#[inline]
pub fn is_zero(self) -> bool {
self.x() == 0.0 && self.y() == 0.0 && self.z() == 0.0
}
#[inline]
pub fn lerp(self, other: Point3DF32, t: f32) -> Point3DF32 {
Point3DF32(self.0 + (other.0 - self.0) * F32x4::splat(t))
}
}
impl Add<Point3DF32> for Point3DF32 {
type Output = Point3DF32;
#[inline]
fn add(self, other: Point3DF32) -> Point3DF32 {
Point3DF32(self.0 + other.0)
}
}
impl AddAssign for Point3DF32 {
#[inline]
fn add_assign(&mut self, other: Point3DF32) {
self.0 += other.0
}
}
impl Mul<Point3DF32> for Point3DF32 {
type Output = Point3DF32;
#[inline]
fn mul(self, other: Point3DF32) -> Point3DF32 {
Point3DF32(self.0 * other.0)
}
}
impl Default for Point3DF32 {
#[inline]
fn default() -> Point3DF32 {
let mut point = F32x4::default();
point.set_w(1.0);
Point3DF32(point)
}
}

210
geometry/src/basic/rect.rs Normal file
View File

@ -0,0 +1,210 @@
// pathfinder/geometry/src/basic/rect.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! 2D axis-aligned rectangles, optimized with SIMD.
use crate::basic::point::{Point2DF32, Point2DI32};
use pathfinder_simd::default::{F32x4, I32x4};
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct RectF32(pub F32x4);
impl RectF32 {
#[inline]
pub fn new(origin: Point2DF32, size: Point2DF32) -> RectF32 {
RectF32(origin.0.concat_xy_xy(origin.0 + size.0))
}
#[inline]
pub fn from_points(origin: Point2DF32, lower_right: Point2DF32) -> RectF32 {
RectF32(origin.0.concat_xy_xy(lower_right.0))
}
#[inline]
pub fn origin(&self) -> Point2DF32 {
Point2DF32(self.0)
}
#[inline]
pub fn size(&self) -> Point2DF32 {
Point2DF32(self.0.zwxy() - self.0.xyxy())
}
#[inline]
pub fn upper_right(&self) -> Point2DF32 {
Point2DF32(self.0.zyxw())
}
#[inline]
pub fn lower_left(&self) -> Point2DF32 {
Point2DF32(self.0.xwzy())
}
#[inline]
pub fn lower_right(&self) -> Point2DF32 {
Point2DF32(self.0.zwxy())
}
#[inline]
pub fn contains_point(&self, point: Point2DF32) -> bool {
// self.origin <= point && point <= self.lower_right
self.0.concat_xy_xy(point.0).packed_le(point.0.concat_xy_zw(self.0)).is_all_ones()
}
#[inline]
pub fn contains_rect(&self, other: RectF32) -> bool {
// self.origin <= other.origin && other.lower_right <= self.lower_right
self.0.concat_xy_zw(other.0).packed_le(other.0.concat_xy_zw(self.0)).is_all_ones()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.origin() == self.lower_right()
}
#[inline]
pub fn union_point(&self, point: Point2DF32) -> RectF32 {
RectF32::from_points(self.origin().min(point), self.lower_right().max(point))
}
#[inline]
pub fn union_rect(&self, other: RectF32) -> RectF32 {
RectF32::from_points(self.origin().min(other.origin()),
self.lower_right().max(other.lower_right()))
}
#[inline]
pub fn intersects(&self, other: RectF32) -> bool {
// self.origin < other.lower_right && other.origin < self.lower_right
self.0.concat_xy_xy(other.0).packed_lt(other.0.concat_zw_zw(self.0)).is_all_ones()
}
#[inline]
pub fn intersection(&self, other: RectF32) -> Option<RectF32> {
if !self.intersects(other) {
None
} else {
Some(RectF32::from_points(self.origin().max(other.origin()),
self.lower_right().min(other.lower_right())))
}
}
#[inline]
pub fn min_x(self) -> f32 {
self.0[0]
}
#[inline]
pub fn min_y(self) -> f32 {
self.0[1]
}
#[inline]
pub fn max_x(self) -> f32 {
self.0[2]
}
#[inline]
pub fn max_y(self) -> f32 {
self.0[3]
}
#[inline]
pub fn scale_xy(self, factors: Point2DF32) -> RectF32 {
RectF32(self.0 * factors.0.concat_xy_xy(factors.0))
}
#[inline]
pub fn round_out(self) -> RectF32 {
RectF32::from_points(self.origin().floor(), self.lower_right().ceil())
}
#[inline]
pub fn dilate(self, amount: Point2DF32) -> RectF32 {
RectF32::from_points(self.origin() - amount, self.lower_right() + amount)
}
#[inline]
pub fn to_i32(&self) -> RectI32 {
RectI32(self.0.to_i32x4())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct RectI32(pub I32x4);
impl RectI32 {
#[inline]
pub fn new(origin: Point2DI32, size: Point2DI32) -> RectI32 {
RectI32(origin.0.concat_xy_xy(origin.0 + size.0))
}
#[inline]
pub fn from_points(origin: Point2DI32, lower_right: Point2DI32) -> RectI32 {
RectI32(origin.0.concat_xy_xy(lower_right.0))
}
#[inline]
pub fn origin(&self) -> Point2DI32 {
Point2DI32(self.0)
}
#[inline]
pub fn size(&self) -> Point2DI32 {
Point2DI32(self.0.zwxy() - self.0.xyxy())
}
#[inline]
pub fn upper_right(&self) -> Point2DI32 {
Point2DI32(self.0.zyxw())
}
#[inline]
pub fn lower_left(&self) -> Point2DI32 {
Point2DI32(self.0.xwzy())
}
#[inline]
pub fn lower_right(&self) -> Point2DI32 {
Point2DI32(self.0.zwxy())
}
#[inline]
pub fn min_x(self) -> i32 {
self.0[0]
}
#[inline]
pub fn min_y(self) -> i32 {
self.0[1]
}
#[inline]
pub fn max_x(self) -> i32 {
self.0[2]
}
#[inline]
pub fn max_y(self) -> i32 {
self.0[3]
}
#[inline]
pub fn contains_point(&self, point: Point2DI32) -> bool {
// self.origin <= point && point <= self.lower_right - 1
let lower_right = self.lower_right() - Point2DI32::splat(1);
self.0.concat_xy_xy(point.0).packed_le(point.0.concat_xy_xy(lower_right.0)).is_all_ones()
}
#[inline]
pub fn to_f32(&self) -> RectF32 {
RectF32(self.0.to_f32x4())
}
}

View File

@ -0,0 +1,302 @@
// pathfinder/geometry/src/basic/transform2d.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! 2D affine transforms.
use crate::basic::point::Point2DF32;
use crate::basic::rect::RectF32;
use crate::basic::transform3d::Transform3DF32;
use crate::segment::Segment;
use pathfinder_simd::default::F32x4;
use std::ops::Sub;
/// A 2x2 matrix, optimized with SIMD, in column-major order.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Matrix2x2F32(pub F32x4);
impl Default for Matrix2x2F32 {
#[inline]
fn default() -> Matrix2x2F32 {
Self::from_scale(&Point2DF32::splat(1.0))
}
}
impl Matrix2x2F32 {
#[inline]
pub fn from_scale(scale: &Point2DF32) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::new(scale.x(), 0.0, 0.0, scale.y()))
}
#[inline]
pub fn from_rotation(theta: f32) -> Matrix2x2F32 {
let (sin_theta, cos_theta) = (theta.sin(), theta.cos());
Matrix2x2F32(F32x4::new(cos_theta, sin_theta, -sin_theta, cos_theta))
}
#[inline]
pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::new(m11, m21, m12, m22))
}
#[inline]
pub fn post_mul(&self, other: &Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0.xyxy() * other.0.xxzz() + self.0.zwzw() * other.0.yyww())
}
#[inline]
pub fn pre_mul(&self, other: &Matrix2x2F32) -> Matrix2x2F32 {
other.post_mul(self)
}
#[inline]
pub fn entrywise_mul(&self, other: &Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0 * other.0)
}
#[inline]
pub fn adjugate(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.0.wyzx() * F32x4::new(1.0, -1.0, -1.0, 1.0))
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
let halves = self.0 * point.0.xxyy();
Point2DF32(halves + halves.zwzw())
}
#[inline]
pub fn det(&self) -> f32 {
self.0[0] * self.0[3] - self.0[2] * self.0[1]
}
#[inline]
pub fn inverse(&self) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::splat(1.0 / self.det()) * self.adjugate().0)
}
#[inline]
pub fn m11(&self) -> f32 { self.0[0] }
#[inline]
pub fn m21(&self) -> f32 { self.0[1] }
#[inline]
pub fn m12(&self) -> f32 { self.0[2] }
#[inline]
pub fn m22(&self) -> f32 { self.0[3] }
}
impl Sub<Matrix2x2F32> for Matrix2x2F32 {
type Output = Matrix2x2F32;
#[inline]
fn sub(self, other: Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0 - other.0)
}
}
/// An affine transform, optimized with SIMD.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transform2DF32 {
// Row-major order.
matrix: Matrix2x2F32,
vector: Point2DF32,
}
impl Default for Transform2DF32 {
#[inline]
fn default() -> Transform2DF32 {
Self::from_scale(&Point2DF32::splat(1.0))
}
}
impl Transform2DF32 {
#[inline]
pub fn from_scale(scale: &Point2DF32) -> Transform2DF32 {
Transform2DF32 {
matrix: Matrix2x2F32::from_scale(scale),
vector: Point2DF32::default(),
}
}
#[inline]
pub fn from_rotation(theta: f32) -> Transform2DF32 {
Transform2DF32 {
matrix: Matrix2x2F32::from_rotation(theta),
vector: Point2DF32::default(),
}
}
#[inline]
pub fn from_translation(vector: &Point2DF32) -> Transform2DF32 {
Transform2DF32 {
matrix: Matrix2x2F32::default(),
vector: *vector,
}
}
#[inline]
pub fn from_scale_rotation_translation(scale: Point2DF32, theta: f32, translation: Point2DF32)
-> Transform2DF32 {
let rotation = Transform2DF32::from_rotation(theta);
let translation = Transform2DF32::from_translation(&translation);
Transform2DF32::from_scale(&scale).post_mul(&rotation).post_mul(&translation)
}
#[inline]
pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32)
-> Transform2DF32 {
Transform2DF32 {
matrix: Matrix2x2F32::row_major(m11, m12, m21, m22),
vector: Point2DF32::new(m31, m32),
}
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
self.matrix.transform_point(point) + self.vector
}
#[inline]
pub fn transform_rect(&self, rect: &RectF32) -> RectF32 {
let upper_left = self.transform_point(&rect.origin());
let upper_right = self.transform_point(&rect.upper_right());
let lower_left = self.transform_point(&rect.lower_left());
let lower_right = self.transform_point(&rect.lower_right());
let min_point = upper_left.min(upper_right).min(lower_left).min(lower_right);
let max_point = upper_left.max(upper_right).max(lower_left).max(lower_right);
RectF32::from_points(min_point, max_point)
}
#[inline]
pub fn post_mul(&self, other: &Transform2DF32) -> Transform2DF32 {
let matrix = self.matrix.post_mul(&other.matrix);
let vector = other.transform_point(&self.vector);
Transform2DF32 { matrix, vector }
}
#[inline]
pub fn pre_mul(&self, other: &Transform2DF32) -> Transform2DF32 {
other.post_mul(self)
}
// TODO(pcwalton): Optimize better with SIMD.
#[inline]
pub fn to_3d(&self) -> Transform3DF32 {
Transform3DF32::row_major(self.matrix.0[0], self.matrix.0[1], 0.0, self.vector.x(),
self.matrix.0[2], self.matrix.0[3], 0.0, self.vector.y(),
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0)
}
#[inline]
pub fn is_identity(&self) -> bool {
*self == Transform2DF32::default()
}
#[inline]
pub fn m11(&self) -> f32 { self.matrix.m11() }
#[inline]
pub fn m21(&self) -> f32 { self.matrix.m21() }
#[inline]
pub fn m12(&self) -> f32 { self.matrix.m12() }
#[inline]
pub fn m22(&self) -> f32 { self.matrix.m22() }
#[inline]
pub fn post_translate(&self, vector: Point2DF32) -> Transform2DF32 {
self.post_mul(&Transform2DF32::from_translation(&vector))
}
#[inline]
pub fn post_rotate(&self, theta: f32) -> Transform2DF32 {
self.post_mul(&Transform2DF32::from_rotation(theta))
}
#[inline]
pub fn post_scale(&self, scale: Point2DF32) -> Transform2DF32 {
self.post_mul(&Transform2DF32::from_scale(&scale))
}
/// Returns the translation part of this matrix.
///
/// This decomposition assumes that scale, rotation, and translation are applied in that order.
#[inline]
pub fn translation(&self) -> Point2DF32 {
self.vector
}
/// Returns the rotation angle of this matrix.
///
/// This decomposition assumes that scale, rotation, and translation are applied in that order.
#[inline]
pub fn rotation(&self) -> f32 {
f32::atan2(self.m21(), self.m11())
}
/// Returns the scale factor of this matrix.
///
/// This decomposition assumes that scale, rotation, and translation are applied in that order.
#[inline]
pub fn scale_factor(&self) -> f32 {
Point2DF32(self.matrix.0.zwxy()).length()
}
}
/// Transforms a path with a SIMD 2D transform.
pub struct Transform2DF32PathIter<I>
where
I: Iterator<Item = Segment>,
{
iter: I,
transform: Transform2DF32,
}
impl<I> Iterator for Transform2DF32PathIter<I>
where
I: Iterator<Item = Segment>,
{
type Item = Segment;
#[inline]
fn next(&mut self) -> Option<Segment> {
// TODO(pcwalton): Can we go faster by transforming an entire line segment with SIMD?
let mut segment = self.iter.next()?;
if !segment.is_none() {
segment
.baseline
.set_from(&self.transform.transform_point(&segment.baseline.from()));
segment
.baseline
.set_to(&self.transform.transform_point(&segment.baseline.to()));
if !segment.is_line() {
segment
.ctrl
.set_from(&self.transform.transform_point(&segment.ctrl.from()));
if !segment.is_quadratic() {
segment
.ctrl
.set_to(&self.transform.transform_point(&segment.ctrl.to()));
}
}
}
Some(segment)
}
}
impl<I> Transform2DF32PathIter<I>
where
I: Iterator<Item = Segment>,
{
#[inline]
pub fn new(iter: I, transform: &Transform2DF32) -> Transform2DF32PathIter<I> {
Transform2DF32PathIter {
iter,
transform: *transform,
}
}
}

View File

@ -0,0 +1,424 @@
// pathfinder/geometry/src/basic/transform3d.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! 3D transforms that can be applied to paths.
use crate::basic::point::{Point2DF32, Point2DI32, Point3DF32};
use crate::basic::rect::RectF32;
use crate::basic::transform2d::Matrix2x2F32;
use crate::segment::Segment;
use pathfinder_simd::default::F32x4;
use std::ops::{Add, Neg};
/// An transform, optimized with SIMD.
///
/// In column-major order.
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(C)]
pub struct Transform3DF32 {
pub c0: F32x4,
pub c1: F32x4,
pub c2: F32x4,
pub c3: F32x4,
}
impl Default for Transform3DF32 {
#[inline]
fn default() -> Transform3DF32 {
Transform3DF32 {
c0: F32x4::new(1.0, 0.0, 0.0, 0.0),
c1: F32x4::new(0.0, 1.0, 0.0, 0.0),
c2: F32x4::new(0.0, 0.0, 1.0, 0.0),
c3: F32x4::new(0.0, 0.0, 0.0, 1.0),
}
}
}
impl Transform3DF32 {
#[inline]
pub fn row_major(m00: f32, m01: f32, m02: f32, m03: f32,
m10: f32, m11: f32, m12: f32, m13: f32,
m20: f32, m21: f32, m22: f32, m23: f32,
m30: f32, m31: f32, m32: f32, m33: f32)
-> Transform3DF32 {
Transform3DF32 {
c0: F32x4::new(m00, m10, m20, m30),
c1: F32x4::new(m01, m11, m21, m31),
c2: F32x4::new(m02, m12, m22, m32),
c3: F32x4::new(m03, m13, m23, m33),
}
}
#[inline]
pub fn from_scale(x: f32, y: f32, z: f32) -> Transform3DF32 {
Transform3DF32::row_major( x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 0.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1.0)
}
#[inline]
pub fn from_uniform_scale(factor: f32) -> Transform3DF32 {
Transform3DF32::from_scale(factor, factor, factor)
}
#[inline]
pub fn from_translation(x: f32, y: f32, z: f32) -> Transform3DF32 {
Transform3DF32::row_major(1.0, 0.0, 0.0, x,
0.0, 1.0, 0.0, y,
0.0, 0.0, 1.0, z,
0.0, 0.0, 0.0, 1.0)
}
// TODO(pcwalton): Optimize.
pub fn from_rotation(yaw: f32, pitch: f32, roll: f32) -> Transform3DF32 {
let (cos_b, sin_b) = (yaw.cos(), yaw.sin());
let (cos_c, sin_c) = (pitch.cos(), pitch.sin());
let (cos_a, sin_a) = (roll.cos(), roll.sin());
let m00 = cos_a * cos_b;
let m01 = cos_a * sin_b * sin_c - sin_a * cos_c;
let m02 = cos_a * sin_b * cos_c + sin_a * sin_c;
let m10 = sin_a * cos_b;
let m11 = sin_a * sin_b * sin_c + cos_a * cos_c;
let m12 = sin_a * sin_b * cos_c - cos_a * sin_c;
let m20 = -sin_b;
let m21 = cos_b * sin_c;
let m22 = cos_b * cos_c;
Transform3DF32::row_major(m00, m01, m02, 0.0,
m10, m11, m12, 0.0,
m20, m21, m22, 0.0,
0.0, 0.0, 0.0, 1.0)
}
/// Creates a rotation matrix from the given quaternion.
///
/// The quaternion is expected to be packed into a SIMD type (x, y, z, w) corresponding to
/// x + yi + zj + wk.
pub fn from_rotation_quaternion(q: F32x4) -> Transform3DF32 {
// TODO(pcwalton): Optimize better with more shuffles.
let (mut sq, mut w, mut xy_xz_yz) = (q * q, q.wwww() * q, q.xxyy() * q.yzzy());
sq += sq; w += w; xy_xz_yz += xy_xz_yz;
let diag = F32x4::splat(1.0) - (sq.yxxy() + sq.zzyy());
let (wx2, wy2, wz2) = (w.x(), w.y(), w.z());
let (xy2, xz2, yz2) = (xy_xz_yz.x(), xy_xz_yz.y(), xy_xz_yz.z());
Transform3DF32::row_major(diag.x(), xy2 - wz2, xz2 + wy2, 0.0,
xy2 + wz2, diag.y(), yz2 - wx2, 0.0,
xz2 - wy2, yz2 + wx2, diag.z(), 0.0,
0.0, 0.0, 0.0, 1.0)
}
/// Just like `glOrtho()`.
#[inline]
pub fn from_ortho(left: f32, right: f32, bottom: f32, top: f32, near_val: f32, far_val: f32)
-> Transform3DF32 {
let x_inv = 1.0 / (right - left);
let y_inv = 1.0 / (top - bottom);
let z_inv = 1.0 / (far_val - near_val);
let tx = -(right + left) * x_inv;
let ty = -(top + bottom) * y_inv;
let tz = -(far_val + near_val) * z_inv;
Transform3DF32::row_major(2.0 * x_inv, 0.0, 0.0, tx,
0.0, 2.0 * y_inv, 0.0, ty,
0.0, 0.0, -2.0 * z_inv, tz,
0.0, 0.0, 0.0, 1.0)
}
/// Just like `gluPerspective()`.
#[inline]
pub fn from_perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Transform3DF32 {
let f = 1.0 / (fov_y * 0.5).tan();
let z_denom = 1.0 / (z_near - z_far);
let m00 = f / aspect;
let m11 = f;
let m22 = (z_far + z_near) * z_denom;
let m23 = 2.0 * z_far * z_near * z_denom;
let m32 = -1.0;
Transform3DF32::row_major(m00, 0.0, 0.0, 0.0,
0.0, m11, 0.0, 0.0,
0.0, 0.0, m22, m23,
0.0, 0.0, m32, 0.0)
}
// +- -+
// | A B |
// | C D |
// +- -+
#[inline]
pub fn from_submatrices(a: Matrix2x2F32, b: Matrix2x2F32, c: Matrix2x2F32, d: Matrix2x2F32)
-> Transform3DF32 {
Transform3DF32 {
c0: a.0.concat_xy_xy(c.0),
c1: a.0.concat_zw_zw(c.0),
c2: b.0.concat_xy_xy(d.0),
c3: b.0.concat_zw_zw(d.0),
}
}
// FIXME(pcwalton): Is this right, due to transposition? I think we may have to reverse the
// two.
//
// https://stackoverflow.com/a/18508113
#[inline]
pub fn pre_mul(&self, other: &Transform3DF32) -> Transform3DF32 {
return Transform3DF32 {
c0: mul_col(self.c0, other),
c1: mul_col(self.c1, other),
c2: mul_col(self.c2, other),
c3: mul_col(self.c3, other),
};
fn mul_col(a_col: F32x4, b: &Transform3DF32) -> F32x4 {
let (a0, a1) = (F32x4::splat(a_col[0]), F32x4::splat(a_col[1]));
let (a2, a3) = (F32x4::splat(a_col[2]), F32x4::splat(a_col[3]));
a0 * b.c0 + a1 * b.c1 + a2 * b.c2 + a3 * b.c3
}
}
#[inline]
pub fn post_mul(&self, other: &Transform3DF32) -> Transform3DF32 {
other.pre_mul(self)
}
#[inline]
pub fn transform_point(&self, point: Point3DF32) -> Point3DF32 {
let term0 = self.c0 * F32x4::splat(point.x());
let term1 = self.c1 * F32x4::splat(point.y());
let term2 = self.c2 * F32x4::splat(point.z());
let term3 = self.c3 * F32x4::splat(point.w());
Point3DF32(term0 + term1 + term2 + term3)
}
#[inline]
pub fn upper_left(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c0.concat_xy_xy(self.c1))
}
#[inline]
pub fn upper_right(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c2.concat_xy_xy(self.c3))
}
#[inline]
pub fn lower_left(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c0.concat_zw_zw(self.c1))
}
#[inline]
pub fn lower_right(&self) -> Matrix2x2F32 {
Matrix2x2F32(self.c2.concat_zw_zw(self.c3))
}
// https://en.wikipedia.org/wiki/Invertible_matrix#Blockwise_inversion
//
// If A is the upper left submatrix of this matrix, this method assumes that A and the Schur
// complement of A are invertible.
pub fn inverse(&self) -> Transform3DF32 {
// Extract submatrices.
let (a, b) = (self.upper_left(), self.upper_right());
let (c, d) = (self.lower_left(), self.lower_right());
// Compute temporary matrices.
let a_inv = a.inverse();
let x = c.post_mul(&a_inv);
let y = (d - x.post_mul(&b)).inverse();
let z = a_inv.post_mul(&b);
// Compute new submatrices.
let (a_new, b_new) = (a_inv + z.post_mul(&y).post_mul(&x), (-z).post_mul(&y));
let (c_new, d_new) = ((-y).post_mul(&x), y);
// Construct inverse.
Transform3DF32::from_submatrices(a_new, b_new, c_new, d_new)
}
pub fn approx_eq(&self, other: &Transform3DF32, epsilon: f32) -> bool {
self.c0.approx_eq(other.c0, epsilon) &&
self.c1.approx_eq(other.c1, epsilon) &&
self.c2.approx_eq(other.c2, epsilon) &&
self.c3.approx_eq(other.c3, epsilon)
}
#[inline]
pub fn as_ptr(&self) -> *const f32 {
(&self.c0) as *const F32x4 as *const f32
}
}
impl Add<Matrix2x2F32> for Matrix2x2F32 {
type Output = Matrix2x2F32;
#[inline]
fn add(self, other: Matrix2x2F32) -> Matrix2x2F32 {
Matrix2x2F32(self.0 + other.0)
}
}
impl Neg for Matrix2x2F32 {
type Output = Matrix2x2F32;
#[inline]
fn neg(self) -> Matrix2x2F32 {
Matrix2x2F32(-self.0)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Perspective {
pub transform: Transform3DF32,
pub window_size: Point2DI32,
}
impl Perspective {
#[inline]
pub fn new(transform: &Transform3DF32, window_size: Point2DI32) -> Perspective {
Perspective { transform: *transform, window_size }
}
#[inline]
pub fn transform_point_2d(&self, point: &Point2DF32) -> Point2DF32 {
let point = self.transform
.transform_point(point.to_3d())
.perspective_divide()
.to_2d() * Point2DF32::new(1.0, -1.0);
(point + Point2DF32::splat(1.0)) * self.window_size.to_f32().scale(0.5)
}
// TODO(pcwalton): SIMD?
#[inline]
pub fn transform_rect(&self, rect: RectF32) -> RectF32 {
let upper_left = self.transform_point_2d(&rect.origin());
let upper_right = self.transform_point_2d(&rect.upper_right());
let lower_left = self.transform_point_2d(&rect.lower_left());
let lower_right = self.transform_point_2d(&rect.lower_right());
let min_point = upper_left.min(upper_right).min(lower_left).min(lower_right);
let max_point = upper_left.max(upper_right).max(lower_left).max(lower_right);
RectF32::from_points(min_point, max_point)
}
#[inline]
pub fn post_mul(&self, other: &Transform3DF32) -> Perspective {
Perspective {
transform: self.transform.post_mul(other),
window_size: self.window_size,
}
}
}
/// Transforms a path with a perspective projection.
pub struct PerspectivePathIter<I>
where
I: Iterator<Item = Segment>,
{
iter: I,
perspective: Perspective,
}
impl<I> Iterator for PerspectivePathIter<I>
where
I: Iterator<Item = Segment>,
{
type Item = Segment;
#[inline]
fn next(&mut self) -> Option<Segment> {
let mut segment = self.iter.next()?;
if !segment.is_none() {
segment.baseline.set_from(&self.perspective
.transform_point_2d(&segment.baseline.from()));
segment.baseline.set_to(&self.perspective.transform_point_2d(&segment.baseline.to()));
if !segment.is_line() {
segment.ctrl.set_from(&self.perspective.transform_point_2d(&segment.ctrl.from()));
if !segment.is_quadratic() {
segment.ctrl.set_to(&self.perspective.transform_point_2d(&segment.ctrl.to()));
}
}
}
Some(segment)
}
}
impl<I> PerspectivePathIter<I>
where
I: Iterator<Item = Segment>,
{
#[inline]
pub fn new(iter: I, perspective: &Perspective) -> PerspectivePathIter<I> {
PerspectivePathIter { iter, perspective: *perspective }
}
}
#[cfg(test)]
mod test {
use crate::basic::point::Point3DF32;
use crate::basic::transform3d::Transform3DF32;
#[test]
fn test_post_mul() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let b = Transform3DF32::row_major(3.0, 8.0, 4.0, 6.0,
2.0, 6.0, 4.0, 3.0,
3.0, 8.0, 3.0, 2.0,
7.0, 9.0, 5.0, 0.0);
let c = Transform3DF32::row_major(58.0, 107.0, 53.0, 29.0,
84.0, 177.0, 87.0, 72.0,
106.0, 199.0, 101.0, 49.0,
62.0, 152.0, 83.0, 75.0);
assert_eq!(a.post_mul(&b), c);
}
#[test]
fn test_pre_mul() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let b = Transform3DF32::row_major(3.0, 8.0, 4.0, 6.0,
2.0, 6.0, 4.0, 3.0,
3.0, 8.0, 3.0, 2.0,
7.0, 9.0, 5.0, 0.0);
let c = Transform3DF32::row_major(135.0, 93.0, 110.0, 103.0,
93.0, 61.0, 85.0, 82.0,
104.0, 52.0, 90.0, 86.0,
117.0, 50.0, 122.0, 125.0);
assert_eq!(a.pre_mul(&b), c);
}
#[test]
fn test_transform_point() {
let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0,
9.0, 2.0, 6.0, 5.0,
3.0, 5.0, 8.0, 9.0,
7.0, 9.0, 3.0, 2.0);
let p = Point3DF32::new(3.0, 8.0, 4.0, 6.0);
let q = Point3DF32::new(63.0, 97.0, 135.0, 117.0);
assert_eq!(a.transform_point(p), q);
}
#[test]
fn test_inverse() {
// Random matrix.
let m = Transform3DF32::row_major(0.86277982, 0.15986552, 0.90739898, 0.60066808,
0.17386167, 0.016353 , 0.8535783 , 0.12969608,
0.0946466 , 0.43248631, 0.63480505, 0.08154603,
0.50305436, 0.48359687, 0.51057162, 0.24812012);
let p0 = Point3DF32::new(0.95536648, 0.80633691, 0.16357357, 0.5477598);
let p1 = m.transform_point(p0);
let m_inv = m.inverse();
let m_inv_exp =
Transform3DF32::row_major(-2.47290136 , 3.48865688, -6.12298336 , 6.17536696 ,
0.00124033357, -1.72561993, 2.16876606 , 0.186227748,
-0.375021729 , 1.53883017, -0.0558194403, 0.121857058,
5.78300323 , -6.87635769, 8.30196620 , -9.10374060);
assert!(m_inv.approx_eq(&m_inv_exp, 0.0001));
let p2 = m_inv.transform_point(p1);
assert!(p0.approx_eq(&p2, 0.0001));
}
}

508
geometry/src/clip.rs Normal file
View File

@ -0,0 +1,508 @@
// pathfinder/geometry/src/clip.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::{Point2DF32, Point3DF32};
use crate::basic::rect::RectF32;
use crate::outline::{Contour, PointFlags};
use crate::segment::{CubicSegment, Segment};
use crate::util::lerp;
use arrayvec::ArrayVec;
use smallvec::SmallVec;
use std::mem;
#[derive(Clone, Copy, Debug)]
struct Edge(LineSegmentF32);
impl TEdge for Edge {
#[inline]
fn point_is_inside(&self, point: &Point2DF32) -> bool {
let area = (self.0.to() - self.0.from()).det(*point - self.0.from());
//println!("point_is_inside({:?}, {:?}), area={}", self, point, area);
area >= 0.0
}
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]> {
let mut results = ArrayVec::new();
if let Some(t) = segment.intersection_t(&self.0) {
if t >= 0.0 && t <= 1.0 {
results.push(t);
}
}
results
}
}
#[derive(Clone, Copy, Debug)]
enum AxisAlignedEdge {
Left(f32),
Top(f32),
Right(f32),
Bottom(f32),
}
impl TEdge for AxisAlignedEdge {
#[inline]
fn point_is_inside(&self, point: &Point2DF32) -> bool {
match *self {
AxisAlignedEdge::Left(x) => point.x() >= x,
AxisAlignedEdge::Top(y) => point.y() >= y,
AxisAlignedEdge::Right(x) => point.x() <= x,
AxisAlignedEdge::Bottom(y) => point.y() <= y,
}
}
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]> {
let mut results = ArrayVec::new();
let t = match *self {
AxisAlignedEdge::Left(x) | AxisAlignedEdge::Right(x) => segment.solve_t_for_x(x),
AxisAlignedEdge::Top(y) | AxisAlignedEdge::Bottom(y) => segment.solve_t_for_y(y),
};
if t >= 0.0 && t <= 1.0 {
results.push(t);
}
results
}
}
trait TEdge {
fn point_is_inside(&self, point: &Point2DF32) -> bool;
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]>;
fn trivially_test_segment(&self, segment: &Segment) -> EdgeRelativeLocation {
let from_inside = self.point_is_inside(&segment.baseline.from());
//println!("point {:?} inside {:?}: {:?}", segment.baseline.from(), self, from_inside);
if from_inside != self.point_is_inside(&segment.baseline.to()) {
return EdgeRelativeLocation::Intersecting;
}
if !segment.is_line() {
if from_inside != self.point_is_inside(&segment.ctrl.from()) {
return EdgeRelativeLocation::Intersecting;
}
if !segment.is_quadratic() {
if from_inside != self.point_is_inside(&segment.ctrl.to()) {
return EdgeRelativeLocation::Intersecting;
}
}
}
if from_inside { EdgeRelativeLocation::Inside } else { EdgeRelativeLocation::Outside }
}
fn intersect_segment(&self, segment: &Segment) -> ArrayVec<[f32; 3]> {
if segment.is_line() {
return self.intersect_line_segment(&segment.baseline);
}
let mut segment = *segment;
if segment.is_quadratic() {
segment = segment.to_cubic();
}
let mut results = ArrayVec::new();
let mut prev_t = 0.0;
while !results.is_full() {
if prev_t >= 1.0 {
break
}
let next_t = match self.intersect_cubic_segment(&segment, prev_t, 1.0) {
None => break,
Some(next_t) => next_t,
};
results.push(next_t);
prev_t = next_t + EPSILON;
}
return results;
const EPSILON: f32 = 0.0001;
}
fn intersect_cubic_segment(&self, segment: &Segment, mut t_min: f32, mut t_max: f32)
-> Option<f32> {
/*println!("... intersect_cubic_segment({:?}, {:?}, t=({}, {}))",
self, segment, t_min, t_max);*/
let mut segment = segment.as_cubic_segment().split_after(t_min);
segment = segment.as_cubic_segment().split_before(t_max / (1.0 - t_min));
if !self.intersects_cubic_segment_hull(segment.as_cubic_segment()) {
return None
}
loop {
let t_mid = lerp(t_min, t_max, 0.5);
if t_max - t_min < 0.00001 {
return Some(t_mid);
}
let (prev_segment, next_segment) = segment.as_cubic_segment().split(0.5);
if self.intersects_cubic_segment_hull(prev_segment.as_cubic_segment()) {
t_max = t_mid;
segment = prev_segment;
} else if self.intersects_cubic_segment_hull(next_segment.as_cubic_segment()) {
t_min = t_mid;
segment = next_segment;
} else {
return None;
}
}
}
fn intersects_cubic_segment_hull(&self, cubic_segment: CubicSegment) -> bool {
let inside = self.point_is_inside(&cubic_segment.0.baseline.from());
inside != self.point_is_inside(&cubic_segment.0.ctrl.from()) ||
inside != self.point_is_inside(&cubic_segment.0.ctrl.to()) ||
inside != self.point_is_inside(&cubic_segment.0.baseline.to())
}
}
trait ContourClipper where Self::Edge: TEdge {
type Edge;
fn contour_mut(&mut self) -> &mut Contour;
fn clip_against(&mut self, edge: Self::Edge) {
// Fast path to avoid allocation in the no-clip case.
match self.check_for_fast_clip(&edge) {
FastClipResult::SlowPath => {}
FastClipResult::AllInside => return,
FastClipResult::AllOutside => {
*self.contour_mut() = Contour::new();
return;
}
}
let input = self.contour_mut().take();
for segment in input.iter() {
self.clip_segment_against(segment, &edge);
}
if input.is_closed() {
self.contour_mut().close();
}
}
fn clip_segment_against(&mut self, mut segment: Segment, edge: &Self::Edge) {
// Easy cases.
match edge.trivially_test_segment(&segment) {
EdgeRelativeLocation::Outside => return,
EdgeRelativeLocation::Inside => {
//println!("trivial test inside, pushing segment");
self.push_segment(&segment);
return;
}
EdgeRelativeLocation::Intersecting => {}
}
// We have a potential intersection.
//println!("potential intersection: {:?} edge: {:?}", segment, edge);
let mut starts_inside = edge.point_is_inside(&segment.baseline.from());
let intersection_ts = edge.intersect_segment(&segment);
let mut last_t = 0.0;
//println!("... intersections: {:?}", intersection_ts);
for t in intersection_ts {
let (before_split, after_split) = segment.split((t - last_t) / (1.0 - last_t));
// Push the split segment if appropriate.
/*println!("... ... edge={:?} before_split={:?} t={:?} starts_inside={:?}",
edge.0,
before_split,
t,
starts_inside);*/
if starts_inside {
//println!("... split segment case, pushing segment");
self.push_segment(&before_split);
}
// We've now transitioned from inside to outside or vice versa.
starts_inside = !starts_inside;
last_t = t;
segment = after_split;
}
// No more intersections. Push the last segment if applicable.
if starts_inside {
//println!("... last segment case, pushing segment");
self.push_segment(&segment);
}
}
fn push_segment(&mut self, segment: &Segment) {
//println!("... push_segment({:?}, edge={:?}", segment, edge);
let contour = self.contour_mut();
if let Some(last_position) = contour.last_position() {
if last_position != segment.baseline.from() {
// Add a line to join up segments.
contour.push_point(segment.baseline.from(), PointFlags::empty(), true);
}
}
contour.push_segment(*segment, true);
}
fn check_for_fast_clip(&mut self, edge: &Self::Edge) -> FastClipResult {
let mut result = None;
for segment in self.contour_mut().iter() {
let location = edge.trivially_test_segment(&segment);
match (result, location) {
(None, EdgeRelativeLocation::Outside) => {
result = Some(FastClipResult::AllOutside);
}
(None, EdgeRelativeLocation::Inside) => {
result = Some(FastClipResult::AllInside);
}
(Some(FastClipResult::AllInside), EdgeRelativeLocation::Inside) |
(Some(FastClipResult::AllOutside), EdgeRelativeLocation::Outside) => {}
(_, _) => return FastClipResult::SlowPath,
}
}
result.unwrap_or(FastClipResult::AllOutside)
}
}
#[derive(Clone, Copy)]
enum FastClipResult {
SlowPath,
AllInside,
AllOutside,
}
// General convex polygon clipping in 2D
pub(crate) struct ContourPolygonClipper {
clip_polygon: SmallVec<[Point2DF32; 4]>,
contour: Contour,
}
impl ContourClipper for ContourPolygonClipper {
type Edge = Edge;
#[inline]
fn contour_mut(&mut self) -> &mut Contour {
&mut self.contour
}
}
impl ContourPolygonClipper {
#[inline]
pub(crate) fn new(clip_polygon: &[Point2DF32], contour: Contour) -> ContourPolygonClipper {
ContourPolygonClipper { clip_polygon: SmallVec::from_slice(clip_polygon), contour }
}
pub(crate) fn clip(mut self) -> Contour {
// TODO(pcwalton): Maybe have a coarse circumscribed rect and use that for clipping?
let clip_polygon = mem::replace(&mut self.clip_polygon, SmallVec::default());
let mut prev = match clip_polygon.last() {
None => return Contour::new(),
Some(prev) => *prev,
};
for &next in &clip_polygon {
self.clip_against(Edge(LineSegmentF32::new(&prev, &next)));
prev = next;
}
self.contour
}
}
#[derive(PartialEq)]
enum EdgeRelativeLocation {
Intersecting,
Inside,
Outside,
}
// Fast axis-aligned box 2D clipping
pub(crate) struct ContourRectClipper {
clip_rect: RectF32,
contour: Contour,
}
impl ContourClipper for ContourRectClipper {
type Edge = AxisAlignedEdge;
#[inline]
fn contour_mut(&mut self) -> &mut Contour {
&mut self.contour
}
}
impl ContourRectClipper {
#[inline]
pub(crate) fn new(clip_rect: RectF32, contour: Contour) -> ContourRectClipper {
ContourRectClipper { clip_rect, contour }
}
pub(crate) fn clip(mut self) -> Contour {
if self.clip_rect.contains_rect(self.contour.bounds()) {
return self.contour
}
self.clip_against(AxisAlignedEdge::Left(self.clip_rect.min_x()));
self.clip_against(AxisAlignedEdge::Top(self.clip_rect.min_y()));
self.clip_against(AxisAlignedEdge::Right(self.clip_rect.max_x()));
self.clip_against(AxisAlignedEdge::Bottom(self.clip_rect.max_y()));
self.contour
}
}
// 3D quad clipping
pub struct PolygonClipper3D {
subject: Vec<Point3DF32>,
}
impl PolygonClipper3D {
#[inline]
pub fn new(subject: Vec<Point3DF32>) -> PolygonClipper3D {
PolygonClipper3D { subject }
}
pub fn clip(mut self) -> Vec<Point3DF32> {
// TODO(pcwalton): Fast path for completely contained polygon?
//println!("before clipping against bottom: {:?}", self.subject);
self.clip_against(Edge3D::Bottom);
//println!("before clipping against top: {:?}", self.subject);
self.clip_against(Edge3D::Top);
//println!("before clipping against left: {:?}", self.subject);
self.clip_against(Edge3D::Left);
//println!("before clipping against right: {:?}", self.subject);
self.clip_against(Edge3D::Right);
//println!("before clipping against far: {:?}", self.subject);
self.clip_against(Edge3D::Far);
//println!("before clipping against near: {:?}", self.subject);
self.clip_against(Edge3D::Near);
//println!("after clipping: {:?}", self.subject);
self.subject
}
fn clip_against(&mut self, edge: Edge3D) {
let input = mem::replace(&mut self.subject, vec![]);
let mut prev = match input.last() {
None => return,
Some(point) => *point,
};
for next in input {
if edge.point_is_inside(next) {
if !edge.point_is_inside(prev) {
self.subject.push(edge.line_intersection(prev, next));
}
self.subject.push(next);
} else if edge.point_is_inside(prev) {
self.subject.push(edge.line_intersection(prev, next));
}
prev = next;
}
}
}
#[derive(Clone, Copy, Debug)]
enum Edge3D {
Left,
Right,
Bottom,
Top,
Near,
Far
}
impl Edge3D {
#[inline]
fn point_is_inside(self, point: Point3DF32) -> bool {
let w = point.w();
match self {
Edge3D::Left => point.x() >= -w, Edge3D::Right => point.x() <= w,
Edge3D::Bottom => point.y() >= -w, Edge3D::Top => point.y() <= w,
Edge3D::Near => point.z() >= -w, Edge3D::Far => point.z() <= w,
}
}
// Blinn & Newell, "Clipping using homogeneous coordinates", SIGGRAPH 1978.
fn line_intersection(self, prev: Point3DF32, next: Point3DF32) -> Point3DF32 {
let (x0, x1) = match self {
Edge3D::Left | Edge3D::Right => (prev.x(), next.x()),
Edge3D::Bottom | Edge3D::Top => (prev.y(), next.y()),
Edge3D::Near | Edge3D::Far => (prev.z(), next.z()),
};
let (w0, w1) = (prev.w(), next.w());
let sign = match self {
Edge3D::Left | Edge3D::Bottom | Edge3D::Near => -1.0,
Edge3D::Right | Edge3D::Top | Edge3D::Far => 1.0,
};
let alpha = ((x0 - sign * w0) as f64) / ((sign * (w1 - w0) - (x1 - x0)) as f64);
prev.lerp(next, alpha as f32)
}
}
/// Coarse collision detection
// Separating axis theorem. Requires that the polygon be convex.
pub(crate) fn rect_is_outside_polygon(rect: RectF32, polygon_points: &[Point2DF32]) -> bool {
let mut outcode = Outcode::all();
for point in polygon_points {
if point.x() > rect.min_x() { outcode.remove(Outcode::LEFT); }
if point.x() < rect.max_x() { outcode.remove(Outcode::RIGHT); }
if point.y() > rect.min_y() { outcode.remove(Outcode::TOP); }
if point.y() < rect.max_y() { outcode.remove(Outcode::BOTTOM); }
}
if !outcode.is_empty() {
return true
}
// FIXME(pcwalton): Check winding!
let rect_points = [rect.origin(), rect.upper_right(), rect.lower_left(), rect.lower_right()];
for (next_point_index, &next) in polygon_points.iter().enumerate() {
let prev_point_index = if next_point_index == 0 {
polygon_points.len() - 1
} else {
next_point_index - 1
};
let prev = polygon_points[prev_point_index];
let polygon_edge_vector = next - prev;
if rect_points.iter().all(|&rect_point| polygon_edge_vector.det(rect_point - prev) < 0.0) {
return true
}
}
false
}
// Edge equation method. Requires that the polygon be convex.
pub(crate) fn rect_is_inside_polygon(rect: RectF32, polygon_points: &[Point2DF32]) -> bool {
// FIXME(pcwalton): Check winding!
let rect_points = [rect.origin(), rect.upper_right(), rect.lower_left(), rect.lower_right()];
for (next_point_index, &next) in polygon_points.iter().enumerate() {
let prev_point_index = if next_point_index == 0 {
polygon_points.len() - 1
} else {
next_point_index - 1
};
let prev = polygon_points[prev_point_index];
let polygon_edge_vector = next - prev;
for &rect_point in &rect_points {
if polygon_edge_vector.det(rect_point - prev) < 0.0 {
return false;
}
}
}
true
}
bitflags! {
struct Outcode: u8 {
const LEFT = 0x01;
const RIGHT = 0x02;
const TOP = 0x04;
const BOTTOM = 0x08;
}
}

83
geometry/src/color.rs Normal file
View File

@ -0,0 +1,83 @@
// pathfinder/geometry/src/color.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 pathfinder_simd::default::F32x4;
use std::fmt::{self, Debug, Formatter};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ColorU {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl ColorU {
#[inline]
pub fn black() -> ColorU {
ColorU {
r: 0,
g: 0,
b: 0,
a: 255,
}
}
#[inline]
pub fn to_f32(&self) -> ColorF {
let color = F32x4::new(self.r as f32, self.g as f32, self.b as f32, self.a as f32);
ColorF(color * F32x4::splat(1.0 / 255.0))
}
}
impl Debug for ColorU {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
if self.a == 255 {
write!(formatter, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
} else {
write!(formatter,
"rgba({}, {}, {}, {})",
self.r,
self.g,
self.b,
self.a as f32 / 255.0)
}
}
}
#[derive(Clone, Copy)]
pub struct ColorF(pub F32x4);
impl ColorF {
#[inline]
pub fn transparent_black() -> ColorF {
ColorF(F32x4::default())
}
#[inline]
pub fn r(&self) -> f32 {
self.0[0]
}
#[inline]
pub fn g(&self) -> f32 {
self.0[1]
}
#[inline]
pub fn b(&self) -> f32 {
self.0[2]
}
#[inline]
pub fn a(&self) -> f32 {
self.0[3]
}
}

129
geometry/src/dilation.rs Normal file
View File

@ -0,0 +1,129 @@
// pathfinder/geometry/src/dilation.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::basic::point::Point2DF32;
use crate::orientation::Orientation;
use crate::outline::Contour;
pub struct ContourDilator<'a> {
contour: &'a mut Contour,
amount: Point2DF32,
orientation: Orientation,
}
impl<'a> ContourDilator<'a> {
pub fn new(contour: &'a mut Contour, amount: Point2DF32, orientation: Orientation)
-> ContourDilator<'a> {
ContourDilator { contour, amount, orientation }
}
pub fn dilate(&mut self) {
// Determine orientation.
let scale = self.amount.scale_xy(match self.orientation {
Orientation::Ccw => Point2DF32::new( 1.0, -1.0),
Orientation::Cw => Point2DF32::new(-1.0, 1.0),
});
// Find the starting and previous positions.
let first_position = self.contour.position_of(0);
let mut prev_point_index = 0;
let mut prev_position;
loop {
prev_point_index = self.contour.prev_point_index_of(prev_point_index);
prev_position = self.contour.position_of(prev_point_index);
if prev_point_index == 0 || prev_position != first_position {
break;
}
}
// Initialize our loop.
let first_point_index = self.contour.next_point_index_of(prev_point_index);
let mut current_point_index = first_point_index;
let mut position = first_position;
let mut prev_vector = (position - prev_position).normalize();
loop {
// Find the next non-degenerate position.
let mut next_point_index = current_point_index;
let mut next_position;
loop {
next_point_index = self.contour.next_point_index_of(next_point_index);
if next_point_index == first_point_index {
next_position = first_position;
break;
}
next_position = self.contour.position_of(next_point_index);
if next_point_index == current_point_index || next_position != position {
break;
}
}
let next_vector = (next_position - position).normalize();
/*
println!("prev={} cur={} next={}",
prev_point_index,
current_point_index,
next_point_index);
*/
// Calculate new position by moving the point by the bisector.
let bisector = prev_vector.yx() + next_vector.yx();
let bisector_length = bisector.length();
let scaled_bisector = if bisector_length == 0.0 {
Point2DF32::default()
} else {
bisector.scale_xy(scale).scale(1.0 / bisector_length)
};
let new_position = position - scaled_bisector;
/*
println!("dilate(): prev={}({:?}) cur={}({:?}) next={}({:?}) bisector={:?}({:?}, {:?})",
prev_point_index,
prev_position,
current_point_index,
position,
next_point_index,
next_position,
bisector,
bisector_length,
scaled_bisector);
*/
/*if bisector_length == 0.0 {
println!("dilate({:?}): {:?} -> {:?} (bisector {:?}, length {:?})",
self.amount,
position,
new_position,
bisector,
bisector_length);
}*/
// Update all points.
let mut point_index = current_point_index;
while point_index != next_point_index {
self.contour.points[point_index as usize] = new_position;
//println!("... updating {:?}", point_index);
point_index = self.contour.next_point_index_of(point_index);
}
// Check to see if we're done.
if next_point_index == first_point_index {
break;
}
// Continue.
prev_vector = next_vector;
position = next_position;
current_point_index = next_point_index;
}
}
}

View File

@ -0,0 +1,67 @@
// pathfinder/geometry/src/distortion.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::basic::point::{Point2DF32, Point2DI32};
use crate::outline::{self, Contour};
#[derive(Clone, Copy, Debug)]
pub struct BarrelDistortionCoefficients {
pub k0: f32,
pub k1: f32,
}
impl Default for BarrelDistortionCoefficients {
// Matches Google Daydream (Cardboard v2.2).
#[inline]
fn default() -> BarrelDistortionCoefficients {
BarrelDistortionCoefficients { k0: 0.34, k1: 0.55 }
}
}
pub struct ContourBarrelDistorter<'a> {
contour: &'a mut Contour,
window_size: Point2DI32,
coefficients: BarrelDistortionCoefficients,
}
impl<'a> ContourBarrelDistorter<'a> {
pub fn new(contour: &'a mut Contour,
coefficients: BarrelDistortionCoefficients,
window_size: Point2DI32)
-> ContourBarrelDistorter<'a> {
ContourBarrelDistorter { contour, window_size, coefficients }
}
pub fn distort(&mut self) {
let one = Point2DF32::splat(1.0);
let window_size = self.window_size.to_f32();
let inv_window_size = Point2DF32(window_size.0.approx_recip());
let BarrelDistortionCoefficients { k0, k1 } = self.coefficients;
let point_count = self.contour.len();
for point_index in 0..point_count {
// Convert from window coordinates to NDC.
let mut position = self.contour.position_of(point_index);
position = position.scale_xy(inv_window_size).scale(2.0) - one;
// Apply distortion.
let r2 = position.square_length();
let scaling = 1.0 + k0 * r2 + k1 * r2 * r2;
position = position.scale(1.0 / scaling);
// Convert back to window coordinates.
position = (position + one).scale(0.5).scale_xy(window_size);
// Store resulting point.
self.contour.points[point_index as usize] = position;
outline::union_rect(&mut self.contour.bounds, position, point_index == 0);
}
}
}

29
geometry/src/lib.rs Normal file
View File

@ -0,0 +1,29 @@
// pathfinder/geometry/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Utilities for Bézier curves.
//!
//! These may be merged into upstream Lyon eventually.
#[macro_use]
extern crate bitflags;
pub mod basic;
pub mod clip;
pub mod color;
pub mod distortion;
pub mod monotonic;
pub mod orientation;
pub mod outline;
pub mod segment;
pub mod stroke;
pub mod util;
mod dilation;

78
geometry/src/monotonic.rs Normal file
View File

@ -0,0 +1,78 @@
// pathfinder/geometry/src/monotonic.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Converts paths to monotonically increasing/decreasing segments in Y.
use crate::segment::{Segment, SegmentKind};
use arrayvec::ArrayVec;
pub struct MonotonicConversionIter<I>
where
I: Iterator<Item = Segment>,
{
iter: I,
buffer: ArrayVec<[Segment; 2]>,
}
impl<I> Iterator for MonotonicConversionIter<I>
where
I: Iterator<Item = Segment>,
{
type Item = Segment;
#[inline]
fn next(&mut self) -> Option<Segment> {
if let Some(segment) = self.buffer.pop() {
return Some(segment);
}
let segment = self.iter.next()?;
match segment.kind {
SegmentKind::None => self.next(),
SegmentKind::Line => Some(segment),
SegmentKind::Cubic => self.handle_cubic(&segment),
SegmentKind::Quadratic => {
// TODO(pcwalton): Don't degree elevate!
self.handle_cubic(&segment.to_cubic())
}
}
}
}
impl<I> MonotonicConversionIter<I>
where
I: Iterator<Item = Segment>,
{
#[inline]
pub fn new(iter: I) -> MonotonicConversionIter<I> {
MonotonicConversionIter {
iter,
buffer: ArrayVec::new(),
}
}
pub fn handle_cubic(&mut self, segment: &Segment) -> Option<Segment> {
match segment.as_cubic_segment().y_extrema() {
(Some(t0), Some(t1)) => {
let (segments_01, segment_2) = segment.as_cubic_segment().split(t1);
self.buffer.push(segment_2);
let (segment_0, segment_1) = segments_01.as_cubic_segment().split(t0 / t1);
self.buffer.push(segment_1);
Some(segment_0)
}
(Some(t0), None) | (None, Some(t0)) => {
let (segment_0, segment_1) = segment.as_cubic_segment().split(t0);
self.buffer.push(segment_1);
Some(segment_0)
}
(None, None) => Some(*segment),
}
}
}

View File

@ -0,0 +1,43 @@
// pathfinder/geometry/src/orientation.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::outline::Outline;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Orientation {
Ccw = -1,
Cw = 1,
}
impl Orientation {
/// This follows the FreeType algorithm.
pub fn from_outline(outline: &Outline) -> Orientation {
let mut area = 0.0;
for contour in &outline.contours {
let mut prev_position = match contour.last_position() {
None => continue,
Some(position) => position,
};
for &next_position in &contour.points {
area += prev_position.det(next_position);
prev_position = next_position;
}
}
Orientation::from_area(area)
}
fn from_area(area: f32) -> Orientation {
if area <= 0.0 {
Orientation::Ccw
} else {
Orientation::Cw
}
}
}

694
geometry/src/outline.rs Normal file
View File

@ -0,0 +1,694 @@
// pathfinder/geometry/src/outline.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 compressed in-memory representation of paths.
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::{Point2DF32, Point2DI32};
use crate::basic::rect::RectF32;
use crate::basic::transform2d::Transform2DF32;
use crate::basic::transform3d::Perspective;
use crate::clip::{self, ContourPolygonClipper, ContourRectClipper};
use crate::dilation::ContourDilator;
use crate::distortion::{BarrelDistortionCoefficients, ContourBarrelDistorter};
use crate::orientation::Orientation;
use crate::segment::{Segment, SegmentFlags, SegmentKind};
use std::fmt::{self, Debug, Formatter};
use std::mem;
#[derive(Clone)]
pub struct Outline {
pub contours: Vec<Contour>,
pub(crate) bounds: RectF32,
}
#[derive(Clone)]
pub struct Contour {
pub(crate) points: Vec<Point2DF32>,
pub(crate) flags: Vec<PointFlags>,
pub(crate) bounds: RectF32,
pub(crate) closed: bool,
}
bitflags! {
pub struct PointFlags: u8 {
const CONTROL_POINT_0 = 0x01;
const CONTROL_POINT_1 = 0x02;
}
}
impl Outline {
#[inline]
pub fn new() -> Outline {
Outline { contours: vec![], bounds: RectF32::default() }
}
#[inline]
pub fn from_segments<I>(segments: I) -> Outline
where
I: Iterator<Item = Segment>,
{
let mut outline = Outline::new();
let mut current_contour = Contour::new();
let mut bounds = None;
for segment in segments {
if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) {
if !current_contour.is_empty() {
outline
.contours
.push(mem::replace(&mut current_contour, Contour::new()));
}
current_contour.push_point(segment.baseline.from(), PointFlags::empty(), true);
}
if segment.flags.contains(SegmentFlags::CLOSES_SUBPATH) {
if !current_contour.is_empty() {
current_contour.close();
let contour = mem::replace(&mut current_contour, Contour::new());
contour.update_bounds(&mut bounds);
outline.contours.push(contour);
}
continue;
}
if segment.is_none() {
continue;
}
if !segment.is_line() {
current_contour.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, true);
if !segment.is_quadratic() {
current_contour.push_point(segment.ctrl.to(),
PointFlags::CONTROL_POINT_1,
true);
}
}
current_contour.push_point(segment.baseline.to(), PointFlags::empty(), true);
}
if !current_contour.is_empty() {
current_contour.update_bounds(&mut bounds);
outline.contours.push(current_contour);
}
if let Some(bounds) = bounds {
outline.bounds = bounds;
}
outline
}
#[inline]
pub fn bounds(&self) -> RectF32 {
self.bounds
}
pub fn transform(&mut self, transform: &Transform2DF32) {
let mut new_bounds = None;
for contour in &mut self.contours {
contour.transform(transform);
contour.update_bounds(&mut new_bounds);
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
pub fn apply_perspective(&mut self, perspective: &Perspective) {
let mut new_bounds = None;
for contour in &mut self.contours {
contour.apply_perspective(perspective);
contour.update_bounds(&mut new_bounds);
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
pub fn dilate(&mut self, amount: Point2DF32) {
let orientation = Orientation::from_outline(self);
self.contours.iter_mut().for_each(|contour| contour.dilate(amount, orientation));
self.bounds = self.bounds.dilate(amount);
}
pub fn barrel_distort(&mut self,
coefficients: BarrelDistortionCoefficients,
window_size: Point2DI32) {
let mut new_bounds = None;
for contour in &mut self.contours {
contour.barrel_distort(coefficients, window_size);
contour.update_bounds(&mut new_bounds);
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
pub fn prepare_for_tiling(&mut self, view_box: RectF32) {
self.contours.iter_mut().for_each(|contour| contour.prepare_for_tiling(view_box));
self.bounds = self.bounds.intersection(view_box).unwrap_or_else(|| RectF32::default());
}
pub fn is_outside_polygon(&self, clip_polygon: &[Point2DF32]) -> bool {
clip::rect_is_outside_polygon(self.bounds, clip_polygon)
}
fn is_inside_polygon(&self, clip_polygon: &[Point2DF32]) -> bool {
clip::rect_is_inside_polygon(self.bounds, clip_polygon)
}
pub fn clip_against_polygon(&mut self, clip_polygon: &[Point2DF32]) {
// Quick check.
if self.is_inside_polygon(clip_polygon) {
return;
}
let mut new_bounds = None;
for contour in mem::replace(&mut self.contours, vec![]) {
let contour = ContourPolygonClipper::new(clip_polygon, contour).clip();
if !contour.is_empty() {
contour.update_bounds(&mut new_bounds);
self.contours.push(contour);
}
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
pub fn clip_against_rect(&mut self, clip_rect: RectF32) {
if clip_rect.contains_rect(self.bounds) {
return;
}
let mut new_bounds = None;
for contour in mem::replace(&mut self.contours, vec![]) {
let contour = ContourRectClipper::new(clip_rect, contour).clip();
if !contour.is_empty() {
contour.update_bounds(&mut new_bounds);
self.contours.push(contour);
}
}
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
}
impl Debug for Outline {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
for (contour_index, contour) in self.contours.iter().enumerate() {
if contour_index > 0 {
write!(formatter, " ")?;
}
contour.fmt(formatter)?;
}
Ok(())
}
}
impl Contour {
#[inline]
pub fn new() -> Contour {
Contour { points: vec![], flags: vec![], bounds: RectF32::default(), closed: false }
}
// Replaces this contour with a new one, with arrays preallocated to match `self`.
#[inline]
pub(crate) fn take(&mut self) -> Contour {
let length = self.len() as usize;
mem::replace(self, Contour {
points: Vec::with_capacity(length),
flags: Vec::with_capacity(length),
bounds: RectF32::default(),
closed: false,
})
}
#[inline]
pub fn iter(&self) -> ContourIter {
ContourIter { contour: self, index: 1 }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
#[inline]
pub fn len(&self) -> u32 {
self.points.len() as u32
}
#[inline]
pub fn bounds(&self) -> RectF32 {
self.bounds
}
#[inline]
pub fn is_closed(&self) -> bool {
self.closed
}
#[inline]
pub fn position_of(&self, index: u32) -> Point2DF32 {
self.points[index as usize]
}
#[inline]
pub(crate) fn last_position(&self) -> Option<Point2DF32> {
self.points.last().cloned()
}
#[inline]
pub(crate) fn close(&mut self) {
self.closed = true;
}
// TODO(pcwalton): SIMD.
#[inline]
pub(crate) fn push_point(&mut self,
point: Point2DF32,
flags: PointFlags,
update_bounds: bool) {
debug_assert!(!point.x().is_nan() && !point.y().is_nan());
if update_bounds {
let first = self.is_empty();
union_rect(&mut self.bounds, point, first);
}
self.points.push(point);
self.flags.push(flags);
}
#[inline]
pub(crate) fn push_segment(&mut self, segment: Segment, update_bounds: bool) {
if segment.is_none() {
return
}
if self.is_empty() {
self.push_point(segment.baseline.from(), PointFlags::empty(), update_bounds);
}
if !segment.is_line() {
self.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, update_bounds);
if !segment.is_quadratic() {
self.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1, update_bounds);
}
}
self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds);
}
#[inline]
pub(crate) fn push_full_segment(&mut self, segment: &Segment, update_bounds: bool) {
self.push_point(segment.baseline.from(), PointFlags::empty(), update_bounds);
if !segment.is_line() {
self.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, update_bounds);
if !segment.is_quadratic() {
self.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1, update_bounds);
}
}
self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds);
}
#[inline]
pub fn segment_after(&self, point_index: u32) -> Segment {
debug_assert!(self.point_is_endpoint(point_index));
let mut segment = Segment::none();
segment.baseline.set_from(&self.position_of(point_index));
let point1_index = self.add_to_point_index(point_index, 1);
if self.point_is_endpoint(point1_index) {
segment.baseline.set_to(&self.position_of(point1_index));
segment.kind = SegmentKind::Line;
} else {
segment.ctrl.set_from(&self.position_of(point1_index));
let point2_index = self.add_to_point_index(point_index, 2);
if self.point_is_endpoint(point2_index) {
segment.baseline.set_to(&self.position_of(point2_index));
segment.kind = SegmentKind::Quadratic;
} else {
segment.ctrl.set_to(&self.position_of(point2_index));
segment.kind = SegmentKind::Cubic;
let point3_index = self.add_to_point_index(point_index, 3);
segment.baseline.set_to(&self.position_of(point3_index));
}
}
segment
}
#[inline]
pub fn hull_segment_after(&self, prev_point_index: u32) -> LineSegmentF32 {
let next_point_index = self.next_point_index_of(prev_point_index);
LineSegmentF32::new(&self.points[prev_point_index as usize],
&self.points[next_point_index as usize])
}
#[inline]
pub fn point_is_endpoint(&self, point_index: u32) -> bool {
!self.flags[point_index as usize]
.intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1)
}
#[inline]
pub fn add_to_point_index(&self, point_index: u32, addend: u32) -> u32 {
let (index, limit) = (point_index + addend, self.len());
if index >= limit {
index - limit
} else {
index
}
}
#[inline]
pub fn point_is_logically_above(&self, a: u32, b: u32) -> bool {
let (a_y, b_y) = (self.points[a as usize].y(), self.points[b as usize].y());
a_y < b_y || (a_y == b_y && a < b)
}
#[inline]
pub fn prev_endpoint_index_of(&self, mut point_index: u32) -> u32 {
loop {
point_index = self.prev_point_index_of(point_index);
if self.point_is_endpoint(point_index) {
return point_index;
}
}
}
#[inline]
pub fn next_endpoint_index_of(&self, mut point_index: u32) -> u32 {
loop {
point_index = self.next_point_index_of(point_index);
if self.point_is_endpoint(point_index) {
return point_index;
}
}
}
#[inline]
pub fn prev_point_index_of(&self, point_index: u32) -> u32 {
if point_index == 0 {
self.len() - 1
} else {
point_index - 1
}
}
#[inline]
pub fn next_point_index_of(&self, point_index: u32) -> u32 {
if point_index == self.len() - 1 {
0
} else {
point_index + 1
}
}
pub fn transform(&mut self, transform: &Transform2DF32) {
for (point_index, point) in self.points.iter_mut().enumerate() {
*point = transform.transform_point(point);
union_rect(&mut self.bounds, *point, point_index == 0);
}
}
pub fn apply_perspective(&mut self, perspective: &Perspective) {
for (point_index, point) in self.points.iter_mut().enumerate() {
*point = perspective.transform_point_2d(point);
union_rect(&mut self.bounds, *point, point_index == 0);
}
}
pub fn dilate(&mut self, amount: Point2DF32, orientation: Orientation) {
ContourDilator::new(self, amount, orientation).dilate();
self.bounds = self.bounds.dilate(amount);
}
pub fn barrel_distort(&mut self,
coefficients: BarrelDistortionCoefficients,
window_size: Point2DI32) {
ContourBarrelDistorter::new(self, coefficients, window_size).distort();
}
fn prepare_for_tiling(&mut self, view_box: RectF32) {
// Snap points to the view box bounds. This mops up floating point error from the clipping
// process.
let (mut last_endpoint_index, mut contour_is_monotonic) = (None, true);
for point_index in 0..(self.points.len() as u32) {
if contour_is_monotonic {
if self.point_is_endpoint(point_index) {
if let Some(last_endpoint_index) = last_endpoint_index {
if !self.curve_with_endpoints_is_monotonic(last_endpoint_index,
point_index) {
contour_is_monotonic = false;
}
}
last_endpoint_index = Some(point_index);
}
}
}
// Convert to monotonic, if necessary.
if !contour_is_monotonic {
self.make_monotonic();
}
// Update bounds.
self.bounds = self.bounds.intersection(view_box).unwrap_or_else(|| RectF32::default());
}
fn make_monotonic(&mut self) {
let contour = self.take();
self.bounds = contour.bounds;
let mut last_endpoint_index = None;
let input_point_count = contour.points.len() as u32;
for point_index in 0..(input_point_count + 1) {
if point_index < input_point_count && !contour.point_is_endpoint(point_index) {
continue;
}
if let Some(last_endpoint_index) = last_endpoint_index {
let position_index =
if point_index == input_point_count { 0 } else { point_index };
let baseline = LineSegmentF32::new(&contour.points[last_endpoint_index as usize],
&contour.points[position_index as usize]);
let point_count = point_index - last_endpoint_index + 1;
if point_count == 3 {
let ctrl_point_index = last_endpoint_index as usize + 1;
let ctrl_position = &contour.points[ctrl_point_index];
handle_cubic(self, Segment::quadratic(&baseline, &ctrl_position).to_cubic());
} else if point_count == 4 {
let first_ctrl_point_index = last_endpoint_index as usize + 1;
let ctrl_position_0 = &contour.points[first_ctrl_point_index + 0];
let ctrl_position_1 = &contour.points[first_ctrl_point_index + 1];
let ctrl = LineSegmentF32::new(&ctrl_position_0, &ctrl_position_1);
handle_cubic(self, Segment::cubic(&baseline, &ctrl));
}
self.push_point(contour.points[position_index as usize],
PointFlags::empty(),
false);
}
last_endpoint_index = Some(point_index);
}
fn handle_cubic(contour: &mut Contour, segment: Segment) {
match segment.as_cubic_segment().y_extrema() {
(Some(t0), Some(t1)) => {
let (segments_01, segment_2) = segment.as_cubic_segment().split(t1);
let (segment_0, segment_1) = segments_01.as_cubic_segment().split(t0 / t1);
contour.push_segment(segment_0, false);
contour.push_segment(segment_1, false);
contour.push_segment(segment_2, false);
}
(Some(t0), None) | (None, Some(t0)) => {
let (segment_0, segment_1) = segment.as_cubic_segment().split(t0);
contour.push_segment(segment_0, false);
contour.push_segment(segment_1, false);
}
(None, None) => contour.push_segment(segment, false),
}
}
}
fn curve_with_endpoints_is_monotonic(&self, start_endpoint_index: u32, end_endpoint_index: u32)
-> bool {
let start_position = self.points[start_endpoint_index as usize];
let end_position = self.points[end_endpoint_index as usize];
if start_position.x() <= end_position.x() {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].x() >
self.points[point_index as usize + 1].x() {
return false;
}
}
} else {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].x() <
self.points[point_index as usize + 1].x() {
return false;
}
}
}
if start_position.y() <= end_position.y() {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].y() >
self.points[point_index as usize + 1].y() {
return false;
}
}
} else {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].y() <
self.points[point_index as usize + 1].y() {
return false;
}
}
}
true
}
pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF32>) {
*bounds = Some(match *bounds {
None => self.bounds,
Some(bounds) => bounds.union_rect(self.bounds),
})
}
}
impl Debug for Contour {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
for (segment_index, segment) in self.iter().enumerate() {
if segment_index == 0 {
write!(formatter,
"M {} {}",
segment.baseline.from_x(),
segment.baseline.from_y())?;
}
match segment.kind {
SegmentKind::None => {}
SegmentKind::Line => {
write!(formatter,
" L {} {}",
segment.baseline.to_x(),
segment.baseline.to_y())?;
}
SegmentKind::Quadratic => {
write!(formatter,
" Q {} {} {} {}",
segment.ctrl.from_x(),
segment.ctrl.from_y(),
segment.baseline.to_x(),
segment.baseline.to_y())?;
}
SegmentKind::Cubic => {
write!(formatter,
" C {} {} {} {} {} {}",
segment.ctrl.from_x(),
segment.ctrl.from_y(),
segment.ctrl.to_x(),
segment.ctrl.to_y(),
segment.baseline.to_x(),
segment.baseline.to_y())?;
}
}
}
if self.closed {
write!(formatter, " z")?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct PointIndex(u32);
impl PointIndex {
#[inline]
pub fn new(contour: u32, point: u32) -> PointIndex {
debug_assert!(contour <= 0xfff);
debug_assert!(point <= 0x000f_ffff);
PointIndex((contour << 20) | point)
}
#[inline]
pub fn contour(self) -> u32 {
self.0 >> 20
}
#[inline]
pub fn point(self) -> u32 {
self.0 & 0x000f_ffff
}
}
pub struct ContourIter<'a> {
contour: &'a Contour,
index: u32,
}
impl<'a> Iterator for ContourIter<'a> {
type Item = Segment;
#[inline]
fn next(&mut self) -> Option<Segment> {
let contour = self.contour;
if (self.index == contour.len() && !self.contour.closed) ||
self.index == contour.len() + 1 {
return None;
}
let point0_index = self.index - 1;
let point0 = contour.position_of(point0_index);
if self.index == contour.len() {
let point1 = contour.position_of(0);
self.index += 1;
return Some(Segment::line(&LineSegmentF32::new(&point0, &point1)));
}
let point1_index = self.index;
self.index += 1;
let point1 = contour.position_of(point1_index);
if contour.point_is_endpoint(point1_index) {
return Some(Segment::line(&LineSegmentF32::new(&point0, &point1)));
}
let point2_index = self.index;
let point2 = contour.position_of(point2_index);
self.index += 1;
if contour.point_is_endpoint(point2_index) {
return Some(Segment::quadratic(&LineSegmentF32::new(&point0, &point2), &point1));
}
let point3_index = self.index;
let point3 = contour.position_of(point3_index);
self.index += 1;
debug_assert!(contour.point_is_endpoint(point3_index));
return Some(Segment::cubic(&LineSegmentF32::new(&point0, &point3),
&LineSegmentF32::new(&point1, &point2)));
}
}
#[inline]
pub(crate) fn union_rect(bounds: &mut RectF32, new_point: Point2DF32, first: bool) {
if first {
*bounds = RectF32::from_points(new_point, new_point);
} else {
*bounds = bounds.union_point(new_point)
}
}

327
geometry/src/segment.rs Normal file
View File

@ -0,0 +1,327 @@
// pathfinder/geometry/src/segment.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Line or curve segments, optimized with SIMD.
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::Point2DF32;
use pathfinder_simd::default::F32x4;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Segment {
pub baseline: LineSegmentF32,
pub ctrl: LineSegmentF32,
pub kind: SegmentKind,
pub flags: SegmentFlags,
}
impl Segment {
#[inline]
pub fn none() -> Segment {
Segment {
baseline: LineSegmentF32::default(),
ctrl: LineSegmentF32::default(),
kind: SegmentKind::None,
flags: SegmentFlags::empty(),
}
}
#[inline]
pub fn line(line: &LineSegmentF32) -> Segment {
Segment {
baseline: *line,
ctrl: LineSegmentF32::default(),
kind: SegmentKind::Line,
flags: SegmentFlags::empty(),
}
}
#[inline]
pub fn quadratic(baseline: &LineSegmentF32, ctrl: &Point2DF32) -> Segment {
Segment {
baseline: *baseline,
ctrl: LineSegmentF32::new(ctrl, &Point2DF32::default()),
kind: SegmentKind::Cubic,
flags: SegmentFlags::empty(),
}
}
#[inline]
pub fn cubic(baseline: &LineSegmentF32, ctrl: &LineSegmentF32) -> Segment {
Segment {
baseline: *baseline,
ctrl: *ctrl,
kind: SegmentKind::Cubic,
flags: SegmentFlags::empty(),
}
}
#[inline]
pub fn as_line_segment(&self) -> LineSegmentF32 {
debug_assert!(self.is_line());
self.baseline
}
#[inline]
pub fn is_none(&self) -> bool {
self.kind == SegmentKind::None
}
#[inline]
pub fn is_line(&self) -> bool {
self.kind == SegmentKind::Line
}
#[inline]
pub fn is_quadratic(&self) -> bool {
self.kind == SegmentKind::Quadratic
}
#[inline]
pub fn is_cubic(&self) -> bool {
self.kind == SegmentKind::Cubic
}
#[inline]
pub fn as_cubic_segment(&self) -> CubicSegment {
debug_assert!(self.is_cubic());
CubicSegment(self)
}
// FIXME(pcwalton): We should basically never use this function.
// FIXME(pcwalton): Handle lines!
#[inline]
pub fn to_cubic(&self) -> Segment {
if self.is_cubic() {
return *self;
}
let mut new_segment = *self;
let p1_2 = self.ctrl.from() + self.ctrl.from();
new_segment.ctrl =
LineSegmentF32::new(&(self.baseline.from() + p1_2), &(p1_2 + self.baseline.to()))
.scale(1.0 / 3.0);
new_segment
}
#[inline]
pub fn is_monotonic(&self) -> bool {
// FIXME(pcwalton): Don't degree elevate!
match self.kind {
SegmentKind::None | SegmentKind::Line => true,
SegmentKind::Quadratic => self.to_cubic().as_cubic_segment().is_monotonic(),
SegmentKind::Cubic => self.as_cubic_segment().is_monotonic(),
}
}
#[inline]
pub fn reversed(&self) -> Segment {
Segment {
baseline: self.baseline.reversed(),
ctrl: if self.is_quadratic() {
self.ctrl
} else {
self.ctrl.reversed()
},
kind: self.kind,
flags: self.flags,
}
}
// Reverses if necessary so that the from point is above the to point. Calling this method
// again will undo the transformation.
#[inline]
pub fn orient(&self, y_winding: i32) -> Segment {
if y_winding >= 0 {
*self
} else {
self.reversed()
}
}
#[inline]
pub fn is_tiny(&self) -> bool {
const EPSILON: f32 = 0.0001;
self.baseline.square_length() < EPSILON
}
#[inline]
pub fn split(&self, t: f32) -> (Segment, Segment) {
// FIXME(pcwalton): Don't degree elevate!
if self.is_line() {
let (before, after) = self.as_line_segment().split(t);
(Segment::line(&before), Segment::line(&after))
} else {
self.to_cubic().as_cubic_segment().split(t)
}
}
#[inline]
pub fn sample(self, t: f32) -> Point2DF32 {
// FIXME(pcwalton): Don't degree elevate!
if self.is_line() {
self.as_line_segment().sample(t)
} else {
self.to_cubic().as_cubic_segment().sample(t)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum SegmentKind {
None,
Line,
Quadratic,
Cubic,
}
bitflags! {
pub struct SegmentFlags: u8 {
const FIRST_IN_SUBPATH = 0x01;
const CLOSES_SUBPATH = 0x02;
}
}
#[derive(Clone, Copy, Debug)]
pub struct CubicSegment<'s>(pub &'s Segment);
impl<'s> CubicSegment<'s> {
// See Kaspar Fischer, "Piecewise Linear Approximation of Bézier Curves", 2000.
#[inline]
pub fn is_flat(self, tolerance: f32) -> bool {
let mut uv = F32x4::splat(3.0) * self.0.ctrl.0 -
self.0.baseline.0 - self.0.baseline.0 -
self.0.baseline.reversed().0;
uv = uv * uv;
uv = uv.max(uv.zwxy());
uv[0] + uv[1] <= 16.0 * tolerance * tolerance
}
#[inline]
pub fn split(self, t: f32) -> (Segment, Segment) {
let (baseline0, ctrl0, baseline1, ctrl1);
if t <= 0.0 {
let from = &self.0.baseline.from();
baseline0 = LineSegmentF32::new(from, from);
ctrl0 = LineSegmentF32::new(from, from);
baseline1 = self.0.baseline;
ctrl1 = self.0.ctrl;
} else if t >= 1.0 {
let to = &self.0.baseline.to();
baseline0 = self.0.baseline;
ctrl0 = self.0.ctrl;
baseline1 = LineSegmentF32::new(to, to);
ctrl1 = LineSegmentF32::new(to, to);
} else {
let tttt = F32x4::splat(t);
let (p0p3, p1p2) = (self.0.baseline.0, self.0.ctrl.0);
let p0p1 = p0p3.concat_xy_xy(p1p2);
// p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t)
let p01p12 = p0p1 + tttt * (p1p2 - p0p1);
let pxxp23 = p1p2 + tttt * (p0p3 - p1p2);
let p12p23 = p01p12.concat_zw_zw(pxxp23);
// p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t)
let p012p123 = p01p12 + tttt * (p12p23 - p01p12);
let p123 = p012p123.zwzw();
// p0123 = lerp(p012, p123, t)
let p0123 = p012p123 + tttt * (p123 - p012p123);
baseline0 = LineSegmentF32(p0p3.concat_xy_xy(p0123));
ctrl0 = LineSegmentF32(p01p12.concat_xy_xy(p012p123));
baseline1 = LineSegmentF32(p0123.concat_xy_zw(p0p3));
ctrl1 = LineSegmentF32(p012p123.concat_zw_zw(p12p23));
}
(Segment {
baseline: baseline0,
ctrl: ctrl0,
kind: SegmentKind::Cubic,
flags: self.0.flags & SegmentFlags::FIRST_IN_SUBPATH,
}, Segment {
baseline: baseline1,
ctrl: ctrl1,
kind: SegmentKind::Cubic,
flags: self.0.flags & SegmentFlags::CLOSES_SUBPATH,
})
}
#[inline]
pub fn split_before(self, t: f32) -> Segment {
self.split(t).0
}
#[inline]
pub fn split_after(self, t: f32) -> Segment {
self.split(t).1
}
// FIXME(pcwalton): Use Horner's method!
#[inline]
pub fn sample(self, t: f32) -> Point2DF32 {
self.split(t).0.baseline.to()
}
#[inline]
pub fn is_monotonic(self) -> bool {
// TODO(pcwalton): Optimize this.
let (p0, p3) = (self.0.baseline.from_y(), self.0.baseline.to_y());
let (p1, p2) = (self.0.ctrl.from_y(), self.0.ctrl.to_y());
(p0 <= p1 && p1 <= p2 && p2 <= p3) || (p0 >= p1 && p1 >= p2 && p2 >= p3)
}
#[inline]
pub fn y_extrema(self) -> (Option<f32>, Option<f32>) {
if self.is_monotonic() {
return (None, None)
}
let p0p1p2p3 = F32x4::new(self.0.baseline.from_y(),
self.0.ctrl.from_y(),
self.0.ctrl.to_y(),
self.0.baseline.to_y());
let pxp0p1p2 = p0p1p2p3.wxyz();
let pxv0v1v2 = p0p1p2p3 - pxp0p1p2;
let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]);
let (v0_to_v1, v2_to_v1) = (v0 - v1, v2 - v1);
let discrim = f32::sqrt(v1 * v1 - v0 * v2);
let denom = 1.0 / (v0_to_v1 + v2_to_v1);
let t0 = (v0_to_v1 + discrim) * denom;
let t1 = (v0_to_v1 - discrim) * denom;
return match (
t0 > EPSILON && t0 < 1.0 - EPSILON,
t1 > EPSILON && t1 < 1.0 - EPSILON,
) {
(false, false) => (None, None),
(true, false) => (Some(t0), None),
(false, true) => (Some(t1), None),
(true, true) => (Some(f32::min(t0, t1)), Some(f32::max(t0, t1))),
};
const EPSILON: f32 = 0.001;
}
#[inline]
pub fn min_x(&self) -> f32 { f32::min(self.0.baseline.min_x(), self.0.ctrl.min_x()) }
#[inline]
pub fn min_y(&self) -> f32 { f32::min(self.0.baseline.min_y(), self.0.ctrl.min_y()) }
#[inline]
pub fn max_x(&self) -> f32 { f32::max(self.0.baseline.max_x(), self.0.ctrl.max_x()) }
#[inline]
pub fn max_y(&self) -> f32 { f32::max(self.0.baseline.max_y(), self.0.ctrl.max_y()) }
}

193
geometry/src/stroke.rs Normal file
View File

@ -0,0 +1,193 @@
// pathfinder/geometry/src/stroke.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Utilities for converting path strokes to fills.
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::rect::RectF32;
use crate::outline::{Contour, Outline};
use crate::segment::Segment as SegmentPF3;
use std::mem;
const TOLERANCE: f32 = 0.01;
pub struct OutlineStrokeToFill {
pub outline: Outline,
pub stroke_width: f32,
}
impl OutlineStrokeToFill {
#[inline]
pub fn new(outline: Outline, stroke_width: f32) -> OutlineStrokeToFill {
OutlineStrokeToFill { outline, stroke_width }
}
#[inline]
pub fn offset(&mut self) {
let mut new_bounds = None;
for contour in &mut self.outline.contours {
let input = mem::replace(contour, Contour::new());
let mut contour_stroke_to_fill =
ContourStrokeToFill::new(input, Contour::new(), self.stroke_width * 0.5);
contour_stroke_to_fill.offset_forward();
contour_stroke_to_fill.offset_backward();
*contour = contour_stroke_to_fill.output;
contour.update_bounds(&mut new_bounds);
}
self.outline.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
}
struct ContourStrokeToFill {
input: Contour,
output: Contour,
radius: f32,
}
impl ContourStrokeToFill {
#[inline]
fn new(input: Contour, output: Contour, radius: f32) -> ContourStrokeToFill {
ContourStrokeToFill { input, output, radius }
}
fn offset_forward(&mut self) {
for segment in self.input.iter() {
segment.offset(self.radius, &mut self.output);
}
}
fn offset_backward(&mut self) {
// FIXME(pcwalton)
let mut segments: Vec<_> = self.input.iter().map(|segment| segment.reversed()).collect();
segments.reverse();
for segment in &segments {
segment.offset(self.radius, &mut self.output);
}
}
}
trait Offset {
fn offset(&self, distance: f32, contour: &mut Contour);
fn offset_once(&self, distance: f32) -> Self;
fn error_is_within_tolerance(&self, other: &SegmentPF3, distance: f32) -> bool;
}
impl Offset for SegmentPF3 {
fn offset(&self, distance: f32, contour: &mut Contour) {
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
contour.push_full_segment(self, true);
return;
}
let candidate = self.offset_once(distance);
if self.error_is_within_tolerance(&candidate, distance) {
contour.push_full_segment(&candidate, true);
return;
}
//println!("--- SPLITTING ---");
//println!("... PRE-SPLIT: {:?}", self);
let (before, after) = self.split(0.5);
//println!("... AFTER-SPLIT: {:?} {:?}", before, after);
before.offset(distance, contour);
after.offset(distance, contour);
}
fn offset_once(&self, distance: f32) -> SegmentPF3 {
if self.is_line() {
return SegmentPF3::line(&self.baseline.offset(distance));
}
if self.is_quadratic() {
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(&segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to());
return SegmentPF3::quadratic(&baseline, &ctrl);
}
debug_assert!(self.is_cubic());
if self.baseline.from() == self.ctrl.from() {
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.to());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.to(), &self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(&segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to());
let ctrl = LineSegmentF32::new(&segment_0.from(), &ctrl);
return SegmentPF3::cubic(&baseline, &ctrl);
}
if self.ctrl.to() == self.baseline.to() {
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(&segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to());
let ctrl = LineSegmentF32::new(&ctrl, &segment_1.to());
return SegmentPF3::cubic(&baseline, &ctrl);
}
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.ctrl.to());
let mut segment_2 = LineSegmentF32::new(&self.ctrl.to(), &self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
segment_2 = segment_2.offset(distance);
let (ctrl_0, ctrl_1) = match (segment_0.intersection_t(&segment_1),
segment_1.intersection_t(&segment_2)) {
(Some(t0), Some(t1)) => (segment_0.sample(t0), segment_1.sample(t1)),
_ => {
(segment_0.to().lerp(segment_1.from(), 0.5),
segment_1.to().lerp(segment_2.from(), 0.5))
}
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_2.to());
let ctrl = LineSegmentF32::new(&ctrl_0, &ctrl_1);
SegmentPF3::cubic(&baseline, &ctrl)
}
fn error_is_within_tolerance(&self, other: &SegmentPF3, distance: f32) -> bool {
let (mut min, mut max) = (f32::abs(distance) - TOLERANCE, f32::abs(distance) + TOLERANCE);
min = if min <= 0.0 { 0.0 } else { min * min };
max = if max <= 0.0 { 0.0 } else { max * max };
for t_num in 0..(SAMPLE_COUNT + 1) {
let t = t_num as f32 / SAMPLE_COUNT as f32;
// FIXME(pcwalton): Use signed distance!
let (this_p, other_p) = (self.sample(t), other.sample(t));
let vector = this_p - other_p;
let square_distance = vector.square_length();
/*println!("this_p={:?} other_p={:?} vector={:?} sqdist={:?} min={:?} max={:?}",
this_p, other_p, vector, square_distance, min, max);*/
if square_distance < min || square_distance > max {
return false;
}
}
return true;
const SAMPLE_COUNT: u32 = 16;
}
}

31
geometry/src/util.rs Normal file
View File

@ -0,0 +1,31 @@
// pathfinder/geometry/src/util.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Various utilities.
use std::f32;
/// Linear interpolation.
#[inline]
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t
}
/// Clamping.
#[inline]
pub fn clamp(x: f32, min_val: f32, max_val: f32) -> f32 {
f32::min(max_val, f32::max(min_val, x))
}
/// Divides `a` by `b`, rounding up.
#[inline]
pub fn alignup_i32(a: i32, b: i32) -> i32 {
(a + b - 1) / b
}

View File

@ -1,51 +0,0 @@
// pathfinder/gfx-utils/lib.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
extern crate euclid;
use euclid::{Point2D, Size2D, Vector2D};
use std::cmp;
pub struct ShelfBinPacker {
next: Point2D<i32>,
max_size: Size2D<i32>,
padding: Vector2D<i32>,
shelf_height: i32,
}
impl ShelfBinPacker {
pub fn new(max_size: &Size2D<i32>, padding: &Vector2D<i32>) -> ShelfBinPacker {
ShelfBinPacker {
next: padding.to_point(),
max_size: *max_size,
padding: *padding,
shelf_height: 0,
}
}
pub fn add(&mut self, size: &Size2D<i32>) -> Result<Point2D<i32>, ()> {
let mut next = self.next;
let mut lower_right = Point2D::new(next.x + size.width, next.y + size.height) +
self.padding;
if lower_right.x > self.max_size.width {
next = Point2D::new(0, next.y + self.shelf_height);
self.shelf_height = 0;
lower_right = Point2D::new(size.width, next.y + size.height) + self.padding;
}
if lower_right.x > self.max_size.width || lower_right.y > self.max_size.height {
return Err(())
}
self.shelf_height = cmp::max(self.shelf_height, size.height);
self.next = next + Vector2D::new(size.width + self.padding.x * 2, 0);
Ok(next)
}
}

26
gl/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "pathfinder_gl"
version = "0.1.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
gl = "0.6"
rustache = "0.1"
[dependencies.image]
version = "0.21"
default-features = false
features = ["png_codec"]
[dependencies.pathfinder_geometry]
path = "../geometry"
[dependencies.pathfinder_gpu]
path = "../gpu"
[dependencies.pathfinder_renderer]
path = "../renderer"
[dependencies.pathfinder_simd]
path = "../simd"

893
gl/src/lib.rs Normal file
View File

@ -0,0 +1,893 @@
// pathfinder/gl/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! An OpenGL implementation of the device abstraction.
use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, ClearParams};
use pathfinder_gpu::{DepthFunc, Device, Primitive, RenderState, ShaderKind, StencilFunc};
use pathfinder_gpu::{TextureFormat, UniformData, VertexAttrType};
use pathfinder_simd::default::F32x4;
use rustache::{HashBuilder, Render};
use std::ffi::CString;
use std::io::Cursor;
use std::mem;
use std::ptr;
use std::str;
use std::time::Duration;
pub struct GLDevice {
version: GLVersion,
default_framebuffer: GLuint,
}
impl GLDevice {
#[inline]
pub fn new(version: GLVersion, default_framebuffer: GLuint) -> GLDevice {
GLDevice {
version,
default_framebuffer,
}
}
fn set_texture_parameters(&self, texture: &GLTexture) {
self.bind_texture(texture, 0);
unsafe {
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); ck();
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); ck();
gl::TexParameteri(gl::TEXTURE_2D,
gl::TEXTURE_WRAP_S,
gl::CLAMP_TO_EDGE as GLint); ck();
gl::TexParameteri(gl::TEXTURE_2D,
gl::TEXTURE_WRAP_T,
gl::CLAMP_TO_EDGE as GLint); ck();
}
}
fn set_render_state(&self, render_state: &RenderState) {
unsafe {
// Set blend.
match render_state.blend {
BlendState::Off => {
gl::Disable(gl::BLEND); ck();
}
BlendState::RGBOneAlphaOne => {
gl::BlendEquation(gl::FUNC_ADD); ck();
gl::BlendFunc(gl::ONE, gl::ONE); ck();
gl::Enable(gl::BLEND); ck();
}
BlendState::RGBOneAlphaOneMinusSrcAlpha => {
gl::BlendEquation(gl::FUNC_ADD); ck();
gl::BlendFuncSeparate(gl::ONE,
gl::ONE_MINUS_SRC_ALPHA,
gl::ONE,
gl::ONE); ck();
gl::Enable(gl::BLEND); ck();
}
BlendState::RGBSrcAlphaAlphaOneMinusSrcAlpha => {
gl::BlendEquation(gl::FUNC_ADD); ck();
gl::BlendFuncSeparate(gl::SRC_ALPHA,
gl::ONE_MINUS_SRC_ALPHA,
gl::ONE,
gl::ONE); ck();
gl::Enable(gl::BLEND); ck();
}
}
// Set depth.
match render_state.depth {
None => {
gl::Disable(gl::DEPTH_TEST); ck();
}
Some(ref state) => {
gl::DepthFunc(state.func.to_gl_depth_func()); ck();
gl::DepthMask(state.write as GLboolean); ck();
gl::Enable(gl::DEPTH_TEST); ck();
}
}
// Set stencil.
match render_state.stencil {
None => {
gl::Disable(gl::STENCIL_TEST); ck();
}
Some(ref state) => {
gl::StencilFunc(state.func.to_gl_stencil_func(),
state.reference as GLint,
state.mask); ck();
let (pass_action, write_mask) = if state.write {
(gl::REPLACE, state.mask)
} else {
(gl::KEEP, 0)
};
gl::StencilOp(gl::KEEP, gl::KEEP, pass_action); ck();
gl::StencilMask(write_mask);
gl::Enable(gl::STENCIL_TEST); ck();
}
}
// Set color mask.
let color_mask = render_state.color_mask as GLboolean;
gl::ColorMask(color_mask, color_mask, color_mask, color_mask); ck();
}
}
fn reset_render_state(&self, render_state: &RenderState) {
unsafe {
match render_state.blend {
BlendState::Off => {}
BlendState::RGBOneAlphaOneMinusSrcAlpha |
BlendState::RGBOneAlphaOne |
BlendState::RGBSrcAlphaAlphaOneMinusSrcAlpha => {
gl::Disable(gl::BLEND); ck();
}
}
if render_state.depth.is_some() {
gl::Disable(gl::DEPTH_TEST); ck();
}
if render_state.stencil.is_some() {
gl::StencilMask(!0); ck();
gl::Disable(gl::STENCIL_TEST); ck();
}
gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); ck();
}
}
}
impl Device for GLDevice {
type Buffer = GLBuffer;
type Framebuffer = GLFramebuffer;
type Program = GLProgram;
type Shader = GLShader;
type Texture = GLTexture;
type TimerQuery = GLTimerQuery;
type Uniform = GLUniform;
type VertexArray = GLVertexArray;
type VertexAttr = GLVertexAttr;
fn create_texture(&self, format: TextureFormat, size: Point2DI32) -> GLTexture {
let (gl_internal_format, gl_format, gl_type);
match format {
TextureFormat::R8 => {
gl_internal_format = gl::R8 as GLint;
gl_format = gl::RED;
gl_type = gl::UNSIGNED_BYTE;
}
TextureFormat::R16F => {
gl_internal_format = gl::R16F as GLint;
gl_format = gl::RED;
gl_type = gl::HALF_FLOAT;
}
TextureFormat::RGBA8 => {
gl_internal_format = gl::RGBA as GLint;
gl_format = gl::RGBA;
gl_type = gl::UNSIGNED_BYTE;
}
}
let mut texture = GLTexture { gl_texture: 0, size };
unsafe {
gl::GenTextures(1, &mut texture.gl_texture); ck();
self.bind_texture(&texture, 0);
gl::TexImage2D(gl::TEXTURE_2D,
0,
gl_internal_format,
size.x() as GLsizei,
size.y() as GLsizei,
0,
gl_format,
gl_type,
ptr::null()); ck();
}
self.set_texture_parameters(&texture);
texture
}
fn create_texture_from_data(&self, size: Point2DI32, data: &[u8]) -> GLTexture {
assert!(data.len() >= size.x() as usize * size.y() as usize);
let mut texture = GLTexture { gl_texture: 0, size };
unsafe {
gl::GenTextures(1, &mut texture.gl_texture); ck();
self.bind_texture(&texture, 0);
gl::TexImage2D(gl::TEXTURE_2D,
0,
gl::R8 as GLint,
size.x() as GLsizei,
size.y() as GLsizei,
0,
gl::RED,
gl::UNSIGNED_BYTE,
data.as_ptr() as *const GLvoid); ck();
}
self.set_texture_parameters(&texture);
texture
}
fn create_shader_from_source(&self,
name: &str,
source: &[u8],
kind: ShaderKind,
mut template_input: HashBuilder)
-> GLShader {
// FIXME(pcwalton): Do this once and cache it.
let glsl_version_spec = self.version.to_glsl_version_spec();
template_input = template_input.insert("version", glsl_version_spec);
let mut output = Cursor::new(vec![]);
template_input.render(str::from_utf8(source).unwrap(), &mut output).unwrap();
let source = output.into_inner();
let gl_shader_kind = match kind {
ShaderKind::Vertex => gl::VERTEX_SHADER,
ShaderKind::Fragment => gl::FRAGMENT_SHADER,
};
unsafe {
let gl_shader = gl::CreateShader(gl_shader_kind); ck();
gl::ShaderSource(gl_shader,
1,
[source.as_ptr() as *const GLchar].as_ptr(),
[source.len() as GLint].as_ptr()); ck();
gl::CompileShader(gl_shader); ck();
let mut compile_status = 0;
gl::GetShaderiv(gl_shader, gl::COMPILE_STATUS, &mut compile_status); ck();
if compile_status != gl::TRUE as GLint {
let mut info_log_length = 0;
gl::GetShaderiv(gl_shader, gl::INFO_LOG_LENGTH, &mut info_log_length); ck();
let mut info_log = vec![0; info_log_length as usize];
gl::GetShaderInfoLog(gl_shader,
info_log.len() as GLint,
ptr::null_mut(),
info_log.as_mut_ptr() as *mut GLchar); ck();
eprintln!("Shader info log:\n{}", String::from_utf8_lossy(&info_log));
panic!("{:?} shader '{}' compilation failed", kind, name);
}
GLShader { gl_shader }
}
}
fn create_program_from_shaders(&self,
name: &str,
vertex_shader: GLShader,
fragment_shader: GLShader)
-> GLProgram {
let gl_program;
unsafe {
gl_program = gl::CreateProgram(); ck();
gl::AttachShader(gl_program, vertex_shader.gl_shader); ck();
gl::AttachShader(gl_program, fragment_shader.gl_shader); ck();
gl::LinkProgram(gl_program); ck();
let mut link_status = 0;
gl::GetProgramiv(gl_program, gl::LINK_STATUS, &mut link_status); ck();
if link_status != gl::TRUE as GLint {
let mut info_log_length = 0;
gl::GetProgramiv(gl_program, gl::INFO_LOG_LENGTH, &mut info_log_length); ck();
let mut info_log = vec![0; info_log_length as usize];
gl::GetProgramInfoLog(gl_program,
info_log.len() as GLint,
ptr::null_mut(),
info_log.as_mut_ptr() as *mut GLchar); ck();
eprintln!("Program info log:\n{}", String::from_utf8_lossy(&info_log));
panic!("Program '{}' linking failed", name);
}
}
GLProgram { gl_program, vertex_shader, fragment_shader }
}
#[inline]
fn create_vertex_array(&self) -> GLVertexArray {
unsafe {
let mut array = GLVertexArray { gl_vertex_array: 0 };
gl::GenVertexArrays(1, &mut array.gl_vertex_array); ck();
array
}
}
fn get_vertex_attr(&self, program: &Self::Program, name: &str) -> GLVertexAttr {
let name = CString::new(format!("a{}", name)).unwrap();
let attr = unsafe {
gl::GetAttribLocation(program.gl_program, name.as_ptr() as *const GLchar) as GLuint
}; ck();
GLVertexAttr { attr }
}
fn get_uniform(&self, program: &GLProgram, name: &str) -> GLUniform {
let name = CString::new(format!("u{}", name)).unwrap();
let location = unsafe {
gl::GetUniformLocation(program.gl_program, name.as_ptr() as *const GLchar)
}; ck();
GLUniform { location }
}
fn use_program(&self, program: &Self::Program) {
unsafe {
gl::UseProgram(program.gl_program); ck();
}
}
fn configure_float_vertex_attr(&self,
attr: &GLVertexAttr,
size: usize,
attr_type: VertexAttrType,
normalized: bool,
stride: usize,
offset: usize,
divisor: u32) {
unsafe {
gl::VertexAttribPointer(attr.attr,
size as GLint,
attr_type.to_gl_type(),
if normalized { gl::TRUE } else { gl::FALSE },
stride as GLint,
offset as *const GLvoid); ck();
gl::VertexAttribDivisor(attr.attr, divisor); ck();
gl::EnableVertexAttribArray(attr.attr); ck();
}
}
fn configure_int_vertex_attr(&self,
attr: &GLVertexAttr,
size: usize,
attr_type: VertexAttrType,
stride: usize,
offset: usize,
divisor: u32) {
unsafe {
gl::VertexAttribIPointer(attr.attr,
size as GLint,
attr_type.to_gl_type(),
stride as GLint,
offset as *const GLvoid); ck();
gl::VertexAttribDivisor(attr.attr, divisor); ck();
gl::EnableVertexAttribArray(attr.attr); ck();
}
}
fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData) {
unsafe {
match data {
UniformData::Int(value) => {
gl::Uniform1i(uniform.location, value); ck();
}
UniformData::Mat2(data) => {
assert_eq!(mem::size_of::<F32x4>(), 4 * 4);
let data_ptr: *const F32x4 = &data;
gl::UniformMatrix2fv(uniform.location,
1,
gl::FALSE,
data_ptr as *const GLfloat);
}
UniformData::Mat4(data) => {
assert_eq!(mem::size_of::<[F32x4; 4]>(), 4 * 4 * 4);
let data_ptr: *const F32x4 = data.as_ptr();
gl::UniformMatrix4fv(uniform.location,
1,
gl::FALSE,
data_ptr as *const GLfloat);
}
UniformData::Vec2(data) => {
gl::Uniform2f(uniform.location, data.x(), data.y()); ck();
}
UniformData::Vec4(data) => {
gl::Uniform4f(uniform.location, data.x(), data.y(), data.z(), data.w()); ck();
}
UniformData::TextureUnit(unit) => {
gl::Uniform1i(uniform.location, unit as GLint); ck();
}
}
}
}
fn create_framebuffer(&self, texture: GLTexture) -> GLFramebuffer {
let mut gl_framebuffer = 0;
unsafe {
gl::GenFramebuffers(1, &mut gl_framebuffer); ck();
gl::BindFramebuffer(gl::FRAMEBUFFER, gl_framebuffer); ck();
self.bind_texture(&texture, 0);
gl::FramebufferTexture2D(gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0,
gl::TEXTURE_2D,
texture.gl_texture,
0); ck();
assert_eq!(gl::CheckFramebufferStatus(gl::FRAMEBUFFER), gl::FRAMEBUFFER_COMPLETE);
}
GLFramebuffer { gl_framebuffer, texture }
}
fn create_buffer(&self) -> GLBuffer {
unsafe {
let mut gl_buffer = 0;
gl::GenBuffers(1, &mut gl_buffer); ck();
GLBuffer { gl_buffer }
}
}
fn allocate_buffer<T>(&self,
buffer: &GLBuffer,
data: BufferData<T>,
target: BufferTarget,
mode: BufferUploadMode) {
let target = match target {
BufferTarget::Vertex => gl::ARRAY_BUFFER,
BufferTarget::Index => gl::ELEMENT_ARRAY_BUFFER,
};
let (ptr, len) = match data {
BufferData::Uninitialized(len) => (ptr::null(), len),
BufferData::Memory(buffer) => (buffer.as_ptr() as *const GLvoid, buffer.len()),
};
let len = (len * mem::size_of::<T>()) as GLsizeiptr;
let usage = mode.to_gl_usage();
unsafe {
gl::BindBuffer(target, buffer.gl_buffer); ck();
gl::BufferData(target, len, ptr, usage); ck();
}
}
#[inline]
fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture {
&framebuffer.texture
}
#[inline]
fn texture_size(&self, texture: &Self::Texture) -> Point2DI32 {
texture.size
}
fn upload_to_texture(&self, texture: &Self::Texture, size: Point2DI32, data: &[u8]) {
assert!(data.len() >= size.x() as usize * size.y() as usize * 4);
unsafe {
self.bind_texture(texture, 0);
gl::TexImage2D(gl::TEXTURE_2D,
0,
gl::RGBA as GLint,
size.x() as GLsizei,
size.y() as GLsizei,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
data.as_ptr() as *const GLvoid); ck();
}
self.set_texture_parameters(texture);
}
fn read_pixels_from_default_framebuffer(&self, size: Point2DI32) -> Vec<u8> {
let mut pixels = vec![0; size.x() as usize * size.y() as usize * 4];
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.default_framebuffer); ck();
gl::ReadPixels(0,
0,
size.x() as GLsizei,
size.y() as GLsizei,
gl::RGBA,
gl::UNSIGNED_BYTE,
pixels.as_mut_ptr() as *mut GLvoid); ck();
}
// Flip right-side-up.
let stride = size.x() as usize * 4;
for y in 0..(size.y() as usize / 2) {
let (index_a, index_b) = (y * stride, (size.y() as usize - y - 1) * stride);
for offset in 0..stride {
pixels.swap(index_a + offset, index_b + offset);
}
}
pixels
}
fn clear(&self, params: &ClearParams) {
unsafe {
if let Some(rect) = params.rect {
let (origin, size) = (rect.origin(), rect.size());
gl::Scissor(origin.x(), origin.y(), size.x(), size.y()); ck();
gl::Enable(gl::SCISSOR_TEST); ck();
}
let mut flags = 0;
if let Some(color) = params.color {
gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); ck();
gl::ClearColor(color.r(), color.g(), color.b(), color.a()); ck();
flags |= gl::COLOR_BUFFER_BIT;
}
if let Some(depth) = params.depth {
gl::DepthMask(gl::TRUE); ck();
gl::ClearDepthf(depth as _); ck(); // FIXME(pcwalton): GLES
flags |= gl::DEPTH_BUFFER_BIT;
}
if let Some(stencil) = params.stencil {
gl::StencilMask(!0); ck();
gl::ClearStencil(stencil as GLint); ck();
flags |= gl::STENCIL_BUFFER_BIT;
}
if flags != 0 {
gl::Clear(flags); ck();
}
if params.rect.is_some() {
gl::Disable(gl::SCISSOR_TEST); ck();
}
}
}
fn draw_arrays(&self, primitive: Primitive, index_count: u32, render_state: &RenderState) {
self.set_render_state(render_state);
unsafe {
gl::DrawArrays(primitive.to_gl_primitive(), 0, index_count as GLsizei); ck();
}
self.reset_render_state(render_state);
}
fn draw_elements(&self, primitive: Primitive, index_count: u32, render_state: &RenderState) {
self.set_render_state(render_state);
unsafe {
gl::DrawElements(primitive.to_gl_primitive(),
index_count as GLsizei,
gl::UNSIGNED_INT,
ptr::null()); ck();
}
self.reset_render_state(render_state);
}
fn draw_arrays_instanced(&self,
primitive: Primitive,
index_count: u32,
instance_count: u32,
render_state: &RenderState) {
self.set_render_state(render_state);
unsafe {
gl::DrawArraysInstanced(primitive.to_gl_primitive(),
0,
index_count as GLsizei,
instance_count as GLsizei); ck();
}
self.reset_render_state(render_state);
}
#[inline]
fn create_timer_query(&self) -> GLTimerQuery {
let mut query = GLTimerQuery { gl_query: 0 };
unsafe {
gl::GenQueries(1, &mut query.gl_query); ck();
}
query
}
#[inline]
fn begin_timer_query(&self, query: &Self::TimerQuery) {
unsafe {
gl::BeginQuery(gl::TIME_ELAPSED, query.gl_query); ck();
}
}
#[inline]
fn end_timer_query(&self, _: &Self::TimerQuery) {
unsafe {
gl::EndQuery(gl::TIME_ELAPSED); ck();
}
}
#[inline]
fn timer_query_is_available(&self, query: &Self::TimerQuery) -> bool {
unsafe {
let mut result = 0;
gl::GetQueryObjectiv(query.gl_query, gl::QUERY_RESULT_AVAILABLE, &mut result); ck();
result != gl::FALSE as GLint
}
}
#[inline]
fn get_timer_query(&self, query: &Self::TimerQuery) -> Duration {
unsafe {
let mut result = 0;
gl::GetQueryObjectui64v(query.gl_query, gl::QUERY_RESULT, &mut result); ck();
Duration::from_nanos(result)
}
}
#[inline]
fn bind_vertex_array(&self, vertex_array: &GLVertexArray) {
unsafe {
gl::BindVertexArray(vertex_array.gl_vertex_array); ck();
}
}
#[inline]
fn bind_buffer(&self, buffer: &GLBuffer, target: BufferTarget) {
unsafe {
gl::BindBuffer(target.to_gl_target(), buffer.gl_buffer); ck();
}
}
#[inline]
fn bind_default_framebuffer(&self, viewport: RectI32) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, self.default_framebuffer); ck();
gl::Viewport(viewport.origin().x(),
viewport.origin().y(),
viewport.size().x(),
viewport.size().y()); ck();
}
}
#[inline]
fn bind_framebuffer(&self, framebuffer: &GLFramebuffer) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer.gl_framebuffer); ck();
gl::Viewport(0, 0, framebuffer.texture.size.x(), framebuffer.texture.size.y()); ck();
}
}
#[inline]
fn bind_texture(&self, texture: &GLTexture, unit: u32) {
unsafe {
gl::ActiveTexture(gl::TEXTURE0 + unit); ck();
gl::BindTexture(gl::TEXTURE_2D, texture.gl_texture); ck();
}
}
}
pub struct GLVertexArray {
pub gl_vertex_array: GLuint,
}
impl Drop for GLVertexArray {
#[inline]
fn drop(&mut self) {
unsafe {
gl::DeleteVertexArrays(1, &mut self.gl_vertex_array); ck();
}
}
}
pub struct GLVertexAttr {
attr: GLuint,
}
impl GLVertexAttr {
pub fn configure_float(&self,
size: GLint,
gl_type: GLuint,
normalized: bool,
stride: GLsizei,
offset: usize,
divisor: GLuint) {
unsafe {
gl::VertexAttribPointer(self.attr,
size,
gl_type,
if normalized { gl::TRUE } else { gl::FALSE },
stride,
offset as *const GLvoid); ck();
gl::VertexAttribDivisor(self.attr, divisor); ck();
gl::EnableVertexAttribArray(self.attr); ck();
}
}
pub fn configure_int(&self,
size: GLint,
gl_type: GLuint,
stride: GLsizei,
offset: usize,
divisor: GLuint) {
unsafe {
gl::VertexAttribIPointer(self.attr,
size,
gl_type,
stride,
offset as *const GLvoid); ck();
gl::VertexAttribDivisor(self.attr, divisor); ck();
gl::EnableVertexAttribArray(self.attr); ck();
}
}
}
pub struct GLFramebuffer {
pub gl_framebuffer: GLuint,
pub texture: GLTexture,
}
impl Drop for GLFramebuffer {
fn drop(&mut self) {
unsafe {
gl::DeleteFramebuffers(1, &mut self.gl_framebuffer); ck();
}
}
}
pub struct GLBuffer {
pub gl_buffer: GLuint,
}
impl Drop for GLBuffer {
fn drop(&mut self) {
unsafe {
gl::DeleteBuffers(1, &mut self.gl_buffer); ck();
}
}
}
#[derive(Debug)]
pub struct GLUniform {
pub location: GLint,
}
pub struct GLProgram {
pub gl_program: GLuint,
#[allow(dead_code)]
vertex_shader: GLShader,
#[allow(dead_code)]
fragment_shader: GLShader,
}
impl Drop for GLProgram {
fn drop(&mut self) {
unsafe {
gl::DeleteProgram(self.gl_program); ck();
}
}
}
pub struct GLShader {
gl_shader: GLuint,
}
impl Drop for GLShader {
fn drop(&mut self) {
unsafe {
gl::DeleteShader(self.gl_shader); ck();
}
}
}
pub struct GLTexture {
gl_texture: GLuint,
pub size: Point2DI32,
}
pub struct GLTimerQuery {
gl_query: GLuint,
}
impl Drop for GLTimerQuery {
#[inline]
fn drop(&mut self) {
unsafe {
gl::DeleteQueries(1, &mut self.gl_query); ck();
}
}
}
trait BufferTargetExt {
fn to_gl_target(self) -> GLuint;
}
impl BufferTargetExt for BufferTarget {
fn to_gl_target(self) -> GLuint {
match self {
BufferTarget::Vertex => gl::ARRAY_BUFFER,
BufferTarget::Index => gl::ELEMENT_ARRAY_BUFFER,
}
}
}
trait BufferUploadModeExt {
fn to_gl_usage(self) -> GLuint;
}
impl BufferUploadModeExt for BufferUploadMode {
fn to_gl_usage(self) -> GLuint {
match self {
BufferUploadMode::Static => gl::STATIC_DRAW,
BufferUploadMode::Dynamic => gl::DYNAMIC_DRAW,
}
}
}
trait DepthFuncExt {
fn to_gl_depth_func(self) -> GLenum;
}
impl DepthFuncExt for DepthFunc {
fn to_gl_depth_func(self) -> GLenum {
match self {
DepthFunc::Less => gl::LESS,
DepthFunc::Always => gl::ALWAYS,
}
}
}
trait PrimitiveExt {
fn to_gl_primitive(self) -> GLuint;
}
impl PrimitiveExt for Primitive {
fn to_gl_primitive(self) -> GLuint {
match self {
Primitive::Triangles => gl::TRIANGLES,
Primitive::TriangleFan => gl::TRIANGLE_FAN,
Primitive::Lines => gl::LINES,
}
}
}
trait StencilFuncExt {
fn to_gl_stencil_func(self) -> GLenum;
}
impl StencilFuncExt for StencilFunc {
fn to_gl_stencil_func(self) -> GLenum {
match self {
StencilFunc::Always => gl::ALWAYS,
StencilFunc::Equal => gl::EQUAL,
StencilFunc::NotEqual => gl::NOTEQUAL,
}
}
}
trait VertexAttrTypeExt {
fn to_gl_type(self) -> GLuint;
}
impl VertexAttrTypeExt for VertexAttrType {
fn to_gl_type(self) -> GLuint {
match self {
VertexAttrType::F32 => gl::FLOAT,
VertexAttrType::I16 => gl::SHORT,
VertexAttrType::I8 => gl::BYTE,
VertexAttrType::U16 => gl::UNSIGNED_SHORT,
VertexAttrType::U8 => gl::UNSIGNED_BYTE,
}
}
}
/// The version/dialect of OpenGL we should render with.
pub enum GLVersion {
/// OpenGL 3.0+, core profile.
GL3,
/// OpenGL ES 3.0+.
GLES3,
}
impl GLVersion {
fn to_glsl_version_spec(&self) -> &'static str {
match *self {
GLVersion::GL3 => "330",
GLVersion::GLES3 => "300 es",
}
}
}
// Error checking
#[cfg(debug)]
fn ck() {
unsafe {
let err = gl::GetError();
if err != 0 {
panic!("GL error: 0x{:x}", err);
}
}
}
#[cfg(not(debug))]
fn ck() {}

19
gpu/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "pathfinder_gpu"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
rustache = "0.1"
[dependencies.image]
version = "0.21"
default-features = false
features = ["png_codec"]
[dependencies.pathfinder_geometry]
path = "../geometry"
[dependencies.pathfinder_simd]
path = "../simd"

313
gpu/src/lib.rs Normal file
View File

@ -0,0 +1,313 @@
// pathfinder/gpu/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Minimal abstractions over GPU device capabilities.
use crate::resources::ResourceLoader;
use image::ImageFormat;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_geometry::color::ColorF;
use pathfinder_simd::default::F32x4;
use rustache::HashBuilder;
use std::time::Duration;
pub mod resources;
pub trait Device {
type Buffer;
type Framebuffer;
type Program;
type Shader;
type Texture;
type TimerQuery;
type Uniform;
type VertexArray;
type VertexAttr;
fn create_texture(&self, format: TextureFormat, size: Point2DI32) -> Self::Texture;
fn create_texture_from_data(&self, size: Point2DI32, data: &[u8]) -> Self::Texture;
fn create_shader_from_source(&self,
name: &str,
source: &[u8],
kind: ShaderKind,
template_input: HashBuilder)
-> Self::Shader;
fn create_vertex_array(&self) -> Self::VertexArray;
fn create_program_from_shaders(&self,
name: &str,
vertex_shader: Self::Shader,
fragment_shader: Self::Shader)
-> Self::Program;
fn get_vertex_attr(&self, program: &Self::Program, name: &str) -> Self::VertexAttr;
fn get_uniform(&self, program: &Self::Program, name: &str) -> Self::Uniform;
fn use_program(&self, program: &Self::Program);
fn configure_float_vertex_attr(&self,
attr: &Self::VertexAttr,
size: usize,
attr_type: VertexAttrType,
normalized: bool,
stride: usize,
offset: usize,
divisor: u32);
fn configure_int_vertex_attr(&self,
attr: &Self::VertexAttr,
size: usize,
attr_type: VertexAttrType,
stride: usize,
offset: usize,
divisor: u32);
fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData);
fn create_framebuffer(&self, texture: Self::Texture) -> Self::Framebuffer;
fn create_buffer(&self) -> Self::Buffer;
fn allocate_buffer<T>(&self,
buffer: &Self::Buffer,
data: BufferData<T>,
target: BufferTarget,
mode: BufferUploadMode);
fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture;
fn texture_size(&self, texture: &Self::Texture) -> Point2DI32;
fn upload_to_texture(&self, texture: &Self::Texture, size: Point2DI32, data: &[u8]);
fn read_pixels_from_default_framebuffer(&self, size: Point2DI32) -> Vec<u8>;
fn clear(&self, params: &ClearParams);
fn draw_arrays(&self, primitive: Primitive, index_count: u32, render_state: &RenderState);
fn draw_elements(&self, primitive: Primitive, index_count: u32, render_state: &RenderState);
fn draw_arrays_instanced(&self,
primitive: Primitive,
index_count: u32,
instance_count: u32,
render_state: &RenderState);
fn create_timer_query(&self) -> Self::TimerQuery;
fn begin_timer_query(&self, query: &Self::TimerQuery);
fn end_timer_query(&self, query: &Self::TimerQuery);
fn timer_query_is_available(&self, query: &Self::TimerQuery) -> bool;
fn get_timer_query(&self, query: &Self::TimerQuery) -> Duration;
// TODO(pcwalton): Go bindless...
fn bind_vertex_array(&self, vertex_array: &Self::VertexArray);
fn bind_buffer(&self, buffer: &Self::Buffer, target: BufferTarget);
fn bind_default_framebuffer(&self, viewport: RectI32);
fn bind_framebuffer(&self, framebuffer: &Self::Framebuffer);
fn bind_texture(&self, texture: &Self::Texture, unit: u32);
fn create_texture_from_png(&self, resources: &dyn ResourceLoader, name: &str)
-> Self::Texture {
let data = resources.slurp(&format!("textures/{}.png", name)).unwrap();
let image = image::load_from_memory_with_format(&data, ImageFormat::PNG).unwrap().to_luma();
let size = Point2DI32::new(image.width() as i32, image.height() as i32);
self.create_texture_from_data(size, &image)
}
fn create_shader(&self, resources: &dyn ResourceLoader, name: &str, kind: ShaderKind)
-> Self::Shader {
let suffix = match kind { ShaderKind::Vertex => 'v', ShaderKind::Fragment => 'f' };
let source = resources.slurp(&format!("shaders/{}.{}s.glsl", name, suffix)).unwrap();
let mut load_include_tile_alpha_vertex =
|_| load_shader_include(resources, "tile_alpha_vertex");
let mut load_include_tile_monochrome =
|_| load_shader_include(resources, "tile_monochrome");
let mut load_include_tile_multicolor =
|_| load_shader_include(resources, "tile_multicolor");
let mut load_include_tile_solid_vertex =
|_| load_shader_include(resources, "tile_solid_vertex");
let mut load_include_post_convolve = |_| load_shader_include(resources, "post_convolve");
let mut load_include_post_gamma_correct =
|_| load_shader_include(resources, "post_gamma_correct");
let template_input =
HashBuilder::new().insert_lambda("include_tile_alpha_vertex",
&mut load_include_tile_alpha_vertex)
.insert_lambda("include_tile_monochrome",
&mut load_include_tile_monochrome)
.insert_lambda("include_tile_multicolor",
&mut load_include_tile_multicolor)
.insert_lambda("include_tile_solid_vertex",
&mut load_include_tile_solid_vertex)
.insert_lambda("include_post_convolve",
&mut load_include_post_convolve)
.insert_lambda("include_post_gamma_correct",
&mut load_include_post_gamma_correct);
self.create_shader_from_source(name, &source, kind, template_input)
}
fn create_program_from_shader_names(&self,
resources: &dyn ResourceLoader,
program_name: &str,
vertex_shader_name: &str,
fragment_shader_name: &str)
-> Self::Program {
let vertex_shader = self.create_shader(resources, vertex_shader_name, ShaderKind::Vertex);
let fragment_shader = self.create_shader(resources,
fragment_shader_name,
ShaderKind::Fragment);
self.create_program_from_shaders(program_name, vertex_shader, fragment_shader)
}
fn create_program(&self, resources: &dyn ResourceLoader, name: &str) -> Self::Program {
self.create_program_from_shader_names(resources, name, name, name)
}
}
#[derive(Clone, Copy, Debug)]
pub enum TextureFormat {
R8,
R16F,
RGBA8,
}
#[derive(Clone, Copy, Debug)]
pub enum VertexAttrType {
F32,
I16,
I8,
U16,
U8,
}
#[derive(Clone, Copy, Debug)]
pub enum BufferData<'a, T> {
Uninitialized(usize),
Memory(&'a [T]),
}
#[derive(Clone, Copy, Debug)]
pub enum BufferTarget {
Vertex,
Index,
}
#[derive(Clone, Copy, Debug)]
pub enum BufferUploadMode {
Static,
Dynamic,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ShaderKind {
Vertex,
Fragment,
}
#[derive(Clone, Copy)]
pub enum UniformData {
Int(i32),
Mat2(F32x4),
Mat4([F32x4; 4]),
Vec2(F32x4),
Vec4(F32x4),
TextureUnit(u32),
}
#[derive(Clone, Copy)]
pub enum Primitive {
Triangles,
TriangleFan,
Lines,
}
#[derive(Clone, Copy, Default)]
pub struct ClearParams {
pub color: Option<ColorF>,
pub rect: Option<RectI32>,
pub depth: Option<f32>,
pub stencil: Option<u8>,
}
#[derive(Clone, Debug)]
pub struct RenderState {
pub blend: BlendState,
pub depth: Option<DepthState>,
pub stencil: Option<StencilState>,
pub color_mask: bool,
}
#[derive(Clone, Copy, Debug)]
pub enum BlendState {
Off,
RGBOneAlphaOne,
RGBOneAlphaOneMinusSrcAlpha,
RGBSrcAlphaAlphaOneMinusSrcAlpha,
}
#[derive(Clone, Copy, Default, Debug)]
pub struct DepthState {
pub func: DepthFunc,
pub write: bool,
}
#[derive(Clone, Copy, Debug)]
pub enum DepthFunc {
Less,
Always,
}
#[derive(Clone, Copy, Debug)]
pub struct StencilState {
pub func: StencilFunc,
pub reference: u32,
pub mask: u32,
pub write: bool,
}
#[derive(Clone, Copy, Debug)]
pub enum StencilFunc {
Always,
Equal,
NotEqual,
}
impl Default for RenderState {
#[inline]
fn default() -> RenderState {
RenderState { blend: BlendState::default(), depth: None, stencil: None, color_mask: true }
}
}
impl Default for BlendState {
#[inline]
fn default() -> BlendState {
BlendState::Off
}
}
impl Default for StencilState {
#[inline]
fn default() -> StencilState {
StencilState { func: StencilFunc::default(), reference: 0, mask: !0, write: false }
}
}
impl Default for DepthFunc {
#[inline]
fn default() -> DepthFunc {
DepthFunc::Less
}
}
impl Default for StencilFunc {
#[inline]
fn default() -> StencilFunc {
StencilFunc::Always
}
}
impl UniformData {
#[inline]
pub fn from_transform_3d(transform: &Transform3DF32) -> UniformData {
UniformData::Mat4([transform.c0, transform.c1, transform.c2, transform.c3])
}
}
fn load_shader_include(resources: &dyn ResourceLoader, include_name: &str) -> String {
let resource = resources.slurp(&format!("shaders/{}.inc.glsl", include_name)).unwrap();
String::from_utf8_lossy(&resource).to_string()
}

65
gpu/src/resources.rs Normal file
View File

@ -0,0 +1,65 @@
// pathfinder/gpu/src/resources.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! An abstraction for reading resources.
//!
//! We can't always count on a filesystem being present.
use std::env;
use std::fs::File;
use std::io::{Error as IOError, Read};
use std::path::PathBuf;
pub trait ResourceLoader {
/// This is deliberately not a `Path`, because these are virtual paths
/// that do not necessarily correspond to real paths on a filesystem.
fn slurp(&self, path: &str) -> Result<Vec<u8>, IOError>;
}
pub struct FilesystemResourceLoader {
pub directory: PathBuf,
}
impl FilesystemResourceLoader {
pub fn locate() -> FilesystemResourceLoader {
let mut parent_directory = env::current_dir().unwrap();
loop {
// So ugly :(
let mut resources_directory = parent_directory.clone();
resources_directory.push("resources");
if resources_directory.is_dir() {
let mut shaders_directory = resources_directory.clone();
let mut textures_directory = resources_directory.clone();
shaders_directory.push("shaders");
textures_directory.push("textures");
if shaders_directory.is_dir() && textures_directory.is_dir() {
return FilesystemResourceLoader { directory: resources_directory };
}
}
if !parent_directory.pop() {
break;
}
}
panic!("No suitable `resources/` directory found!");
}
}
impl ResourceLoader for FilesystemResourceLoader {
fn slurp(&self, virtual_path: &str) -> Result<Vec<u8>, IOError> {
let mut path = self.directory.clone();
virtual_path.split('/').for_each(|segment| path.push(segment));
let mut data = vec![];
File::open(&path)?.read_to_end(&mut data)?;
Ok(data)
}
}

View File

@ -1,2 +0,0 @@
target
Cargo.lock

View File

@ -1,27 +0,0 @@
[package]
name = "pathfinder_partitioner"
version = "0.2.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[lib]
name = "pathfinder_partitioner"
[dependencies]
arrayvec = "0.4"
bincode = "1.0"
bit-vec = "0.4"
byteorder = "1.2"
env_logger = "0.6"
half = "1.0"
log = "0.3"
lyon_geom = "0.12"
lyon_path = "0.12"
serde = "1.0"
serde_derive = "1.0"
[dependencies.euclid]
version = "0.19"
features = ["serde"]
[dependencies.pathfinder_path_utils]
path = "../path-utils"

View File

@ -1,201 +0,0 @@
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.

View File

@ -1,25 +0,0 @@
Copyright (c) 2010 The Rust Project Developers
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.

View File

@ -1,194 +0,0 @@
// pathfinder/partitioner/src/builder.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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 arrayvec::ArrayVec;
use euclid::{Angle, Point2D, Vector2D};
use lyon_geom::{CubicBezierSegment, QuadraticBezierSegment};
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticSegmentIter;
use std::ops::Range;
const TANGENT_PARAMETER_TOLERANCE: f32 = 0.001;
const DEFAULT_APPROX_TOLERANCE: f32 = 0.001;
// TODO(pcwalton): A better debug.
#[derive(Debug)]
pub struct Builder {
pub endpoints: Vec<Endpoint>,
pub subpath_ranges: Vec<Range<u32>>,
pub approx_tolerance: f32,
}
impl Builder {
#[inline]
pub fn new() -> Builder {
Builder {
endpoints: vec![],
subpath_ranges: vec![],
approx_tolerance: DEFAULT_APPROX_TOLERANCE,
}
}
#[inline]
pub fn set_approx_tolerance(&mut self, tolerance: f32) {
self.approx_tolerance = tolerance
}
#[inline]
fn current_subpath_index(&self) -> Option<u32> {
if self.subpath_ranges.is_empty() {
None
} else {
Some(self.subpath_ranges.len() as u32 - 1)
}
}
fn add_endpoint(&mut self, ctrl: Option<Point2D<f32>>, to: Point2D<f32>) {
let current_subpath_index = match self.current_subpath_index() {
None => return,
Some(current_subpath_index) => current_subpath_index,
};
self.endpoints.push(Endpoint {
to: to,
ctrl: ctrl,
subpath_index: current_subpath_index,
});
}
#[inline]
pub fn end_subpath(&mut self) {
let last_endpoint_index = self.endpoints.len() as u32;
if let Some(current_subpath) = self.subpath_ranges.last_mut() {
current_subpath.end = last_endpoint_index
}
}
#[inline]
fn first_position_of_subpath(&self) -> Option<Point2D<f32>> {
self.subpath_ranges
.last()
.map(|subpath_range| self.endpoints[subpath_range.start as usize].to)
}
}
impl FlatPathBuilder for Builder {
type PathType = ();
#[inline]
fn build(self) {}
#[inline]
fn build_and_reset(&mut self) {
self.endpoints.clear();
self.subpath_ranges.clear();
}
#[inline]
fn current_position(&self) -> Point2D<f32> {
match self.endpoints.last() {
None => Point2D::zero(),
Some(endpoint) => endpoint.to,
}
}
fn close(&mut self) {
let first_position_of_subpath = match self.first_position_of_subpath() {
None => return,
Some(first_position_of_subpath) => first_position_of_subpath,
};
if first_position_of_subpath == self.current_position() {
return
}
self.add_endpoint(None, first_position_of_subpath);
self.end_subpath();
}
fn move_to(&mut self, to: Point2D<f32>) {
self.end_subpath();
let last_endpoint_index = self.endpoints.len() as u32;
self.subpath_ranges.push(last_endpoint_index..last_endpoint_index);
self.add_endpoint(None, to);
}
#[inline]
fn line_to(&mut self, to: Point2D<f32>) {
self.add_endpoint(None, to);
}
}
impl PathBuilder for Builder {
fn quadratic_bezier_to(&mut self, ctrl: Point2D<f32>, to: Point2D<f32>) {
let segment = QuadraticBezierSegment {
from: self.current_position(),
ctrl: ctrl,
to: to,
};
//self.add_endpoint(Some(ctrl), to);
// Split at X tangent.
let mut worklist: ArrayVec<[QuadraticBezierSegment<f32>; 2]> = ArrayVec::new();
match segment.local_x_extremum_t() {
Some(t) if t > TANGENT_PARAMETER_TOLERANCE &&
t < 1.0 - TANGENT_PARAMETER_TOLERANCE => {
let subsegments = segment.split(t);
worklist.push(subsegments.0);
worklist.push(subsegments.1);
}
_ => worklist.push(segment),
}
// Split at Y tangent.
for segment in worklist {
match segment.local_y_extremum_t() {
Some(t) if t > TANGENT_PARAMETER_TOLERANCE &&
t < 1.0 - TANGENT_PARAMETER_TOLERANCE => {
let subsegments = segment.split(t);
self.add_endpoint(Some(subsegments.0.ctrl), subsegments.0.to);
self.add_endpoint(Some(subsegments.1.ctrl), subsegments.1.to);
}
_ => self.add_endpoint(Some(segment.ctrl), segment.to),
}
}
}
fn cubic_bezier_to(&mut self, ctrl1: Point2D<f32>, ctrl2: Point2D<f32>, to: Point2D<f32>) {
let cubic_segment = CubicBezierSegment {
from: self.current_position(),
ctrl1: ctrl1,
ctrl2: ctrl2,
to: to,
};
for quadratic_segment in CubicToQuadraticSegmentIter::new(&cubic_segment,
self.approx_tolerance) {
self.quadratic_bezier_to(quadratic_segment.ctrl, quadratic_segment.to)
}
}
fn arc(&mut self,
_center: Point2D<f32>,
_radii: Vector2D<f32>,
_angle: Angle<f32>,
_x_rotation: Angle<f32>) {
panic!("TODO: Support arcs in the Pathfinder builder!")
}
}
#[derive(Clone, Copy, Debug)]
pub struct Endpoint {
pub to: Point2D<f32>,
pub ctrl: Option<Point2D<f32>>,
pub subpath_index: u32,
}

View File

@ -1,164 +0,0 @@
// pathfinder/partitioner/src/lib.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
//! Processes paths into *mesh libraries*, which are vertex buffers ready to be uploaded to the
//! GPU and rendered with the supplied shaders.
//!
//! *Partitioning* is the process of cutting up a filled Bézier path into *B-quads*. A B-quad is
//! the core primitive that Pathfinder renders; it is a trapezoid-like shape that consists of
//! vertical sides on the left and right and Bézier curve segments and/or lines on the top and
//! bottom. Path partitioning is typically O(*n* log *n*) in the number of path commands.
//!
//! If you have a static set of paths (for example, one specific font), you may wish to run the
//! partitioner as a preprocessing step and store the resulting mesh library on disk. To aid this
//! use case, mesh libraries can be serialized into a simple binary format. Of course, meshes can
//! also be generated dynamically and rendered on the fly.
extern crate arrayvec;
extern crate bincode;
extern crate bit_vec;
extern crate byteorder;
extern crate env_logger;
extern crate euclid;
extern crate lyon_path;
extern crate pathfinder_path_utils;
extern crate serde;
use lyon_path::geom as lyon_geom;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
use euclid::Point2D;
use std::u32;
pub mod builder;
pub mod mesh;
pub mod mesh_pack;
pub mod partitioner;
/// The fill rule.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FillRule {
EvenOdd = 0,
Winding = 1,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BQuad {
pub upper_left_vertex_index: u32,
pub upper_right_vertex_index: u32,
pub upper_control_point_vertex_index: u32,
pad0: u32,
pub lower_left_vertex_index: u32,
pub lower_right_vertex_index: u32,
pub lower_control_point_vertex_index: u32,
pad1: u32,
}
impl BQuad {
#[inline]
pub fn new(upper_left_vertex_index: u32,
upper_control_point_vertex_index: u32,
upper_right_vertex_index: u32,
lower_left_vertex_index: u32,
lower_control_point_vertex_index: u32,
lower_right_vertex_index: u32)
-> BQuad {
BQuad {
upper_left_vertex_index: upper_left_vertex_index,
upper_control_point_vertex_index: upper_control_point_vertex_index,
upper_right_vertex_index: upper_right_vertex_index,
lower_left_vertex_index: lower_left_vertex_index,
lower_control_point_vertex_index: lower_control_point_vertex_index,
lower_right_vertex_index: lower_right_vertex_index,
pad0: 0,
pad1: 0,
}
}
#[inline]
pub fn offset(&mut self, delta: u32) {
self.upper_left_vertex_index += delta;
self.upper_right_vertex_index += delta;
self.lower_left_vertex_index += delta;
self.lower_right_vertex_index += delta;
if self.upper_control_point_vertex_index < u32::MAX {
self.upper_control_point_vertex_index += delta;
}
if self.lower_control_point_vertex_index < u32::MAX {
self.lower_control_point_vertex_index += delta;
}
}
}
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
pub struct BQuadVertexPositions {
pub upper_left_vertex_position: Point2D<f32>,
pub upper_control_point_position: Point2D<f32>,
pub upper_right_vertex_position: Point2D<f32>,
pub lower_right_vertex_position: Point2D<f32>,
pub lower_control_point_position: Point2D<f32>,
pub lower_left_vertex_position: Point2D<f32>,
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(u8)]
pub(crate) enum BVertexKind {
Endpoint0,
Endpoint1,
ConvexControlPoint,
ConcaveControlPoint,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[repr(C)]
pub struct BVertexLoopBlinnData {
pub tex_coord: [u8; 2],
pub sign: i8,
pad: u8,
}
impl BVertexLoopBlinnData {
#[inline]
pub(crate) fn new(kind: BVertexKind) -> BVertexLoopBlinnData {
let (tex_coord, sign) = match kind {
BVertexKind::Endpoint0 => ([0, 0], 0),
BVertexKind::Endpoint1 => ([2, 2], 0),
BVertexKind::ConcaveControlPoint => ([1, 0], 1),
BVertexKind::ConvexControlPoint => ([1, 0], -1),
};
BVertexLoopBlinnData {
tex_coord: tex_coord,
sign: sign,
pad: 0,
}
}
pub(crate) fn control_point(left_endpoint_position: &Point2D<f32>,
control_point_position: &Point2D<f32>,
right_endpoint_position: &Point2D<f32>,
bottom: bool)
-> BVertexLoopBlinnData {
let control_point_vector = *control_point_position - *left_endpoint_position;
let right_vector = *right_endpoint_position - *left_endpoint_position;
let determinant = right_vector.cross(control_point_vector);
let endpoint_kind = if (determinant < 0.0) ^ bottom {
BVertexKind::ConvexControlPoint
} else {
BVertexKind::ConcaveControlPoint
};
BVertexLoopBlinnData::new(endpoint_kind)
}
}

View File

@ -1,387 +0,0 @@
// pathfinder/partitioner/src/mesh.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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 euclid::approxeq::ApproxEq;
use euclid::{Point2D, Rect, Size2D, Vector2D};
use lyon_path::PathEvent;
use pathfinder_path_utils::normals::PathNormals;
use pathfinder_path_utils::segments::{self, SegmentIter};
use std::f32;
use std::u32;
use {BQuad, BQuadVertexPositions, BVertexLoopBlinnData};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mesh {
pub b_quads: Vec<BQuad>,
// FIXME(pcwalton): Merge with `b_vertex_positions` below.
pub b_quad_vertex_positions: Vec<BQuadVertexPositions>,
pub b_quad_vertex_interior_indices: Vec<u32>,
pub b_vertex_positions: Vec<Point2D<f32>>,
pub b_vertex_loop_blinn_data: Vec<BVertexLoopBlinnData>,
pub b_boxes: Vec<BBox>,
pub stencil_segments: Vec<StencilSegment>,
pub stencil_normals: Vec<StencilNormals>,
}
impl Mesh {
#[inline]
pub fn new() -> Mesh {
Mesh {
b_quads: vec![],
b_quad_vertex_positions: vec![],
b_quad_vertex_interior_indices: vec![],
b_vertex_positions: vec![],
b_vertex_loop_blinn_data: vec![],
b_boxes: vec![],
stencil_segments: vec![],
stencil_normals: vec![],
}
}
pub fn clear(&mut self) {
self.b_quads.clear();
self.b_quad_vertex_positions.clear();
self.b_quad_vertex_interior_indices.clear();
self.b_vertex_positions.clear();
self.b_vertex_loop_blinn_data.clear();
self.b_boxes.clear();
self.stencil_segments.clear();
self.stencil_normals.clear();
}
pub(crate) fn add_b_vertex(&mut self,
position: &Point2D<f32>,
loop_blinn_data: &BVertexLoopBlinnData) {
self.b_vertex_positions.push(*position);
self.b_vertex_loop_blinn_data.push(*loop_blinn_data);
}
pub(crate) fn add_b_quad(&mut self, b_quad: &BQuad) {
let BQuadVertexPositions {
upper_left_vertex_position: ul,
upper_right_vertex_position: ur,
lower_left_vertex_position: ll,
lower_right_vertex_position: lr,
..
} = self.get_b_quad_vertex_positions(b_quad);
if ul.x.approx_eq(&ur.x) || ll.x.approx_eq(&lr.x) {
return
}
self.b_quads.push(*b_quad);
self.add_b_quad_vertex_positions(b_quad);
self.add_b_box(b_quad);
}
fn add_b_quad_vertex_positions(&mut self, b_quad: &BQuad) {
let b_quad_vertex_positions = self.get_b_quad_vertex_positions(b_quad);
let first_b_quad_vertex_position_index = (self.b_quad_vertex_positions.len() as u32) * 6;
self.push_b_quad_vertex_position_interior_indices(first_b_quad_vertex_position_index,
&b_quad_vertex_positions);
self.b_quad_vertex_positions.push(b_quad_vertex_positions);
}
fn add_b_box(&mut self, b_quad: &BQuad) {
let BQuadVertexPositions {
upper_left_vertex_position: ul,
upper_control_point_position: uc,
upper_right_vertex_position: ur,
lower_left_vertex_position: ll,
lower_control_point_position: lc,
lower_right_vertex_position: lr,
} = self.get_b_quad_vertex_positions(b_quad);
let rect = Rect::from_points([ul, uc, ur, ll, lc, lr].into_iter());
let (edge_ucl, edge_urc, edge_ulr) = (uc - ul, ur - uc, ul - ur);
let (edge_lcl, edge_lrc, edge_llr) = (lc - ll, lr - lc, ll - lr);
let (edge_len_ucl, edge_len_urc) = (edge_ucl.length(), edge_urc.length());
let (edge_len_lcl, edge_len_lrc) = (edge_lcl.length(), edge_lrc.length());
let (edge_len_ulr, edge_len_llr) = (edge_ulr.length(), edge_llr.length());
let (uv_upper, uv_lower, sign_upper, sign_lower, mode_upper, mode_lower);
if edge_len_ucl < 0.01 || edge_len_urc < 0.01 || edge_len_ulr < 0.01 ||
edge_ucl.dot(-edge_ulr) > 0.9999 * edge_len_ucl * edge_len_ulr {
uv_upper = Uv::line(&rect, &ul, &ur);
sign_upper = -1.0;
mode_upper = -1.0;
} else {
uv_upper = Uv::curve(&rect, &ul, &uc, &ur);
sign_upper = (edge_ucl.cross(-edge_ulr)).signum();
mode_upper = 1.0;
}
if edge_len_lcl < 0.01 || edge_len_lrc < 0.01 || edge_len_llr < 0.01 ||
edge_lcl.dot(-edge_llr) > 0.9999 * edge_len_lcl * edge_len_llr {
uv_lower = Uv::line(&rect, &ll, &lr);
sign_lower = 1.0;
mode_lower = -1.0;
} else {
uv_lower = Uv::curve(&rect, &ll, &lc, &lr);
sign_lower = -(edge_lcl.cross(-edge_llr)).signum();
mode_lower = 1.0;
}
let b_box = BBox {
upper_left_position: rect.origin,
lower_right_position: rect.bottom_right(),
upper_left_uv_upper: uv_upper.origin,
upper_left_uv_lower: uv_lower.origin,
d_upper_uv_dx: uv_upper.d_uv_dx,
d_lower_uv_dx: uv_lower.d_uv_dx,
d_upper_uv_dy: uv_upper.d_uv_dy,
d_lower_uv_dy: uv_lower.d_uv_dy,
upper_sign: sign_upper,
lower_sign: sign_lower,
upper_mode: mode_upper,
lower_mode: mode_lower,
};
self.b_boxes.push(b_box);
}
fn get_b_quad_vertex_positions(&self, b_quad: &BQuad) -> BQuadVertexPositions {
let ul = self.b_vertex_positions[b_quad.upper_left_vertex_index as usize];
let ur = self.b_vertex_positions[b_quad.upper_right_vertex_index as usize];
let ll = self.b_vertex_positions[b_quad.lower_left_vertex_index as usize];
let lr = self.b_vertex_positions[b_quad.lower_right_vertex_index as usize];
let mut b_quad_vertex_positions = BQuadVertexPositions {
upper_left_vertex_position: ul,
upper_control_point_position: ul.lerp(ur, 0.5),
upper_right_vertex_position: ur,
lower_left_vertex_position: ll,
lower_control_point_position: ll.lerp(lr, 0.5),
lower_right_vertex_position: lr,
};
if b_quad.upper_control_point_vertex_index != u32::MAX {
let uc = &self.b_vertex_positions[b_quad.upper_control_point_vertex_index as usize];
b_quad_vertex_positions.upper_control_point_position = *uc;
}
if b_quad.lower_control_point_vertex_index != u32::MAX {
let lc = &self.b_vertex_positions[b_quad.lower_control_point_vertex_index as usize];
b_quad_vertex_positions.lower_control_point_position = *lc;
}
b_quad_vertex_positions
}
fn push_b_quad_vertex_position_interior_indices(&mut self,
first_vertex_index: u32,
b_quad: &BQuadVertexPositions) {
let upper_curve_is_concave =
(b_quad.upper_right_vertex_position - b_quad.upper_left_vertex_position).cross(
b_quad.upper_control_point_position - b_quad.upper_left_vertex_position) > 0.0;
let lower_curve_is_concave =
(b_quad.lower_left_vertex_position - b_quad.lower_right_vertex_position).cross(
b_quad.lower_control_point_position - b_quad.lower_right_vertex_position) > 0.0;
let indices: &'static [u32] = match (upper_curve_is_concave, lower_curve_is_concave) {
(false, false) => &[UL, UR, LL, UR, LR, LL],
(true, false) => &[UL, UC, LL, UC, LR, LL, UR, LR, UC],
(false, true) => &[UL, LC, LL, UL, UR, LC, UR, LR, LC],
(true, true) => &[UL, UC, LL, UC, LC, LL, UR, LC, UC, UR, LR, LC],
};
self.b_quad_vertex_interior_indices
.extend(indices.into_iter().map(|index| index + first_vertex_index));
const UL: u32 = 0;
const UC: u32 = 1;
const UR: u32 = 2;
const LR: u32 = 3;
const LC: u32 = 4;
const LL: u32 = 5;
}
pub fn push_stencil_segments<I>(&mut self, stream: I) where I: Iterator<Item = PathEvent> {
let segment_iter = SegmentIter::new(stream);
for segment in segment_iter {
match segment {
segments::Segment::Line(line_segment) => {
self.stencil_segments.push(StencilSegment {
from: line_segment.from,
ctrl: line_segment.from.lerp(line_segment.to, 0.5),
to: line_segment.to,
})
}
segments::Segment::Quadratic(quadratic_segment) => {
self.stencil_segments.push(StencilSegment {
from: quadratic_segment.from,
ctrl: quadratic_segment.ctrl,
to: quadratic_segment.to,
})
}
segments::Segment::Cubic(..) => {
panic!("push_stencil_segments(): Convert cubics to quadratics first!")
}
segments::Segment::EndSubpath(..) => {}
}
}
}
/// Computes vertex normals necessary for emboldening and/or stem darkening. This is intended
/// for stencil-and-cover.
pub fn push_stencil_normals<I>(&mut self, stream: I) where I: Iterator<Item = PathEvent> {
let mut normals = PathNormals::new();
normals.add_path(stream);
self.stencil_normals.extend(normals.normals().iter().map(|normals| {
StencilNormals {
from: normals.from,
ctrl: normals.ctrl,
to: normals.to,
}
}))
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct BBox {
pub upper_left_position: Point2D<f32>,
pub lower_right_position: Point2D<f32>,
pub upper_left_uv_upper: Point2D<f32>,
pub upper_left_uv_lower: Point2D<f32>,
pub d_upper_uv_dx: Vector2D<f32>,
pub d_lower_uv_dx: Vector2D<f32>,
pub d_upper_uv_dy: Vector2D<f32>,
pub d_lower_uv_dy: Vector2D<f32>,
pub upper_sign: f32,
pub lower_sign: f32,
pub upper_mode: f32,
pub lower_mode: f32,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct StencilSegment {
pub from: Point2D<f32>,
pub ctrl: Point2D<f32>,
pub to: Point2D<f32>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct StencilNormals {
pub from: Vector2D<f32>,
pub ctrl: Vector2D<f32>,
pub to: Vector2D<f32>,
}
#[derive(Clone, Copy, Debug)]
struct CornerValues {
upper_left: Point2D<f32>,
upper_right: Point2D<f32>,
lower_left: Point2D<f32>,
lower_right: Point2D<f32>,
}
#[derive(Clone, Copy, Debug)]
struct Uv {
origin: Point2D<f32>,
d_uv_dx: Vector2D<f32>,
d_uv_dy: Vector2D<f32>,
}
impl Uv {
fn from_values(origin: &Point2D<f32>, origin_right: &Point2D<f32>, origin_down: &Point2D<f32>)
-> Uv {
Uv {
origin: *origin,
d_uv_dx: *origin_right - *origin,
d_uv_dy: *origin_down - *origin,
}
}
fn curve(rect: &Rect<f32>, left: &Point2D<f32>, ctrl: &Point2D<f32>, right: &Point2D<f32>)
-> Uv {
let origin_right = rect.top_right();
let origin_down = rect.bottom_left();
let (lambda_origin, denom) = to_barycentric(left, ctrl, right, &rect.origin);
let (lambda_origin_right, _) = to_barycentric(left, ctrl, right, &origin_right);
let (lambda_origin_down, _) = to_barycentric(left, ctrl, right, &origin_down);
let uv_origin = lambda_to_uv(&lambda_origin, denom);
let uv_origin_right = lambda_to_uv(&lambda_origin_right, denom);
let uv_origin_down = lambda_to_uv(&lambda_origin_down, denom);
return Uv::from_values(&uv_origin, &uv_origin_right, &uv_origin_down);
// https://gamedev.stackexchange.com/a/23745
fn to_barycentric(a: &Point2D<f32>, b: &Point2D<f32>, c: &Point2D<f32>, p: &Point2D<f32>)
-> ([f64; 2], f64) {
let (a, b, c, p) = (a.to_f64(), b.to_f64(), c.to_f64(), p.to_f64());
let (v0, v1, v2) = (b - a, c - a, p - a);
let (d00, d01) = (v0.dot(v0), v0.dot(v1));
let d11 = v1.dot(v1);
let (d20, d21) = (v2.dot(v0), v2.dot(v1));
let denom = d00 * d11 - d01 * d01;
([(d11 * d20 - d01 * d21), (d00 * d21 - d01 * d20)], denom)
}
fn lambda_to_uv(lambda: &[f64; 2], denom: f64) -> Point2D<f32> {
(Point2D::new(lambda[0] * 0.5 + lambda[1], lambda[1]) / denom).to_f32()
}
}
fn line(rect: &Rect<f32>, left: &Point2D<f32>, right: &Point2D<f32>) -> Uv {
let (values, line_bounds);
if f32::abs(left.y - right.y) < 0.01 {
values = CornerValues {
upper_left: Point2D::new(0.0, 0.5),
upper_right: Point2D::new(0.5, 1.0),
lower_right: Point2D::new(1.0, 0.5),
lower_left: Point2D::new(0.5, 0.0),
};
line_bounds = Rect::new(*left + Vector2D::new(0.0, -1.0),
Size2D::new(right.x - left.x, 2.0));
} else {
if left.y < right.y {
values = CornerValues {
upper_left: Point2D::new(1.0, 1.0),
upper_right: Point2D::new(0.0, 1.0),
lower_left: Point2D::new(1.0, 0.0),
lower_right: Point2D::new(0.0, 0.0),
};
} else {
values = CornerValues {
upper_left: Point2D::new(0.0, 1.0),
upper_right: Point2D::new(1.0, 1.0),
lower_left: Point2D::new(0.0, 0.0),
lower_right: Point2D::new(1.0, 0.0),
};
}
line_bounds = Rect::from_points([*left, *right].into_iter());
}
let origin_right = rect.top_right();
let origin_down = rect.bottom_left();
let uv_origin = bilerp(&line_bounds, &values, &rect.origin);
let uv_origin_right = bilerp(&line_bounds, &values, &origin_right);
let uv_origin_down = bilerp(&line_bounds, &values, &origin_down);
return Uv::from_values(&uv_origin, &uv_origin_right, &uv_origin_down);
fn bilerp(rect: &Rect<f32>, values: &CornerValues, position: &Point2D<f32>)
-> Point2D<f32> {
let upper = values.upper_left.lerp(values.upper_right,
(position.x - rect.min_x()) / rect.size.width);
let lower = values.lower_left.lerp(values.lower_right,
(position.x - rect.min_x()) / rect.size.width);
upper.lerp(lower, (position.y - rect.min_y()) / rect.size.height)
}
}
}

View File

@ -1,92 +0,0 @@
// pathfinder/partitioner/src/mesh_pack.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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 bincode;
use byteorder::{LittleEndian, WriteBytesExt};
use mesh::Mesh;
use serde::Serialize;
use std::io::{self, ErrorKind, Seek, SeekFrom, Write};
use std::u32;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshPack {
pub meshes: Vec<Mesh>,
}
impl MeshPack {
#[inline]
pub fn new() -> MeshPack {
MeshPack {
meshes: vec![],
}
}
#[inline]
pub fn push(&mut self, mesh: Mesh) {
self.meshes.push(mesh)
}
/// Writes this mesh pack to a RIFF file.
///
/// RIFF is a dead-simple extensible binary format documented here:
/// https://msdn.microsoft.com/en-us/library/windows/desktop/ee415713(v=vs.85).aspx
pub fn serialize_into<W>(&self, writer: &mut W) -> io::Result<()> where W: Write + Seek {
// `PFMP` for "Pathfinder Mesh Pack".
try!(writer.write_all(b"RIFF\0\0\0\0PFMP"));
// NB: The RIFF spec requires that all chunks be padded to an even byte offset. However,
// for us, this is guaranteed by construction because each instance of all of the data that
// we're writing has a byte size that is a multiple of 4. So we don't bother with doing it
// explicitly here.
for mesh in &self.meshes {
try!(write_chunk(writer, b"mesh", |writer| {
try!(write_simple_chunk(writer, b"bqua", &mesh.b_quads));
try!(write_simple_chunk(writer, b"bqvp", &mesh.b_quad_vertex_positions));
try!(write_simple_chunk(writer, b"bqii", &mesh.b_quad_vertex_interior_indices));
try!(write_simple_chunk(writer, b"bbox", &mesh.b_boxes));
try!(write_simple_chunk(writer, b"sseg", &mesh.stencil_segments));
try!(write_simple_chunk(writer, b"snor", &mesh.stencil_normals));
Ok(())
}));
}
let total_length = try!(writer.seek(SeekFrom::Current(0)));
try!(writer.seek(SeekFrom::Start(4)));
try!(writer.write_u32::<LittleEndian>((total_length - 8) as u32));
return Ok(());
fn write_chunk<W, F>(writer: &mut W, tag: &[u8; 4], mut closure: F) -> io::Result<()>
where W: Write + Seek, F: FnMut(&mut W) -> io::Result<()> {
try!(writer.write_all(tag));
try!(writer.write_all(b"\0\0\0\0"));
let start_position = try!(writer.seek(SeekFrom::Current(0)));
try!(closure(writer));
let end_position = try!(writer.seek(SeekFrom::Current(0)));
try!(writer.seek(SeekFrom::Start(start_position - 4)));
try!(writer.write_u32::<LittleEndian>((end_position - start_position) as u32));
try!(writer.seek(SeekFrom::Start(end_position)));
Ok(())
}
fn write_simple_chunk<W, T>(writer: &mut W, tag: &[u8; 4], data: &[T]) -> io::Result<()>
where W: Write + Seek, T: Serialize {
write_chunk(writer, tag, |writer| {
for datum in data {
try!(bincode::serialize_into(&mut *writer, datum).map_err(|_| {
io::Error::from(ErrorKind::Other)
}));
}
Ok(())
})
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +0,0 @@
[package]
name = "pathfinder_path_utils"
version = "0.2.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
description = "Miscellaneous Bézier curve algorithms"
license = "MIT/Apache-2.0"
[dependencies]
arrayvec = "0.4"
lyon_path = "0.12"
serde = "1.0"
serde_derive = "1.0"

View File

@ -1,137 +0,0 @@
// pathfinder/partitioner/src/cubic_to_quadratic.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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 version of Lyon's `cubic_to_quadratic` that is less sensitive to floating point error.
use euclid::Point2D;
use lyon_geom::{CubicBezierSegment, QuadraticBezierSegment};
use lyon_path::PathEvent;
const MAX_APPROXIMATION_ITERATIONS: u8 = 32;
/// Approximates a single cubic Bézier curve with a series of quadratic Bézier curves.
pub struct CubicToQuadraticSegmentIter {
cubic_curves: Vec<CubicBezierSegment<f32>>,
error_bound: f32,
iteration: u8,
}
impl CubicToQuadraticSegmentIter {
pub fn new(cubic: &CubicBezierSegment<f32>, error_bound: f32) -> CubicToQuadraticSegmentIter {
let (curve_a, curve_b) = cubic.split(0.5);
CubicToQuadraticSegmentIter {
cubic_curves: vec![curve_b, curve_a],
error_bound: error_bound,
iteration: 0,
}
}
}
impl Iterator for CubicToQuadraticSegmentIter {
type Item = QuadraticBezierSegment<f32>;
fn next(&mut self) -> Option<QuadraticBezierSegment<f32>> {
let mut cubic = match self.cubic_curves.pop() {
Some(cubic) => cubic,
None => return None,
};
while self.iteration < MAX_APPROXIMATION_ITERATIONS {
self.iteration += 1;
// See Sederberg § 2.6, "Distance Between Two Bézier Curves".
let delta_ctrl_0 = (cubic.from - cubic.ctrl1 * 3.0) + (cubic.ctrl2 * 3.0 - cubic.to);
let delta_ctrl_1 = (cubic.ctrl1 * 3.0 - cubic.from) + (cubic.to - cubic.ctrl2 * 3.0);
let max_error = f32::max(delta_ctrl_1.length(), delta_ctrl_0.length()) / 6.0;
if max_error < self.error_bound {
break
}
let (cubic_a, cubic_b) = cubic.split(0.5);
self.cubic_curves.push(cubic_b);
cubic = cubic_a
}
let approx_ctrl_0 = (cubic.ctrl1 * 3.0 - cubic.from) * 0.5;
let approx_ctrl_1 = (cubic.ctrl2 * 3.0 - cubic.to) * 0.5;
Some(QuadraticBezierSegment {
from: cubic.from,
ctrl: approx_ctrl_0.lerp(approx_ctrl_1, 0.5).to_point(),
to: cubic.to,
})
}
}
pub struct CubicToQuadraticTransformer<I> where I: Iterator<Item = PathEvent> {
inner: I,
segment_iter: Option<CubicToQuadraticSegmentIter>,
last_point: Point2D<f32>,
error_bound: f32,
}
impl<I> CubicToQuadraticTransformer<I> where I: Iterator<Item = PathEvent> {
#[inline]
pub fn new(inner: I, error_bound: f32) -> CubicToQuadraticTransformer<I> {
CubicToQuadraticTransformer {
inner: inner,
segment_iter: None,
last_point: Point2D::zero(),
error_bound: error_bound,
}
}
}
impl<I> Iterator for CubicToQuadraticTransformer<I> where I: Iterator<Item = PathEvent> {
type Item = PathEvent;
fn next(&mut self) -> Option<PathEvent> {
if let Some(ref mut segment_iter) = self.segment_iter {
if let Some(quadratic) = segment_iter.next() {
return Some(PathEvent::QuadraticTo(quadratic.ctrl, quadratic.to))
}
}
self.segment_iter = None;
match self.inner.next() {
None => None,
Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => {
let cubic = CubicBezierSegment {
from: self.last_point,
ctrl1: ctrl1,
ctrl2: ctrl2,
to: to,
};
self.last_point = to;
self.segment_iter = Some(CubicToQuadraticSegmentIter::new(&cubic,
self.error_bound));
self.next()
}
Some(PathEvent::MoveTo(to)) => {
self.last_point = to;
Some(PathEvent::MoveTo(to))
}
Some(PathEvent::LineTo(to)) => {
self.last_point = to;
Some(PathEvent::LineTo(to))
}
Some(PathEvent::QuadraticTo(ctrl, to)) => {
self.last_point = to;
Some(PathEvent::QuadraticTo(ctrl, to))
}
Some(PathEvent::Close) => Some(PathEvent::Close),
Some(PathEvent::Arc(to, vector, angle_from, angle_to)) => {
self.last_point = to;
Some(PathEvent::Arc(to, vector, angle_from, angle_to))
}
}
}
}

View File

@ -1,26 +0,0 @@
// pathfinder/path-utils/src/lib.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
//! Various utilities for manipulating Bézier curves.
//!
//! Most of these should go upstream to Lyon at some point.
extern crate arrayvec;
extern crate lyon_path;
use lyon_path::geom as lyon_geom;
use lyon_path::geom::euclid;
pub mod cubic_to_quadratic;
pub mod normals;
pub mod orientation;
pub mod segments;
pub mod stroke;
pub mod transform;

View File

@ -1,180 +0,0 @@
// pathfinder/path-utils/src/normals.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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 euclid::approxeq::ApproxEq;
use euclid::{Point2D, Vector2D};
use lyon_path::PathEvent;
use orientation::Orientation;
#[derive(Clone, Copy, Debug)]
pub struct SegmentNormals {
pub from: Vector2D<f32>,
pub ctrl: Vector2D<f32>,
pub to: Vector2D<f32>,
}
#[derive(Clone)]
pub struct PathNormals {
normals: Vec<SegmentNormals>,
}
impl PathNormals {
#[inline]
pub fn new() -> PathNormals {
PathNormals {
normals: vec![],
}
}
#[inline]
pub fn normals(&self) -> &[SegmentNormals] {
&self.normals
}
pub fn clear(&mut self) {
self.normals.clear()
}
pub fn add_path<I>(&mut self, stream: I) where I: Iterator<Item = PathEvent> {
let events: Vec<_> = stream.collect();
let orientation = Orientation::from_path(events.iter().cloned());
let (mut path_ops, mut path_points) = (vec![], vec![]);
let mut stream = events.iter().cloned();
while let Some(event) = stream.next() {
path_ops.push(PathOp::from_path_event(&event));
match event {
PathEvent::MoveTo(to) => path_points.push(to),
PathEvent::LineTo(to) => path_points.push(to),
PathEvent::QuadraticTo(ctrl, to) => path_points.extend_from_slice(&[ctrl, to]),
PathEvent::CubicTo(..) => {
panic!("PathNormals::add_path(): Convert cubics to quadratics first!")
}
PathEvent::Arc(..) => {
panic!("PathNormals::add_path(): Convert arcs to quadratics first!")
}
PathEvent::Close => self.flush(orientation, path_ops.drain(..), &mut path_points),
}
}
self.flush(orientation, path_ops.into_iter(), &mut path_points);
}
fn flush<I>(&mut self,
orientation: Orientation,
path_stream: I,
path_points: &mut Vec<Point2D<f32>>)
where I: Iterator<Item = PathOp> {
match path_points.len() {
0 | 1 => path_points.clear(),
2 => {
let orientation = -(orientation as i32 as f32);
self.normals.push(SegmentNormals {
from: (path_points[1] - path_points[0]) * orientation,
ctrl: Vector2D::zero(),
to: (path_points[0] - path_points[1]) * orientation,
});
path_points.clear();
}
_ => self.flush_slow(orientation, path_stream, path_points),
}
}
fn flush_slow<I>(&mut self,
orientation: Orientation,
path_stream: I,
path_points: &mut Vec<Point2D<f32>>)
where I: Iterator<Item = PathOp> {
let mut normals = vec![Vector2D::zero(); path_points.len()];
for (index, point) in path_points.iter().enumerate() {
let (mut prev_index, mut next_index) = (index, index);
while path_points[prev_index].approx_eq(&path_points[index]) {
prev_index = if prev_index == 0 {
path_points.len() - 1
} else {
prev_index - 1
}
}
while path_points[next_index].approx_eq(&path_points[index]) {
next_index = if next_index == path_points.len() - 1 {
0
} else {
next_index + 1
}
}
normals[index] = compute_normal(orientation,
&path_points[prev_index],
point,
&path_points[next_index])
}
path_points.clear();
let mut next_normal_index = 0;
for op in path_stream {
match op {
PathOp::MoveTo => next_normal_index += 1,
PathOp::LineTo => {
next_normal_index += 1;
self.normals.push(SegmentNormals {
from: normals[next_normal_index - 2],
ctrl: normals[next_normal_index - 2].lerp(normals[next_normal_index - 1],
0.5),
to: normals[next_normal_index - 1],
});
}
PathOp::QuadraticTo => {
next_normal_index += 2;
self.normals.push(SegmentNormals {
from: normals[next_normal_index - 3],
ctrl: normals[next_normal_index - 2],
to: normals[next_normal_index - 1],
})
}
PathOp::Close => {
self.normals.push(SegmentNormals {
from: normals[next_normal_index - 1],
ctrl: normals[next_normal_index - 1].lerp(normals[0], 0.5),
to: normals[0],
});
break;
}
}
}
}
}
fn compute_normal(orientation: Orientation,
prev: &Point2D<f32>,
current: &Point2D<f32>,
next: &Point2D<f32>)
-> Vector2D<f32> {
let vector = ((*current - *prev) + (*next - *current)).normalize();
Vector2D::new(vector.y, -vector.x) * -(orientation as i32 as f32)
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum PathOp {
MoveTo,
LineTo,
QuadraticTo,
Close,
}
impl PathOp {
fn from_path_event(event: &PathEvent) -> PathOp {
match *event {
PathEvent::MoveTo(..) => PathOp::MoveTo,
PathEvent::LineTo(..) => PathOp::LineTo,
PathEvent::QuadraticTo(..) => PathOp::QuadraticTo,
PathEvent::Close => PathOp::Close,
PathEvent::Arc(..) | PathEvent::CubicTo(..) => unreachable!(),
}
}
}

View File

@ -1,62 +0,0 @@
// pathfinder/path-utils/src/orientation.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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 euclid::Point2D;
use lyon_path::PathEvent;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Orientation {
Ccw = -1,
Cw = 1,
}
impl Orientation {
/// This follows the FreeType algorithm.
pub fn from_path<I>(stream: I) -> Orientation where I: Iterator<Item = PathEvent> {
let (mut from, mut subpath_start) = (Point2D::zero(), Point2D::zero());
let mut area = 0.0;
for event in stream {
match event {
PathEvent::MoveTo(to) => {
from = to;
subpath_start = to;
}
PathEvent::LineTo(to) => {
area += det(&from, &to);
from = to;
}
PathEvent::QuadraticTo(ctrl, to) => {
area += det(&from, &ctrl) + det(&ctrl, &to);
from = to;
}
PathEvent::CubicTo(ctrl0, ctrl1, to) => {
area += det(&from, &ctrl0) + det(&ctrl0, &ctrl1) + det(&ctrl1, &to);
from = to;
}
PathEvent::Arc(..) => {
// TODO(pcwalton)
}
PathEvent::Close => {
area += det(&from, &subpath_start);
from = subpath_start;
}
}
}
if area <= 0.0 {
Orientation::Ccw
} else {
Orientation::Cw
}
}
}
fn det(a: &Point2D<f32>, b: &Point2D<f32>) -> f32 {
a.x * b.y - a.y * b.x
}

View File

@ -1,262 +0,0 @@
// pathfinder/path-utils/src/segments.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
//! Returns each segment of a path.
use euclid::approxeq::ApproxEq;
use euclid::{Point2D, Vector2D};
use lyon_geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment};
use lyon_path::iterator::{PathIter, PathIterator};
use lyon_path::PathEvent;
pub struct SegmentIter<I> where I: Iterator<Item = PathEvent> {
inner: PathIter<I>,
stack: Vec<Segment>,
was_just_closed: bool,
}
impl<I> SegmentIter<I> where I: Iterator<Item = PathEvent> {
#[inline]
pub fn new(inner: I) -> SegmentIter<I> {
SegmentIter {
inner: PathIter::new(inner),
stack: vec![],
was_just_closed: true,
}
}
}
impl<I> Iterator for SegmentIter<I> where I: Iterator<Item = PathEvent> {
type Item = Segment;
fn next(&mut self) -> Option<Segment> {
if let Some(segment) = self.stack.pop() {
return Some(segment)
}
let current_point = self.inner.get_state().current;
match self.inner.next() {
None => None,
Some(PathEvent::Close) => {
self.was_just_closed = true;
let state = self.inner.get_state();
self.stack.push(Segment::EndSubpath(true));
Some(Segment::Line(LineSegment {
from: current_point,
to: state.first,
}))
}
Some(PathEvent::MoveTo(_)) => {
if self.was_just_closed {
self.was_just_closed = false;
return self.next();
}
Some(Segment::EndSubpath(false))
}
Some(PathEvent::LineTo(to)) => {
Some(Segment::Line(LineSegment {
from: current_point,
to: to,
}))
}
Some(PathEvent::QuadraticTo(ctrl, to)) => {
Some(Segment::Quadratic(QuadraticBezierSegment {
from: current_point,
ctrl: ctrl,
to: to,
}))
}
Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => {
Some(Segment::Cubic(CubicBezierSegment {
from: current_point,
ctrl1: ctrl1,
ctrl2: ctrl2,
to: to,
}))
}
Some(PathEvent::Arc(..)) => {
panic!("SegmentIter doesn't support cubics and arcs yet!")
}
}
}
}
#[derive(Clone, Copy)]
pub enum Segment {
Line(LineSegment<f32>),
Quadratic(QuadraticBezierSegment<f32>),
Cubic(CubicBezierSegment<f32>),
/// True if the subpath is closed.
EndSubpath(bool),
}
impl Segment {
pub fn flip(&self) -> Segment {
match *self {
Segment::EndSubpath(closed) => Segment::EndSubpath(closed),
Segment::Line(line_segment) => Segment::Line(line_segment.flip()),
Segment::Quadratic(quadratic_segment) => Segment::Quadratic(quadratic_segment.flip()),
Segment::Cubic(cubic_segment) => Segment::Cubic(cubic_segment.flip()),
}
}
pub fn offset<F>(&self, distance: f32, mut sink: F) where F: FnMut(&Segment) {
match *self {
Segment::EndSubpath(_) => {}
Segment::Line(ref segment) => {
sink(&Segment::Line(offset_line_segment(segment, distance)))
}
Segment::Quadratic(ref quadratic_segment) => {
// This is the Tiller & Hanson 1984 algorithm for approximate Bézier offset curves.
// We take the cage (i.e. convex hull) and push its edges out along their normals,
// then recompute the control point with a miter join.
let line_segments = (LineSegment {
from: quadratic_segment.from,
to: quadratic_segment.ctrl,
}, LineSegment {
from: quadratic_segment.ctrl,
to: quadratic_segment.to,
});
// Miter join.
let (from, intersection, to) = match offset_and_join_line_segments(line_segments.0,
line_segments.1,
distance) {
None => return sink(self),
Some(intersection) => intersection,
};
sink(&Segment::Quadratic(QuadraticBezierSegment {
from: from,
ctrl: intersection,
to: to,
}))
}
Segment::Cubic(ref cubic_segment) if points_overlap(&cubic_segment.from,
&cubic_segment.ctrl1) => {
// As above.
let line_segments = (LineSegment {
from: cubic_segment.from,
to: cubic_segment.ctrl2,
}, LineSegment {
from: cubic_segment.ctrl2,
to: cubic_segment.to,
});
// Miter join.
let (from, intersection, to) = match offset_and_join_line_segments(line_segments.0,
line_segments.1,
distance) {
None => return sink(self),
Some(intersection) => intersection,
};
sink(&Segment::Cubic(CubicBezierSegment {
from: from,
ctrl1: from,
ctrl2: intersection,
to: to,
}))
}
Segment::Cubic(ref cubic_segment) if points_overlap(&cubic_segment.ctrl2,
&cubic_segment.to) => {
// As above.
let line_segments = (LineSegment {
from: cubic_segment.from,
to: cubic_segment.ctrl1,
}, LineSegment {
from: cubic_segment.ctrl1,
to: cubic_segment.to,
});
// Miter join.
let (from, intersection, to) = match offset_and_join_line_segments(line_segments.0,
line_segments.1,
distance) {
None => return sink(self),
Some(intersection) => intersection,
};
sink(&Segment::Cubic(CubicBezierSegment {
from: from,
ctrl1: intersection,
ctrl2: to,
to: to,
}))
}
Segment::Cubic(ref cubic_segment) => {
// As above.
let line_segments = (LineSegment {
from: cubic_segment.from,
to: cubic_segment.ctrl1,
}, LineSegment {
from: cubic_segment.ctrl1,
to: cubic_segment.ctrl2,
}, LineSegment {
from: cubic_segment.ctrl2,
to: cubic_segment.to,
});
let (from, intersection_0, _) =
match offset_and_join_line_segments(line_segments.0,
line_segments.1,
distance) {
None => return sink(self),
Some(intersection) => intersection,
};
let (_, intersection_1, to) = match offset_and_join_line_segments(line_segments.1,
line_segments.2,
distance) {
None => return sink(self),
Some(intersection) => intersection,
};
sink(&Segment::Cubic(CubicBezierSegment {
from: from,
ctrl1: intersection_0,
ctrl2: intersection_1,
to: to,
}))
}
}
}
}
fn offset_line_segment(segment: &LineSegment<f32>, distance: f32) -> LineSegment<f32> {
let mut segment = *segment;
let vector = segment.to_vector();
if vector.square_length() < f32::approx_epsilon() {
return segment
}
let tangent = vector.normalize() * distance;
segment.translate(Vector2D::new(-tangent.y, tangent.x))
}
// Performs a miter join.
fn offset_and_join_line_segments(mut line_segment_0: LineSegment<f32>,
mut line_segment_1: LineSegment<f32>,
distance: f32)
-> Option<(Point2D<f32>, Point2D<f32>, Point2D<f32>)> {
line_segment_0 = offset_line_segment(&line_segment_0, distance);
line_segment_1 = offset_line_segment(&line_segment_1, distance);
match line_segment_0.to_line().intersection(&line_segment_1.to_line()) {
None => None,
Some(intersection) => Some((line_segment_0.from, intersection, line_segment_1.to)),
}
}
fn points_overlap(a: &Point2D<f32>, b: &Point2D<f32>) -> bool {
a.x.approx_eq(&b.x) && a.y.approx_eq(&b.y)
}

View File

@ -1,147 +0,0 @@
// pathfinder/path-utils/src/stroke.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
//! Utilities for converting path strokes to fills.
use lyon_path::PathEvent;
use lyon_path::iterator::PathIterator;
use segments::{Segment, SegmentIter};
#[derive(Clone, Copy, Debug)]
pub struct StrokeStyle {
pub width: f32,
}
impl StrokeStyle {
#[inline]
pub fn new(width: f32) -> StrokeStyle {
StrokeStyle {
width: width,
}
}
}
pub struct StrokeToFillIter<I> where I: PathIterator {
inner: SegmentIter<I>,
subpath: Vec<Segment>,
stack: Vec<PathEvent>,
state: StrokeToFillState,
style: StrokeStyle,
first_point_in_subpath: bool,
}
impl<I> StrokeToFillIter<I> where I: PathIterator {
#[inline]
pub fn new(inner: I, style: StrokeStyle) -> StrokeToFillIter<I> {
StrokeToFillIter {
inner: SegmentIter::new(inner),
subpath: vec![],
stack: vec![],
state: StrokeToFillState::Forward,
style: style,
first_point_in_subpath: true,
}
}
}
impl<I> Iterator for StrokeToFillIter<I> where I: PathIterator {
type Item = PathEvent;
// TODO(pcwalton): Support miter and round joins. This will probably require the inner iterator
// to be `Peekable`, I guess.
fn next(&mut self) -> Option<PathEvent> {
// If we have path events queued, return the latest.
if let Some(path_event) = self.stack.pop() {
return Some(path_event)
}
// Fetch the next segment.
let next_segment = match self.state {
StrokeToFillState::Forward => {
match self.inner.next() {
None | Some(Segment::EndSubpath(false)) => {
if self.subpath.is_empty() {
return None
}
self.state = StrokeToFillState::Backward;
return self.next()
}
Some(Segment::EndSubpath(true)) => {
if self.subpath.is_empty() {
return None
}
self.state = StrokeToFillState::Backward;
self.first_point_in_subpath = true;
return Some(PathEvent::Close)
}
Some(segment) => {
self.subpath.push(segment);
segment
}
}
}
StrokeToFillState::Backward => {
match self.subpath.pop() {
None | Some(Segment::EndSubpath(_)) => {
self.state = StrokeToFillState::Forward;
self.first_point_in_subpath = true;
return Some(PathEvent::Close)
}
Some(segment) => segment.flip(),
}
}
};
next_segment.offset(self.style.width * 0.5, |offset_segment| {
match *offset_segment {
Segment::EndSubpath(_) => unreachable!(),
Segment::Line(ref offset_segment) => {
if self.first_point_in_subpath {
self.first_point_in_subpath = false;
self.stack.push(PathEvent::MoveTo(offset_segment.from))
} else if self.stack.is_empty() {
self.stack.push(PathEvent::LineTo(offset_segment.from))
}
self.stack.push(PathEvent::LineTo(offset_segment.to))
}
Segment::Quadratic(ref offset_segment) => {
if self.first_point_in_subpath {
self.first_point_in_subpath = false;
self.stack.push(PathEvent::MoveTo(offset_segment.from))
} else if self.stack.is_empty() {
self.stack.push(PathEvent::LineTo(offset_segment.from))
}
self.stack.push(PathEvent::QuadraticTo(offset_segment.ctrl, offset_segment.to))
}
Segment::Cubic(ref offset_segment) => {
if self.first_point_in_subpath {
self.first_point_in_subpath = false;
self.stack.push(PathEvent::MoveTo(offset_segment.from))
} else if self.stack.is_empty() {
self.stack.push(PathEvent::LineTo(offset_segment.from))
}
self.stack.push(PathEvent::CubicTo(offset_segment.ctrl1,
offset_segment.ctrl2,
offset_segment.to))
}
}
});
self.stack.reverse();
return self.next()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum StrokeToFillState {
Forward,
Backward,
}

View File

@ -1,60 +0,0 @@
// pathfinder/path-utils/src/transform.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
//! Applies a transform to paths.
use euclid::Transform2D;
use lyon_path::PathEvent;
pub struct Transform2DPathIter<I> where I: Iterator<Item = PathEvent> {
inner: I,
transform: Transform2D<f32>,
}
impl<I> Transform2DPathIter<I> where I: Iterator<Item = PathEvent> {
#[inline]
pub fn new(inner: I, transform: &Transform2D<f32>) -> Transform2DPathIter<I> {
Transform2DPathIter {
inner: inner,
transform: *transform,
}
}
}
impl<I> Iterator for Transform2DPathIter<I> where I: Iterator<Item = PathEvent> {
type Item = PathEvent;
fn next(&mut self) -> Option<PathEvent> {
match self.inner.next() {
Some(PathEvent::MoveTo(to)) => {
Some(PathEvent::MoveTo(self.transform.transform_point(&to)))
}
Some(PathEvent::LineTo(to)) => {
Some(PathEvent::LineTo(self.transform.transform_point(&to)))
}
Some(PathEvent::QuadraticTo(ctrl, to)) => {
Some(PathEvent::QuadraticTo(self.transform.transform_point(&ctrl),
self.transform.transform_point(&to)))
}
Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => {
Some(PathEvent::CubicTo(self.transform.transform_point(&ctrl1),
self.transform.transform_point(&ctrl2),
self.transform.transform_point(&to)))
}
Some(PathEvent::Arc(center, radius, start, end)) => {
Some(PathEvent::Arc(self.transform.transform_point(&center),
self.transform.transform_vector(&radius),
start,
end))
}
event => event,
}
}
}

29
renderer/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "pathfinder_renderer"
version = "0.1.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
byteorder = "1.2"
fixedbitset = "0.1"
hashbrown = "0.1"
rayon = "1.0"
serde = "1.0"
serde_json = "1.0"
smallvec = "0.6"
[dependencies.pathfinder_geometry]
path = "../geometry"
[dependencies.pathfinder_gpu]
path = "../gpu"
[dependencies.pathfinder_simd]
path = "../simd"
[dependencies.pathfinder_ui]
path = "../ui"
[dev-dependencies]
quickcheck = "0.7"

248
renderer/src/builder.rs Normal file
View File

@ -0,0 +1,248 @@
// pathfinder/renderer/src/builder.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Packs data onto the GPU.
use crate::gpu_data::{AlphaTileBatchPrimitive, RenderCommand};
use crate::scene::Scene;
use crate::tiles::Tiler;
use crate::z_buffer::ZBuffer;
use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_geometry::clip::PolygonClipper3D;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::sync::atomic::AtomicUsize;
use std::u16;
pub trait RenderCommandListener: Send + Sync {
fn send(&self, command: RenderCommand);
}
pub struct SceneBuilder<'a> {
scene: &'a Scene,
built_options: &'a PreparedRenderOptions,
pub(crate) next_alpha_tile_index: AtomicUsize,
pub(crate) z_buffer: ZBuffer,
pub(crate) listener: Box<dyn RenderCommandListener>,
}
impl<'a> SceneBuilder<'a> {
pub fn new(scene: &'a Scene,
built_options: &'a PreparedRenderOptions,
listener: Box<dyn RenderCommandListener>)
-> SceneBuilder<'a> {
let effective_view_box = scene.effective_view_box(built_options);
SceneBuilder {
scene,
built_options,
next_alpha_tile_index: AtomicUsize::new(0),
z_buffer: ZBuffer::new(effective_view_box),
listener,
}
}
pub fn build_sequentially(&mut self) {
let effective_view_box = self.scene.effective_view_box(self.built_options);
let object_count = self.scene.objects.len();
let alpha_tiles: Vec<_> = (0..object_count).into_iter().flat_map(|object_index| {
self.build_object(object_index,
effective_view_box,
&self.built_options,
&self.scene)
}).collect();
self.finish_building(alpha_tiles)
}
pub fn build_in_parallel(&mut self) {
let effective_view_box = self.scene.effective_view_box(self.built_options);
let object_count = self.scene.objects.len();
let alpha_tiles: Vec<_> = (0..object_count).into_par_iter().flat_map(|object_index| {
self.build_object(object_index,
effective_view_box,
&self.built_options,
&self.scene)
}).collect();
self.finish_building(alpha_tiles)
}
fn build_object(&self,
object_index: usize,
view_box: RectF32,
built_options: &PreparedRenderOptions,
scene: &Scene)
-> Vec<AlphaTileBatchPrimitive> {
let object = &scene.objects[object_index];
let outline = scene.apply_render_options(object.outline(), built_options);
let mut tiler = Tiler::new(self, &outline, view_box, object_index as u16);
tiler.generate_tiles();
self.listener.send(RenderCommand::AddFills(tiler.built_object.fills));
tiler.built_object.alpha_tiles
}
fn cull_alpha_tiles(&self, alpha_tiles: &mut Vec<AlphaTileBatchPrimitive>) {
for alpha_tile in alpha_tiles {
let alpha_tile_coords = alpha_tile.tile_coords();
if self.z_buffer.test(alpha_tile_coords, alpha_tile.object_index as u32) {
continue;
}
// FIXME(pcwalton): Clean this up.
alpha_tile.tile_x_lo = 0xff;
alpha_tile.tile_y_lo = 0xff;
alpha_tile.tile_hi = 0xff;
}
}
fn pack_alpha_tiles(&mut self, alpha_tiles: Vec<AlphaTileBatchPrimitive>) {
let object_count = self.scene.objects.len() as u32;
let solid_tiles = self.z_buffer.build_solid_tiles(0..object_count);
if !solid_tiles.is_empty() {
self.listener.send(RenderCommand::SolidTile(solid_tiles));
}
if !alpha_tiles.is_empty() {
self.listener.send(RenderCommand::AlphaTile(alpha_tiles));
}
}
fn finish_building(&mut self, mut alpha_tiles: Vec<AlphaTileBatchPrimitive>) {
self.listener.send(RenderCommand::FlushFills);
self.cull_alpha_tiles(&mut alpha_tiles);
self.pack_alpha_tiles(alpha_tiles);
}
}
#[derive(Clone, Default)]
pub struct RenderOptions {
pub transform: RenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
}
impl RenderOptions {
pub fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
PreparedRenderOptions {
transform: self.transform.prepare(bounds),
dilation: self.dilation,
subpixel_aa_enabled: self.subpixel_aa_enabled,
}
}
}
#[derive(Clone)]
pub enum RenderTransform {
Transform2D(Transform2DF32),
Perspective(Perspective),
}
impl Default for RenderTransform {
#[inline]
fn default() -> RenderTransform {
RenderTransform::Transform2D(Transform2DF32::default())
}
}
impl RenderTransform {
fn prepare(&self, bounds: RectF32) -> PreparedRenderTransform {
let perspective = match self {
RenderTransform::Transform2D(ref transform) => {
if transform.is_identity() {
return PreparedRenderTransform::None;
}
return PreparedRenderTransform::Transform2D(*transform);
}
RenderTransform::Perspective(ref perspective) => *perspective,
};
let mut points = vec![
bounds.origin().to_3d(),
bounds.upper_right().to_3d(),
bounds.lower_right().to_3d(),
bounds.lower_left().to_3d(),
];
//println!("-----");
//println!("bounds={:?} ORIGINAL quad={:?}", self.bounds, points);
for point in &mut points {
*point = perspective.transform.transform_point(*point);
}
//println!("... PERSPECTIVE quad={:?}", points);
// Compute depth.
let quad = [
points[0].perspective_divide(),
points[1].perspective_divide(),
points[2].perspective_divide(),
points[3].perspective_divide(),
];
//println!("... PERSPECTIVE-DIVIDED points = {:?}", quad);
points = PolygonClipper3D::new(points).clip();
//println!("... CLIPPED quad={:?}", points);
for point in &mut points {
*point = point.perspective_divide()
}
let inverse_transform = perspective.transform.inverse();
let clip_polygon = points.into_iter().map(|point| {
inverse_transform.transform_point(point).perspective_divide().to_2d()
}).collect();
return PreparedRenderTransform::Perspective { perspective, clip_polygon, quad };
}
}
pub struct PreparedRenderOptions {
pub transform: PreparedRenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
}
impl PreparedRenderOptions {
#[inline]
pub fn bounding_quad(&self) -> [Point3DF32; 4] {
match self.transform {
PreparedRenderTransform::Perspective { quad, .. } => quad,
_ => [Point3DF32::default(); 4],
}
}
}
pub enum PreparedRenderTransform {
None,
Transform2D(Transform2DF32),
Perspective { perspective: Perspective, clip_polygon: Vec<Point2DF32>, quad: [Point3DF32; 4] }
}
impl PreparedRenderTransform {
#[inline]
pub fn is_2d(&self) -> bool {
match *self {
PreparedRenderTransform::Transform2D(_) => true,
_ => false,
}
}
}
impl<F> RenderCommandListener for F where F: Fn(RenderCommand) + Send + Sync {
#[inline]
fn send(&self, command: RenderCommand) { (*self)(command) }
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TileStats {
pub solid_tile_count: u32,
pub alpha_tile_count: u32,
}

170
renderer/src/gpu/debug.rs Normal file
View File

@ -0,0 +1,170 @@
// pathfinder/renderer/src/gpu/debug.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 debug overlay.
//!
//! We don't render the demo UI text using Pathfinder itself so that we can use the debug UI to
//! debug Pathfinder if it's totally busted.
//!
//! The debug font atlas was generated using: https://evanw.github.io/font-texture-generator/
use crate::gpu::renderer::RenderStats;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::Device;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_ui::{FONT_ASCENT, LINE_HEIGHT, PADDING, UI, WINDOW_COLOR};
use std::collections::VecDeque;
use std::ops::{Add, Div};
use std::time::Duration;
const SAMPLE_BUFFER_SIZE: usize = 60;
const PERF_WINDOW_WIDTH: i32 = 375;
const PERF_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 6 + PADDING + 2;
pub struct DebugUI<D> where D: Device {
pub ui: UI<D>,
cpu_samples: SampleBuffer<CPUSample>,
gpu_samples: SampleBuffer<GPUSample>,
}
impl<D> DebugUI<D> where D: Device {
pub fn new(device: &D, resources: &dyn ResourceLoader, framebuffer_size: Point2DI32)
-> DebugUI<D> {
let ui = UI::new(device, resources, framebuffer_size);
DebugUI { ui, cpu_samples: SampleBuffer::new(), gpu_samples: SampleBuffer::new() }
}
pub fn add_sample(&mut self,
stats: RenderStats,
tile_time: Duration,
rendering_time: Option<Duration>) {
self.cpu_samples.push(CPUSample { stats, elapsed: tile_time });
if let Some(rendering_time) = rendering_time {
self.gpu_samples.push(GPUSample { elapsed: rendering_time });
}
}
pub fn draw(&self, device: &D) {
// Draw performance window.
let framebuffer_size = self.ui.framebuffer_size();
let bottom = framebuffer_size.y() - PADDING;
let window_rect = RectI32::new(
Point2DI32::new(framebuffer_size.x() - PADDING - PERF_WINDOW_WIDTH,
bottom - PERF_WINDOW_HEIGHT),
Point2DI32::new(PERF_WINDOW_WIDTH, PERF_WINDOW_HEIGHT));
self.ui.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR);
let origin = window_rect.origin() + Point2DI32::new(PADDING, PADDING + FONT_ASCENT);
let mean_cpu_sample = self.cpu_samples.mean();
self.ui.draw_text(device,
&format!("Objects: {}", mean_cpu_sample.stats.object_count),
origin,
false);
self.ui.draw_text(device,
&format!("Solid Tiles: {}", mean_cpu_sample.stats.solid_tile_count),
origin + Point2DI32::new(0, LINE_HEIGHT * 1),
false);
self.ui.draw_text(device,
&format!("Alpha Tiles: {}", mean_cpu_sample.stats.alpha_tile_count),
origin + Point2DI32::new(0, LINE_HEIGHT * 2),
false);
self.ui.draw_text(device,
&format!("Fills: {}", mean_cpu_sample.stats.fill_count),
origin + Point2DI32::new(0, LINE_HEIGHT * 3),
false);
self.ui.draw_text(device,
&format!("CPU Time: {:.3} ms", duration_to_ms(mean_cpu_sample.elapsed)),
origin + Point2DI32::new(0, LINE_HEIGHT * 4),
false);
let mean_gpu_sample = self.gpu_samples.mean();
self.ui.draw_text(device,
&format!("GPU Time: {:.3} ms", duration_to_ms(mean_gpu_sample.elapsed)),
origin + Point2DI32::new(0, LINE_HEIGHT * 5),
false);
}
}
struct SampleBuffer<S> where S: Add<S, Output=S> + Div<usize, Output=S> + Clone + Default {
samples: VecDeque<S>,
}
impl<S> SampleBuffer<S> where S: Add<S, Output=S> + Div<usize, Output=S> + Clone + Default {
fn new() -> SampleBuffer<S> {
SampleBuffer { samples: VecDeque::with_capacity(SAMPLE_BUFFER_SIZE) }
}
fn push(&mut self, time: S) {
self.samples.push_back(time);
while self.samples.len() > SAMPLE_BUFFER_SIZE {
self.samples.pop_front();
}
}
fn mean(&self) -> S {
let mut mean = Default::default();
if self.samples.is_empty() {
return mean;
}
for time in &self.samples {
mean = mean + (*time).clone();
}
mean / self.samples.len()
}
}
#[derive(Clone, Default)]
struct CPUSample {
elapsed: Duration,
stats: RenderStats,
}
impl Add<CPUSample> for CPUSample {
type Output = CPUSample;
fn add(self, other: CPUSample) -> CPUSample {
CPUSample { elapsed: self.elapsed + other.elapsed, stats: self.stats + other.stats }
}
}
impl Div<usize> for CPUSample {
type Output = CPUSample;
fn div(self, divisor: usize) -> CPUSample {
CPUSample { elapsed: self.elapsed / (divisor as u32), stats: self.stats / divisor }
}
}
#[derive(Clone, Default)]
struct GPUSample {
elapsed: Duration,
}
impl Add<GPUSample> for GPUSample {
type Output = GPUSample;
fn add(self, other: GPUSample) -> GPUSample {
GPUSample { elapsed: self.elapsed + other.elapsed }
}
}
impl Div<usize> for GPUSample {
type Output = GPUSample;
fn div(self, divisor: usize) -> GPUSample {
GPUSample { elapsed: self.elapsed / (divisor as u32) }
}
}
fn duration_to_ms(time: Duration) -> f64 {
time.as_secs() as f64 * 1000.0 + time.subsec_nanos() as f64 / 1000000.0
}

14
renderer/src/gpu/mod.rs Normal file
View File

@ -0,0 +1,14 @@
// pathfinder/renderer/src/gpu/mod.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! The GPU renderer for Pathfinder 3.
pub mod debug;
pub mod renderer;

1260
renderer/src/gpu/renderer.rs Normal file

File diff suppressed because it is too large Load Diff

291
renderer/src/gpu_data.rs Normal file
View File

@ -0,0 +1,291 @@
// pathfinder/renderer/src/gpu_data.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Packed data ready to be sent to the GPU.
use crate::builder::SceneBuilder;
use crate::tile_map::DenseTileMap;
use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH};
use pathfinder_geometry::basic::line_segment::{LineSegmentF32, LineSegmentU4, LineSegmentU8};
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::basic::rect::{RectF32, RectI32};
use pathfinder_geometry::util;
use pathfinder_simd::default::{F32x4, I32x4};
use std::fmt::{Debug, Formatter, Result as DebugResult};
use std::sync::atomic::Ordering;
#[derive(Debug)]
pub(crate) struct BuiltObject {
pub bounds: RectF32,
pub fills: Vec<FillBatchPrimitive>,
pub alpha_tiles: Vec<AlphaTileBatchPrimitive>,
pub tiles: DenseTileMap<TileObjectPrimitive>,
}
pub enum RenderCommand {
AddFills(Vec<FillBatchPrimitive>),
FlushFills,
AlphaTile(Vec<AlphaTileBatchPrimitive>),
SolidTile(Vec<SolidTileBatchPrimitive>),
}
#[derive(Clone, Copy, Debug)]
pub struct FillObjectPrimitive {
pub px: LineSegmentU4,
pub subpx: LineSegmentU8,
pub tile_x: i16,
pub tile_y: i16,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct TileObjectPrimitive {
/// If `u16::MAX`, then this is a solid tile.
pub alpha_tile_index: u16,
pub backdrop: i8,
}
// FIXME(pcwalton): Move `subpx` before `px` and remove `repr(packed)`.
#[derive(Clone, Copy, Debug, Default)]
#[repr(packed)]
pub struct FillBatchPrimitive {
pub px: LineSegmentU4,
pub subpx: LineSegmentU8,
pub alpha_tile_index: u16,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct SolidTileBatchPrimitive {
pub tile_x: i16,
pub tile_y: i16,
pub object_index: u16,
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct AlphaTileBatchPrimitive {
pub tile_x_lo: u8,
pub tile_y_lo: u8,
pub tile_hi: u8,
pub backdrop: i8,
pub object_index: u16,
pub tile_index: u16,
}
// Utilities for built objects
impl BuiltObject {
pub(crate) fn new(bounds: RectF32) -> BuiltObject {
let tile_rect = tiles::round_rect_out_to_tile_bounds(bounds);
let tiles = DenseTileMap::new(tile_rect);
BuiltObject { bounds, fills: vec![], alpha_tiles: vec![], tiles }
}
#[inline]
pub(crate) fn tile_rect(&self) -> RectI32 {
self.tiles.rect
}
fn add_fill(&mut self,
builder: &SceneBuilder,
segment: &LineSegmentF32,
tile_coords: Point2DI32) {
//println!("add_fill({:?} ({}, {}))", segment, tile_x, tile_y);
// Ensure this fill is in bounds. If not, cull it.
if self.tile_coords_to_local_index(tile_coords).is_none() {
return;
};
debug_assert_eq!(TILE_WIDTH, TILE_HEIGHT);
let tile_size = F32x4::splat(TILE_WIDTH as f32);
let (min, max) = (F32x4::default(), F32x4::splat((TILE_WIDTH * 256 - 1) as f32));
let shuffle_mask = I32x4::new(0x0c08_0400, 0x0d05_0901, 0, 0).as_u8x16();
let tile_upper_left = tile_coords.to_f32().0.xyxy() * tile_size;
//F32x4::new(tile_x as f32, tile_y as f32, tile_x as f32, tile_y as f32) * tile_size;
let segment = (segment.0 - tile_upper_left) * F32x4::splat(256.0);
let segment =
segment.clamp(min, max).to_i32x4().as_u8x16().shuffle(shuffle_mask).as_i32x4();
// Unpack whole and fractional pixels.
let px = LineSegmentU4((segment[1] | (segment[1] >> 12)) as u16);
let subpx = LineSegmentU8(segment[0] as u32);
// Cull degenerate fills.
if (px.0 & 0xf) as u8 == ((px.0 >> 8) & 0xf) as u8 &&
(subpx.0 & 0xff) as u8 == ((subpx.0 >> 16) & 0xff) as u8 {
//println!("... ... culling!");
return;
}
// Allocate global tile if necessary.
let alpha_tile_index = self.get_or_allocate_alpha_tile_index(builder, tile_coords);
//println!("... ... OK, pushing");
self.fills.push(FillBatchPrimitive { px, subpx, alpha_tile_index });
}
fn get_or_allocate_alpha_tile_index(&mut self, builder: &SceneBuilder, tile_coords: Point2DI32)
-> u16 {
let local_tile_index = self.tiles.coords_to_index_unchecked(tile_coords);
let alpha_tile_index = self.tiles.data[local_tile_index].alpha_tile_index;
if alpha_tile_index != !0 {
return alpha_tile_index;
}
let alpha_tile_index = builder.next_alpha_tile_index
.fetch_add(1, Ordering::Relaxed) as u16;
self.tiles.data[local_tile_index].alpha_tile_index = alpha_tile_index;
alpha_tile_index
}
pub(crate) fn add_active_fill(&mut self,
builder: &SceneBuilder,
left: f32,
right: f32,
mut winding: i32,
tile_coords: Point2DI32) {
let tile_origin_y = (tile_coords.y() * TILE_HEIGHT as i32) as f32;
let left = Point2DF32::new(left, tile_origin_y);
let right = Point2DF32::new(right, tile_origin_y);
let segment = if winding < 0 {
LineSegmentF32::new(&left, &right)
} else {
LineSegmentF32::new(&right, &left)
};
/*println!("... emitting active fill {} -> {} winding {} @ tile {},{}",
left.x(),
right.x(),
winding,
tile_x,
tile_y);*/
while winding != 0 {
self.add_fill(builder, &segment, tile_coords);
if winding < 0 {
winding += 1
} else {
winding -= 1
}
}
}
pub(crate) fn generate_fill_primitives_for_line(&mut self,
builder: &SceneBuilder,
mut segment: LineSegmentF32,
tile_y: i32) {
/*println!("... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})",
segment,
tile_y,
tile_y as f32 * TILE_HEIGHT as f32,
(tile_y + 1) as f32 * TILE_HEIGHT as f32);*/
let winding = segment.from_x() > segment.to_x();
let (segment_left, segment_right) = if !winding {
(segment.from_x(), segment.to_x())
} else {
(segment.to_x(), segment.from_x())
};
// FIXME(pcwalton): Optimize this.
let segment_tile_left = f32::floor(segment_left) as i32 / TILE_WIDTH as i32;
let segment_tile_right =
util::alignup_i32(f32::ceil(segment_right) as i32, TILE_WIDTH as i32);
/*println!("segment_tile_left={} segment_tile_right={} tile_rect={:?}",
segment_tile_left, segment_tile_right, self.tile_rect);*/
for subsegment_tile_x in segment_tile_left..segment_tile_right {
let (mut fill_from, mut fill_to) = (segment.from(), segment.to());
let subsegment_tile_right =
((i32::from(subsegment_tile_x) + 1) * TILE_HEIGHT as i32) as f32;
if subsegment_tile_right < segment_right {
let x = subsegment_tile_right;
let point = Point2DF32::new(x, segment.solve_y_for_x(x));
if !winding {
fill_to = point;
segment = LineSegmentF32::new(&point, &segment.to());
} else {
fill_from = point;
segment = LineSegmentF32::new(&segment.from(), &point);
}
}
let fill_segment = LineSegmentF32::new(&fill_from, &fill_to);
let fill_tile_coords = Point2DI32::new(subsegment_tile_x, tile_y);
self.add_fill(builder, &fill_segment, fill_tile_coords);
}
}
#[inline]
pub(crate) fn tile_coords_to_local_index(&self, coords: Point2DI32) -> Option<u32> {
self.tiles.coords_to_index(coords).map(|index| index as u32)
}
#[inline]
pub(crate) fn local_tile_index_to_coords(&self, tile_index: u32) -> Point2DI32 {
self.tiles.index_to_coords(tile_index as usize)
}
}
impl Default for TileObjectPrimitive {
#[inline]
fn default() -> TileObjectPrimitive {
TileObjectPrimitive { backdrop: 0, alpha_tile_index: !0 }
}
}
impl TileObjectPrimitive {
#[inline]
pub fn is_solid(&self) -> bool {
self.alpha_tile_index == !0
}
}
impl AlphaTileBatchPrimitive {
#[inline]
pub fn new(tile_coords: Point2DI32, backdrop: i8, object_index: u16, tile_index: u16)
-> AlphaTileBatchPrimitive {
AlphaTileBatchPrimitive {
tile_x_lo: (tile_coords.x() & 0xff) as u8,
tile_y_lo: (tile_coords.y() & 0xff) as u8,
tile_hi: (((tile_coords.x() >> 8) & 0x0f) | ((tile_coords.y() >> 4) & 0xf0)) as u8,
backdrop,
object_index,
tile_index,
}
}
#[inline]
pub fn tile_coords(&self) -> Point2DI32 {
Point2DI32::new((self.tile_x_lo as i32) | (((self.tile_hi & 0xf) as i32) << 8),
(self.tile_y_lo as i32) | (((self.tile_hi & 0xf0) as i32) << 4))
}
}
impl Debug for RenderCommand {
fn fmt(&self, formatter: &mut Formatter) -> DebugResult {
match *self {
RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()),
RenderCommand::FlushFills => write!(formatter, "FlushFills"),
RenderCommand::AlphaTile(ref tiles) => {
write!(formatter, "AlphaTile(x{})", tiles.len())
}
RenderCommand::SolidTile(ref tiles) => {
write!(formatter, "SolidTile(x{})", tiles.len())
}
}
}
}

22
renderer/src/lib.rs Normal file
View File

@ -0,0 +1,22 @@
// pathfinder/renderer/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! The CPU portion of Pathfinder's renderer.
pub mod builder;
pub mod gpu;
pub mod gpu_data;
pub mod post;
pub mod scene;
pub mod tiles;
mod sorted_vector;
mod tile_map;
mod z_buffer;

35
renderer/src/post.rs Normal file
View File

@ -0,0 +1,35 @@
// pathfinder/renderer/src/post.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Functionality related to postprocessing effects.
//!
//! Since these effects run on GPU as fragment shaders, this contains no
//! implementations, just shared declarations.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct DefringingKernel(pub [f32; 4]);
/// This intentionally does not precisely match what Core Graphics does (a
/// Lanczos function), because we don't want any ringing artefacts.
pub static DEFRINGING_KERNEL_CORE_GRAPHICS: DefringingKernel = DefringingKernel([
0.033165660, 0.102074051, 0.221434336, 0.286651906
]);
pub static DEFRINGING_KERNEL_FREETYPE: DefringingKernel = DefringingKernel([
0.0, 0.031372549, 0.301960784, 0.337254902
]);
/// Should match macOS 10.13 High Sierra.
pub static STEM_DARKENING_FACTORS: [f32; 2] = [0.0121, 0.0121 * 1.25];
/// Should match macOS 10.13 High Sierra.
pub const MAX_STEM_DARKENING_AMOUNT: [f32; 2] = [0.3, 0.3];
/// This value is a subjective cutoff. Above this ppem value, no stem darkening is performed.
pub const MAX_STEM_DARKENING_PIXELS_PER_EM: f32 = 72.0;

205
renderer/src/scene.rs Normal file
View File

@ -0,0 +1,205 @@
// pathfinder/renderer/src/scene.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 set of paths to be rendered.
use crate::builder::{PreparedRenderOptions, PreparedRenderTransform};
use hashbrown::HashMap;
use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::Outline;
use std::fmt::{self, Debug, Formatter};
#[derive(Clone)]
pub struct Scene {
pub objects: Vec<PathObject>,
pub paints: Vec<Paint>,
pub paint_cache: HashMap<Paint, PaintId>,
pub bounds: RectF32,
pub view_box: RectF32,
}
impl Scene {
#[inline]
pub fn new() -> Scene {
Scene {
objects: vec![],
paints: vec![],
paint_cache: HashMap::new(),
bounds: RectF32::default(),
view_box: RectF32::default(),
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn push_paint(&mut self, paint: &Paint) -> PaintId {
if let Some(paint_id) = self.paint_cache.get(paint) {
return *paint_id;
}
let paint_id = PaintId(self.paints.len() as u16);
self.paint_cache.insert(*paint, paint_id);
self.paints.push(*paint);
paint_id
}
pub fn build_descriptor(&self, built_options: &PreparedRenderOptions) -> SceneDescriptor {
SceneDescriptor {
shaders: self.build_shaders(),
bounding_quad: built_options.bounding_quad(),
object_count: self.objects.len(),
}
}
fn build_shaders(&self) -> Vec<ObjectShader> {
self.objects.iter().map(|object| {
let paint = &self.paints[object.paint.0 as usize];
ObjectShader { fill_color: paint.color }
}).collect()
}
pub(crate) fn apply_render_options(&self,
original_outline: &Outline,
options: &PreparedRenderOptions)
-> Outline {
let effective_view_box = self.effective_view_box(options);
let mut outline;
match options.transform {
PreparedRenderTransform::Perspective { ref perspective, ref clip_polygon, .. } => {
if original_outline.is_outside_polygon(clip_polygon) {
outline = Outline::new();
} else {
outline = (*original_outline).clone();
outline.clip_against_polygon(clip_polygon);
outline.apply_perspective(perspective);
// TODO(pcwalton): Support subpixel AA in 3D.
}
}
_ => {
// TODO(pcwalton): Short circuit.
outline = (*original_outline).clone();
if options.transform.is_2d() || options.subpixel_aa_enabled {
let mut transform = match options.transform {
PreparedRenderTransform::Transform2D(transform) => transform,
PreparedRenderTransform::None => Transform2DF32::default(),
PreparedRenderTransform::Perspective { .. } => unreachable!(),
};
if options.subpixel_aa_enabled {
transform = transform.post_mul(&Transform2DF32::from_scale(
&Point2DF32::new(3.0, 1.0)))
}
outline.transform(&transform);
}
outline.clip_against_rect(effective_view_box);
}
}
if !options.dilation.is_zero() {
outline.dilate(options.dilation);
}
// TODO(pcwalton): Fold this into previous passes to avoid unnecessary clones during
// monotonic conversion.
outline.prepare_for_tiling(self.effective_view_box(options));
outline
}
pub fn monochrome_color(&self) -> Option<ColorU> {
if self.objects.is_empty() {
return None;
}
let first_paint_id = self.objects[0].paint;
if self.objects.iter().skip(1).any(|object| object.paint != first_paint_id) {
return None;
}
Some(self.paints[first_paint_id.0 as usize].color)
}
#[inline]
pub fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
if render_options.subpixel_aa_enabled {
self.view_box.scale_xy(Point2DF32::new(3.0, 1.0))
} else {
self.view_box
}
}
}
impl Debug for Scene {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
writeln!(formatter,
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{} {} {} {}\">",
self.view_box.origin().x(),
self.view_box.origin().y(),
self.view_box.size().x(),
self.view_box.size().y())?;
for object in &self.objects {
let paint = &self.paints[object.paint.0 as usize];
write!(formatter, " <path")?;
if !object.name.is_empty() {
write!(formatter, " id=\"{}\"", object.name)?;
}
writeln!(formatter, " fill=\"{:?}\" d=\"{:?}\" />", paint.color, object.outline)?;
}
writeln!(formatter, "</svg>")?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct SceneDescriptor {
pub shaders: Vec<ObjectShader>,
pub bounding_quad: [Point3DF32; 4],
pub object_count: usize,
}
#[derive(Clone, Debug)]
pub struct PathObject {
outline: Outline,
paint: PaintId,
name: String,
kind: PathObjectKind,
}
#[derive(Clone, Copy, Debug)]
pub enum PathObjectKind {
Fill,
Stroke,
}
impl PathObject {
#[inline]
pub fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind)
-> PathObject {
PathObject { outline, paint, name, kind }
}
#[inline]
pub fn outline(&self) -> &Outline {
&self.outline
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Paint {
pub color: ColorU,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct PaintId(pub u16);
#[derive(Clone, Copy, Debug, Default)]
pub struct ObjectShader {
pub fill_color: ColorU,
}

View File

@ -0,0 +1,91 @@
// pathfinder/renderer/src/sorted_vector.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 vector that maintains sorted order with insertion sort.
#[derive(Clone, Debug)]
pub struct SortedVector<T>
where
T: PartialOrd,
{
pub array: Vec<T>,
}
impl<T> SortedVector<T>
where
T: PartialOrd,
{
#[inline]
pub fn new() -> SortedVector<T> {
SortedVector { array: vec![] }
}
#[inline]
pub fn push(&mut self, value: T) {
self.array.push(value);
let mut index = self.array.len() - 1;
while index > 0 {
index -= 1;
if self.array[index] <= self.array[index + 1] {
break;
}
self.array.swap(index, index + 1);
}
}
#[inline]
pub fn peek(&self) -> Option<&T> {
self.array.last()
}
#[inline]
pub fn pop(&mut self) -> Option<T> {
self.array.pop()
}
#[inline]
pub fn clear(&mut self) {
self.array.clear()
}
#[allow(dead_code)]
#[inline]
pub fn is_empty(&self) -> bool {
self.array.is_empty()
}
}
#[cfg(test)]
mod test {
use crate::sorted_vector::SortedVector;
use quickcheck;
#[test]
fn test_sorted_vec() {
quickcheck::quickcheck(prop_sorted_vec as fn(Vec<i32>) -> bool);
fn prop_sorted_vec(mut values: Vec<i32>) -> bool {
let mut sorted_vec = SortedVector::new();
for &value in &values {
sorted_vec.push(value)
}
values.sort();
let mut results = Vec::with_capacity(values.len());
while !sorted_vec.is_empty() {
results.push(sorted_vec.pop().unwrap());
}
results.reverse();
assert_eq!(&values, &results);
true
}
}
}

54
renderer/src/tile_map.rs Normal file
View File

@ -0,0 +1,54 @@
// pathfinder/renderer/src/tile_map.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
#[derive(Debug)]
pub struct DenseTileMap<T> {
pub data: Vec<T>,
pub rect: RectI32,
}
impl<T> DenseTileMap<T> {
#[inline]
pub fn new(rect: RectI32) -> DenseTileMap<T> where T: Copy + Clone + Default {
let length = rect.size().x() as usize * rect.size().y() as usize;
DenseTileMap { data: vec![T::default(); length], rect }
}
#[inline]
pub fn from_builder<F>(build: F, rect: RectI32) -> DenseTileMap<T> where F: FnMut(usize) -> T {
let length = rect.size().x() as usize * rect.size().y() as usize;
DenseTileMap { data: (0..length).map(build).collect(), rect }
}
#[inline]
pub fn coords_to_index(&self, coords: Point2DI32) -> Option<usize> {
// TODO(pcwalton): SIMD?
if coords.x() < self.rect.min_x() || coords.x() >= self.rect.max_x() ||
coords.y() < self.rect.min_y() || coords.y() >= self.rect.max_y() {
return None
}
Some(self.coords_to_index_unchecked(coords))
}
#[inline]
pub fn coords_to_index_unchecked(&self, coords: Point2DI32) -> usize {
(coords.y() - self.rect.min_y()) as usize * self.rect.size().x() as usize +
(coords.x() - self.rect.min_x()) as usize
}
#[inline]
pub fn index_to_coords(&self, index: usize) -> Point2DI32 {
let (width, index) = (self.rect.size().x(), index as i32);
self.rect.origin() + Point2DI32::new(index % width, index / width)
}
}

484
renderer/src/tiles.rs Normal file
View File

@ -0,0 +1,484 @@
// pathfinder/renderer/src/tiles.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::builder::SceneBuilder;
use crate::gpu_data::{AlphaTileBatchPrimitive, BuiltObject};
use crate::sorted_vector::SortedVector;
use pathfinder_geometry::basic::line_segment::LineSegmentF32;
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::basic::rect::{RectF32, RectI32};
use pathfinder_geometry::outline::{Contour, Outline, PointIndex};
use pathfinder_geometry::segment::Segment;
use std::cmp::Ordering;
use std::mem;
// TODO(pcwalton): Make this configurable.
const FLATTENING_TOLERANCE: f32 = 0.1;
pub const TILE_WIDTH: u32 = 16;
pub const TILE_HEIGHT: u32 = 16;
pub(crate) struct Tiler<'a> {
builder: &'a SceneBuilder<'a>,
outline: &'a Outline,
pub built_object: BuiltObject,
object_index: u16,
point_queue: SortedVector<QueuedEndpoint>,
active_edges: SortedVector<ActiveEdge>,
old_active_edges: Vec<ActiveEdge>,
}
impl<'a> Tiler<'a> {
#[allow(clippy::or_fun_call)]
pub(crate) fn new(builder: &'a SceneBuilder<'a>,
outline: &'a Outline,
view_box: RectF32,
object_index: u16)
-> Tiler<'a> {
let bounds = outline.bounds().intersection(view_box).unwrap_or(RectF32::default());
let built_object = BuiltObject::new(bounds);
Tiler {
builder,
outline,
built_object,
object_index,
point_queue: SortedVector::new(),
active_edges: SortedVector::new(),
old_active_edges: vec![],
}
}
pub(crate) fn generate_tiles(&mut self) {
// Initialize the point queue.
self.init_point_queue();
// Reset active edges.
self.active_edges.clear();
self.old_active_edges.clear();
// Generate strips.
let tile_rect = self.built_object.tile_rect();
for strip_origin_y in tile_rect.min_y()..tile_rect.max_y() {
self.generate_strip(strip_origin_y);
}
// Pack and cull.
self.pack_and_cull();
//println!("{:#?}", self.built_object);
}
fn generate_strip(&mut self, strip_origin_y: i32) {
// Process old active edges.
self.process_old_active_edges(strip_origin_y);
// Add new active edges.
let strip_max_y = ((i32::from(strip_origin_y) + 1) * TILE_HEIGHT as i32) as f32;
while let Some(queued_endpoint) = self.point_queue.peek() {
// We're done when we see an endpoint that belongs to the next tile strip.
//
// Note that this test must be `>`, not `>=`, in order to make sure we don't miss
// active edges that lie precisely on the tile strip boundary.
if queued_endpoint.y > strip_max_y {
break;
}
self.add_new_active_edge(strip_origin_y);
}
}
fn pack_and_cull(&mut self) {
for (tile_index, tile) in self.built_object.tiles.data.iter().enumerate() {
let tile_coords = self.built_object.local_tile_index_to_coords(tile_index as u32);
if tile.is_solid() {
if tile.backdrop != 0 {
self.builder.z_buffer.update(tile_coords, self.object_index);
}
continue;
}
let alpha_tile = AlphaTileBatchPrimitive::new(tile_coords,
tile.backdrop,
self.object_index,
tile.alpha_tile_index as u16);
self.built_object.alpha_tiles.push(alpha_tile);
}
}
fn process_old_active_edges(&mut self, tile_y: i32) {
let mut current_tile_x = self.built_object.tile_rect().min_x();
let mut current_subtile_x = 0.0;
let mut current_winding = 0;
debug_assert!(self.old_active_edges.is_empty());
mem::swap(&mut self.old_active_edges, &mut self.active_edges.array);
// FIXME(pcwalton): Yuck.
let mut last_segment_x = -9999.0;
let tile_top = (i32::from(tile_y) * TILE_HEIGHT as i32) as f32;
//println!("---------- tile y {}({}) ----------", tile_y, tile_top);
//println!("old active edges: {:#?}", self.old_active_edges);
for mut active_edge in self.old_active_edges.drain(..) {
// Determine x-intercept and winding.
let segment_x = active_edge.crossing.x();
let edge_winding =
if active_edge.segment.baseline.from_y() < active_edge.segment.baseline.to_y() {
1
} else {
-1
};
/*println!("tile Y {}({}): segment_x={} edge_winding={} current_tile_x={} \
current_subtile_x={} current_winding={}",
tile_y,
tile_top,
segment_x,
edge_winding,
current_tile_x,
current_subtile_x,
current_winding);
println!("... segment={:#?} crossing={:?}", active_edge.segment, active_edge.crossing);
*/
// FIXME(pcwalton): Remove this debug code!
debug_assert!(segment_x >= last_segment_x);
last_segment_x = segment_x;
// Do initial subtile fill, if necessary.
let segment_tile_x = f32::floor(segment_x) as i32 / TILE_WIDTH as i32;
if current_tile_x < segment_tile_x && current_subtile_x > 0.0 {
let current_x =
(i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x;
let tile_right_x = ((i32::from(current_tile_x) + 1) * TILE_WIDTH as i32) as f32;
let current_tile_coords = Point2DI32::new(current_tile_x, tile_y);
self.built_object.add_active_fill(self.builder,
current_x,
tile_right_x,
current_winding,
current_tile_coords);
current_tile_x += 1;
current_subtile_x = 0.0;
}
// Move over to the correct tile, filling in as we go.
while current_tile_x < segment_tile_x {
//println!("... emitting backdrop {} @ tile {}", current_winding, current_tile_x);
let current_tile_coords = Point2DI32::new(current_tile_x, tile_y);
if let Some(tile_index) = self.built_object
.tile_coords_to_local_index(current_tile_coords) {
// FIXME(pcwalton): Handle winding overflow.
self.built_object.tiles.data[tile_index as usize].backdrop =
current_winding as i8;
}
current_tile_x += 1;
current_subtile_x = 0.0;
}
// Do final subtile fill, if necessary.
debug_assert_eq!(current_tile_x, segment_tile_x);
//debug_assert!(current_tile_x <= self.built_object.tile_rect.max_x());
let segment_subtile_x =
segment_x - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32;
if segment_subtile_x > current_subtile_x {
let current_x =
(i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x;
let current_tile_coords = Point2DI32::new(current_tile_x, tile_y);
self.built_object.add_active_fill(self.builder,
current_x,
segment_x,
current_winding,
current_tile_coords);
current_subtile_x = segment_subtile_x;
}
// Update winding.
current_winding += edge_winding;
// Process the edge.
//println!("about to process existing active edge {:#?}", active_edge);
debug_assert!(f32::abs(active_edge.crossing.y() - tile_top) < 0.1);
active_edge.process(self.builder, &mut self.built_object, tile_y);
if !active_edge.segment.is_none() {
self.active_edges.push(active_edge);
}
}
//debug_assert_eq!(current_winding, 0);
}
fn add_new_active_edge(&mut self, tile_y: i32) {
let outline = &self.outline;
let point_index = self.point_queue.pop().unwrap().point_index;
let contour = &outline.contours[point_index.contour() as usize];
// TODO(pcwalton): Could use a bitset of processed edges…
let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point());
let next_endpoint_index = contour.next_endpoint_index_of(point_index.point());
/*
println!("adding new active edge, tile_y={} point_index={} prev={} next={} pos={:?} \
prevpos={:?} nextpos={:?}",
tile_y,
point_index.point(),
prev_endpoint_index,
next_endpoint_index,
contour.position_of(point_index.point()),
contour.position_of(prev_endpoint_index),
contour.position_of(next_endpoint_index));
*/
if contour.point_is_logically_above(point_index.point(), prev_endpoint_index) {
//println!("... adding prev endpoint");
process_active_segment(
contour,
prev_endpoint_index,
&mut self.active_edges,
self.builder,
&mut self.built_object,
tile_y,
);
self.point_queue.push(QueuedEndpoint {
point_index: PointIndex::new(point_index.contour(), prev_endpoint_index),
y: contour.position_of(prev_endpoint_index).y(),
});
//println!("... done adding prev endpoint");
}
if contour.point_is_logically_above(point_index.point(), next_endpoint_index) {
/*
println!("... adding next endpoint {} -> {}",
point_index.point(),
next_endpoint_index);
*/
process_active_segment(
contour,
point_index.point(),
&mut self.active_edges,
self.builder,
&mut self.built_object,
tile_y,
);
self.point_queue.push(QueuedEndpoint {
point_index: PointIndex::new(point_index.contour(), next_endpoint_index),
y: contour.position_of(next_endpoint_index).y(),
});
//println!("... done adding next endpoint");
}
}
fn init_point_queue(&mut self) {
// Find MIN points.
self.point_queue.clear();
for (contour_index, contour) in self.outline.contours.iter().enumerate() {
let contour_index = contour_index as u32;
let mut cur_endpoint_index = 0;
let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index);
let mut next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index);
loop {
if contour.point_is_logically_above(cur_endpoint_index, prev_endpoint_index)
&& contour.point_is_logically_above(cur_endpoint_index, next_endpoint_index)
{
self.point_queue.push(QueuedEndpoint {
point_index: PointIndex::new(contour_index, cur_endpoint_index),
y: contour.position_of(cur_endpoint_index).y(),
});
}
if cur_endpoint_index >= next_endpoint_index {
break;
}
prev_endpoint_index = cur_endpoint_index;
cur_endpoint_index = next_endpoint_index;
next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index);
}
}
}
}
pub fn round_rect_out_to_tile_bounds(rect: RectF32) -> RectI32 {
rect.scale_xy(Point2DF32::new(1.0 / TILE_WIDTH as f32, 1.0 / TILE_HEIGHT as f32))
.round_out()
.to_i32()
}
fn process_active_segment(
contour: &Contour,
from_endpoint_index: u32,
active_edges: &mut SortedVector<ActiveEdge>,
builder: &SceneBuilder,
built_object: &mut BuiltObject,
tile_y: i32,
) {
let mut active_edge = ActiveEdge::from_segment(&contour.segment_after(from_endpoint_index));
//println!("... process_active_segment({:#?})", active_edge);
active_edge.process(builder, built_object, tile_y);
if !active_edge.segment.is_none() {
//println!("... ... pushing resulting active edge: {:#?}", active_edge);
active_edges.push(active_edge);
}
}
// Queued endpoints
#[derive(PartialEq)]
struct QueuedEndpoint {
point_index: PointIndex,
y: f32,
}
impl Eq for QueuedEndpoint {}
impl PartialOrd<QueuedEndpoint> for QueuedEndpoint {
fn partial_cmp(&self, other: &QueuedEndpoint) -> Option<Ordering> {
// NB: Reversed!
(other.y, other.point_index).partial_cmp(&(self.y, self.point_index))
}
}
// Active edges
#[derive(Clone, PartialEq, Debug)]
struct ActiveEdge {
segment: Segment,
// TODO(pcwalton): Shrink `crossing` down to just one f32?
crossing: Point2DF32,
}
impl ActiveEdge {
fn from_segment(segment: &Segment) -> ActiveEdge {
let crossing = if segment.baseline.from_y() < segment.baseline.to_y() {
segment.baseline.from()
} else {
segment.baseline.to()
};
ActiveEdge::from_segment_and_crossing(segment, &crossing)
}
fn from_segment_and_crossing(segment: &Segment, crossing: &Point2DF32) -> ActiveEdge {
ActiveEdge {
segment: *segment,
crossing: *crossing,
}
}
fn process(&mut self, builder: &SceneBuilder, built_object: &mut BuiltObject, tile_y: i32) {
//let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
//println!("process_active_edge({:#?}, tile_y={}({}))", self, tile_y, tile_bottom);
let mut segment = self.segment;
let winding = segment.baseline.y_winding();
if segment.is_line() {
let line_segment = segment.as_line_segment();
self.segment = match self.process_line_segment(&line_segment,
builder,
built_object,
tile_y) {
Some(lower_part) => Segment::line(&lower_part),
None => Segment::none(),
};
return;
}
// TODO(pcwalton): Don't degree elevate!
if !segment.is_cubic() {
segment = segment.to_cubic();
}
// If necessary, draw initial line.
if self.crossing.y() < segment.baseline.min_y() {
let first_line_segment =
LineSegmentF32::new(&self.crossing, &segment.baseline.upper_point())
.orient(winding);
if self.process_line_segment(&first_line_segment, builder, built_object, tile_y)
.is_some() {
return;
}
}
let mut oriented_segment = segment.orient(winding);
loop {
let mut split_t = 1.0;
let mut before_segment = oriented_segment;
let mut after_segment = None;
while !before_segment.as_cubic_segment().is_flat(FLATTENING_TOLERANCE) {
let next_t = 0.5 * split_t;
let (before, after) = oriented_segment.as_cubic_segment().split(next_t);
before_segment = before;
after_segment = Some(after);
split_t = next_t;
}
/*
println!("... tile_y={} winding={} segment={:?} t={} before_segment={:?}
after_segment={:?}",
tile_y,
winding,
segment,
split_t,
before_segment,
after_segment);
*/
let line = before_segment.baseline.orient(winding);
match self.process_line_segment(&line, builder, built_object, tile_y) {
Some(ref lower_part) if split_t == 1.0 => {
self.segment = Segment::line(&lower_part);
return;
}
None if split_t == 1.0 => {
self.segment = Segment::none();
return;
}
Some(_) => {
self.segment = after_segment.unwrap().orient(winding);
return;
}
None => oriented_segment = after_segment.unwrap(),
}
}
}
fn process_line_segment(
&mut self,
line_segment: &LineSegmentF32,
builder: &SceneBuilder,
built_object: &mut BuiltObject,
tile_y: i32,
) -> Option<LineSegmentF32> {
let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
/*println!("process_line_segment({:?}, tile_y={}) tile_bottom={}",
line_segment, tile_y, tile_bottom);*/
if line_segment.max_y() <= tile_bottom {
built_object.generate_fill_primitives_for_line(builder, *line_segment, tile_y);
return None;
}
let (upper_part, lower_part) = line_segment.split_at_y(tile_bottom);
built_object.generate_fill_primitives_for_line(builder, upper_part, tile_y);
self.crossing = lower_part.upper_point();
Some(lower_part)
}
}
impl PartialOrd<ActiveEdge> for ActiveEdge {
fn partial_cmp(&self, other: &ActiveEdge) -> Option<Ordering> {
self.crossing.x().partial_cmp(&other.crossing.x())
}
}

77
renderer/src/z_buffer.rs Normal file
View File

@ -0,0 +1,77 @@
// pathfinder/renderer/src/z_buffer.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Software occlusion culling.
use crate::gpu_data::SolidTileBatchPrimitive;
use crate::tile_map::DenseTileMap;
use crate::tiles;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectF32;
use std::ops::Range;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
pub struct ZBuffer {
buffer: DenseTileMap<AtomicUsize>,
}
impl ZBuffer {
pub fn new(view_box: RectF32) -> ZBuffer {
let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box);
ZBuffer { buffer: DenseTileMap::from_builder(|_| AtomicUsize::new(0), tile_rect) }
}
pub fn test(&self, coords: Point2DI32, object_index: u32) -> bool {
let tile_index = self.buffer.coords_to_index_unchecked(coords);
let existing_depth = self.buffer.data[tile_index as usize].load(AtomicOrdering::SeqCst);
existing_depth < object_index as usize + 1
}
pub fn update(&self, coords: Point2DI32, object_index: u16) {
let tile_index = self.buffer.coords_to_index_unchecked(coords);
let mut old_depth = self.buffer.data[tile_index].load(AtomicOrdering::SeqCst);
let new_depth = (object_index + 1) as usize;
while old_depth < new_depth {
let prev_depth = self.buffer.data[tile_index].compare_and_swap(
old_depth,
new_depth,
AtomicOrdering::SeqCst,
);
if prev_depth == old_depth {
// Successfully written.
return;
}
old_depth = prev_depth;
}
}
pub fn build_solid_tiles(&self, object_range: Range<u32>) -> Vec<SolidTileBatchPrimitive> {
let mut solid_tiles = vec![];
for tile_index in 0..self.buffer.data.len() {
let depth = self.buffer.data[tile_index].load(AtomicOrdering::Relaxed);
if depth == 0 {
continue;
}
let tile_coords = self.buffer.index_to_coords(tile_index);
let object_index = (depth - 1) as u32;
if object_index < object_range.start || object_index >= object_range.end {
continue;
}
solid_tiles.push(SolidTileBatchPrimitive {
tile_x: (tile_coords.x() + self.buffer.rect.min_x()) as i16,
tile_y: (tile_coords.y() + self.buffer.rect.min_y()) as i16,
object_index: object_index as u16,
});
}
solid_tiles
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
{
"name": "D-DIN",
"size": 28,
"bold": false,
"italic": false,
"width": 512,
"height": 128,
"characters": {
"0":{"x":338,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
"1":{"x":30,"y":64,"width":11,"height":29,"originX":0,"originY":28,"advance":12},
"2":{"x":490,"y":35,"width":17,"height":29,"originX":0,"originY":28,"advance":17},
"3":{"x":356,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
"4":{"x":398,"y":35,"width":19,"height":29,"originX":0,"originY":28,"advance":19},
"5":{"x":374,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
"6":{"x":392,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
"7":{"x":436,"y":35,"width":18,"height":29,"originX":1,"originY":28,"advance":16},
"8":{"x":319,"y":0,"width":19,"height":30,"originX":0,"originY":28,"advance":19},
"9":{"x":454,"y":35,"width":18,"height":29,"originX":0,"originY":28,"advance":18},
" ":{"x":147,"y":93,"width":3,"height":3,"originX":1,"originY":1,"advance":8},
"!":{"x":62,"y":64,"width":7,"height":29,"originX":-1,"originY":28,"advance":9},
"\"":{"x":52,"y":93,"width":12,"height":12,"originX":0,"originY":28,"advance":11},
"#":{"x":233,"y":35,"width":21,"height":29,"originX":1,"originY":28,"advance":19},
"$":{"x":138,"y":0,"width":18,"height":34,"originX":1,"originY":30,"advance":17},
"%":{"x":156,"y":0,"width":31,"height":30,"originX":0,"originY":28,"advance":31},
"&":{"x":187,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":22},
"'":{"x":64,"y":93,"width":6,"height":12,"originX":0,"originY":28,"advance":6},
"(":{"x":59,"y":0,"width":10,"height":35,"originX":0,"originY":30,"advance":11},
")":{"x":48,"y":0,"width":11,"height":35,"originX":0,"originY":30,"advance":11},
"*":{"x":19,"y":93,"width":14,"height":14,"originX":0,"originY":28,"advance":14},
"+":{"x":432,"y":64,"width":19,"height":19,"originX":0,"originY":20,"advance":19},
",":{"x":70,"y":93,"width":6,"height":11,"originX":0,"originY":5,"advance":6},
"-":{"x":105,"y":93,"width":14,"height":6,"originX":0,"originY":16,"advance":15},
".":{"x":119,"y":93,"width":7,"height":6,"originX":0,"originY":5,"advance":7},
"/":{"x":446,"y":0,"width":14,"height":30,"originX":0,"originY":28,"advance":15},
":":{"x":470,"y":64,"width":7,"height":19,"originX":0,"originY":18,"advance":7},
";":{"x":171,"y":64,"width":7,"height":24,"originX":0,"originY":18,"advance":7},
"<":{"x":477,"y":64,"width":19,"height":17,"originX":0,"originY":19,"advance":19},
"=":{"x":33,"y":93,"width":19,"height":13,"originX":0,"originY":17,"advance":19},
">":{"x":0,"y":93,"width":19,"height":17,"originX":0,"originY":19,"advance":19},
"?":{"x":0,"y":64,"width":17,"height":29,"originX":1,"originY":28,"advance":16},
"@":{"x":106,"y":0,"width":32,"height":34,"originX":0,"originY":28,"advance":32},
"A":{"x":26,"y":35,"width":25,"height":29,"originX":2,"originY":28,"advance":22},
"B":{"x":254,"y":35,"width":21,"height":29,"originX":-1,"originY":28,"advance":23},
"C":{"x":209,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":23},
"D":{"x":145,"y":35,"width":22,"height":29,"originX":-1,"originY":28,"advance":23},
"E":{"x":338,"y":35,"width":20,"height":29,"originX":-1,"originY":28,"advance":20},
"F":{"x":358,"y":35,"width":20,"height":29,"originX":-1,"originY":28,"advance":20},
"G":{"x":231,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":23},
"H":{"x":275,"y":35,"width":21,"height":29,"originX":-1,"originY":28,"advance":23},
"I":{"x":69,"y":64,"width":7,"height":29,"originX":-1,"originY":28,"advance":9},
"J":{"x":41,"y":64,"width":11,"height":29,"originX":1,"originY":28,"advance":12},
"K":{"x":76,"y":35,"width":23,"height":29,"originX":-1,"originY":28,"advance":22},
"L":{"x":378,"y":35,"width":20,"height":29,"originX":-1,"originY":28,"advance":20},
"M":{"x":0,"y":35,"width":26,"height":29,"originX":-1,"originY":28,"advance":28},
"N":{"x":99,"y":35,"width":23,"height":29,"originX":-1,"originY":28,"advance":25},
"O":{"x":253,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":23},
"P":{"x":296,"y":35,"width":21,"height":29,"originX":-1,"originY":28,"advance":22},
"Q":{"x":0,"y":0,"width":22,"height":35,"originX":0,"originY":28,"advance":23},
"R":{"x":167,"y":35,"width":22,"height":29,"originX":-1,"originY":28,"advance":22},
"S":{"x":275,"y":0,"width":22,"height":30,"originX":1,"originY":28,"advance":20},
"T":{"x":189,"y":35,"width":22,"height":29,"originX":2,"originY":28,"advance":18},
"U":{"x":297,"y":0,"width":22,"height":30,"originX":-1,"originY":28,"advance":24},
"V":{"x":122,"y":35,"width":23,"height":29,"originX":2,"originY":28,"advance":19},
"W":{"x":474,"y":0,"width":33,"height":29,"originX":2,"originY":28,"advance":30},
"X":{"x":51,"y":35,"width":25,"height":29,"originX":2,"originY":28,"advance":21},
"Y":{"x":211,"y":35,"width":22,"height":29,"originX":2,"originY":28,"advance":19},
"Z":{"x":317,"y":35,"width":21,"height":29,"originX":1,"originY":28,"advance":19},
"[":{"x":69,"y":0,"width":10,"height":35,"originX":-1,"originY":30,"advance":11},
"\\":{"x":460,"y":0,"width":14,"height":30,"originX":0,"originY":28,"advance":15},
"]":{"x":79,"y":0,"width":10,"height":35,"originX":0,"originY":30,"advance":11},
"^":{"x":451,"y":64,"width":19,"height":19,"originX":0,"originY":28,"advance":19},
"_":{"x":126,"y":93,"width":21,"height":5,"originX":2,"originY":0,"advance":17},
"`":{"x":95,"y":93,"width":10,"height":8,"originX":-4,"originY":28,"advance":19},
"a":{"x":235,"y":64,"width":18,"height":23,"originX":0,"originY":21,"advance":19},
"b":{"x":410,"y":0,"width":18,"height":30,"originX":-1,"originY":28,"advance":19},
"c":{"x":178,"y":64,"width":19,"height":23,"originX":0,"originY":21,"advance":19},
"d":{"x":428,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":19},
"e":{"x":197,"y":64,"width":19,"height":23,"originX":0,"originY":21,"advance":19},
"f":{"x":17,"y":64,"width":13,"height":29,"originX":0,"originY":28,"advance":12},
"g":{"x":96,"y":64,"width":18,"height":28,"originX":0,"originY":21,"advance":19},
"h":{"x":472,"y":35,"width":18,"height":29,"originX":-1,"originY":28,"advance":19},
"i":{"x":150,"y":64,"width":7,"height":28,"originX":-1,"originY":27,"advance":9},
"j":{"x":89,"y":0,"width":10,"height":35,"originX":3,"originY":28,"advance":8},
"k":{"x":417,"y":35,"width":19,"height":29,"originX":-1,"originY":28,"advance":18},
"l":{"x":52,"y":64,"width":10,"height":29,"originX":-1,"originY":28,"advance":10},
"m":{"x":289,"y":64,"width":29,"height":22,"originX":-1,"originY":21,"advance":31},
"n":{"x":366,"y":64,"width":18,"height":22,"originX":-1,"originY":21,"advance":19},
"o":{"x":216,"y":64,"width":19,"height":23,"originX":0,"originY":21,"advance":19},
"p":{"x":114,"y":64,"width":18,"height":28,"originX":-1,"originY":21,"advance":19},
"q":{"x":132,"y":64,"width":18,"height":28,"originX":0,"originY":21,"advance":19},
"r":{"x":419,"y":64,"width":13,"height":22,"originX":-1,"originY":21,"advance":13},
"s":{"x":253,"y":64,"width":18,"height":23,"originX":0,"originY":21,"advance":17},
"t":{"x":157,"y":64,"width":14,"height":27,"originX":1,"originY":26,"advance":13},
"u":{"x":271,"y":64,"width":18,"height":23,"originX":-1,"originY":21,"advance":19},
"v":{"x":347,"y":64,"width":19,"height":22,"originX":1,"originY":21,"advance":16},
"w":{"x":318,"y":64,"width":29,"height":22,"originX":1,"originY":21,"advance":27},
"x":{"x":384,"y":64,"width":18,"height":22,"originX":1,"originY":21,"advance":16},
"y":{"x":76,"y":64,"width":20,"height":28,"originX":2,"originY":21,"advance":16},
"z":{"x":402,"y":64,"width":17,"height":22,"originX":0,"originY":21,"advance":17},
"{":{"x":22,"y":0,"width":13,"height":35,"originX":0,"originY":30,"advance":13},
"|":{"x":99,"y":0,"width":7,"height":35,"originX":-2,"originY":30,"advance":11},
"}":{"x":35,"y":0,"width":13,"height":35,"originX":0,"originY":30,"advance":13},
"~":{"x":76,"y":93,"width":19,"height":9,"originX":0,"originY":15,"advance":19}
}
}

View File

@ -1,93 +0,0 @@
Copyright (c) 2010-2013 Georg Duffner (http://www.georgduffner.at)
All "EB Garamond" Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -1,92 +0,0 @@
Copyright (c) 2017 The Inter UI Project Authors (me@rsms.me)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -1,202 +0,0 @@
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.

View File

@ -1,340 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@ -1,51 +0,0 @@
Cyrillized free URW fonts.
These fonts were made from the free URW fonts distributed with ghostcript.
There are NO changes in the latin part of them (I hope).
Cyrillic glyphs were added by copying suitable latin ones
and painting oulines of unique cyrillic glyphs in same style as the others.
For all modification pfaedit was used.
The license for result is (of course) same as for original fonts,
i.e. GPL with an exception that you can put these fonts in your own non-GPLed
documents. (Looks like LGPL from my point of view =).
The "sources" of these fonts in the native pfaedit format are available
at ftp://ftp.gnome.ru/fonts/sources
The great font editor pfaedit is available at http://pfaedit.sf.net.
That page also includes some links to fonts created by
George Williams -- the author of pfaedit.
Acknowledgements:
I would like to thank George Williams, the pfaedit's author and developer.
He is the most bug-reporter/feature-requester friendly developer
I ever saw in my not so short life. At some moment in the future
I must write a book about him: "George Williams and my best experience
in bug-reporting." George also greatly helped me bug-hunting these fonts,
explained to me some very important things about fonts and font design,
quickly adopted pfaedit to my needs (or pointed me to The Right Place in
documentation where I found better way of doing things).
I would like to thank Alexey Novodvorsky (aka AEN), who
pointed me to pfaedit and George Williams' fonts, explained
The Task to me. He is also one of the main participators in the
development of Sysiphus -- free repository of free software.
I didn't loose my time for compiling/installing and supporting
my linux box coz I used the result of Sysiphus developers' works.
I would like to thank Sergey Vlasov, who tested these fonts and reported
about bugs. Also he help me to make some bug-reports to George about
pfaedit bugs.
I would like Dmitry 40in, who did big QA for some font outlines, drawn some glyphs,
and explain some The Truths for me.
I would like to thank Vlad Harchev (aka hvv), who
proofread this text for me.
Also I have to thank RMS for GPL and URW for releasing the fonts
under it.
Thank you very much!
Valek Filippov frob@df.ru
(C)opyLeft 2001

View File

@ -1,202 +0,0 @@
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.

View File

@ -0,0 +1,21 @@
#version {{version}}
// pathfinder/demo/shaders/debug_solid.fs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform vec4 uColor;
out vec4 oFragColor;
void main() {
oFragColor = vec4(uColor.rgb, 1.0) * uColor.a;
}

View File

@ -0,0 +1,22 @@
#version {{version}}
// pathfinder/demo/shaders/debug_solid.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform vec2 uFramebufferSize;
in vec2 aPosition;
void main() {
vec2 position = aPosition / uFramebufferSize * 2.0 - 1.0;
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
}

View File

@ -0,0 +1,25 @@
#version {{version}}
// pathfinder/resources/shaders/debug_texture.fs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform sampler2D uTexture;
uniform vec4 uColor;
in vec2 vTexCoord;
out vec4 oFragColor;
void main() {
float alpha = texture(uTexture, vTexCoord).r * uColor.a;
oFragColor = alpha * vec4(uColor.rgb, 1.0);
}

View File

@ -0,0 +1,27 @@
#version {{version}}
// pathfinder/demo/shaders/debug_texture.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform vec2 uFramebufferSize;
uniform vec2 uTextureSize;
in vec2 aPosition;
in vec2 aTexCoord;
out vec2 vTexCoord;
void main() {
vTexCoord = aTexCoord / uTextureSize;
vec2 position = aPosition / uFramebufferSize * 2.0 - 1.0;
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
}

View File

@ -0,0 +1,21 @@
#version {{version}}
// pathfinder/demo/resources/shaders/demo_ground.fs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform vec4 uColor;
out vec4 oFragColor;
void main() {
oFragColor = uColor;
}

View File

@ -0,0 +1,21 @@
#version {{version}}
// pathfinder/demo/resources/shaders/demo_ground.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform mat4 uTransform;
in vec2 aPosition;
void main() {
gl_Position = uTransform * vec4(aPosition.x, 0.0, aPosition.y, 1.0);
}

View File

@ -1,6 +1,8 @@
// pathfinder/shaders/gles2/stencil-aaa.fs.glsl
#version {{version}}
// pathfinder/demo2/stencil.fs.glsl
//
// Copyright (c) 2018 The Pathfinder Project Developers.
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -8,33 +10,32 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
precision mediump float;
precision highp float;
uniform sampler2D uAreaLUT;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
in vec2 vFrom;
in vec2 vTo;
out vec4 oFragColor;
void main() {
// Unpack.
vec2 from = vFrom, ctrl = vCtrl, to = vTo;
vec2 from = vFrom, to = vTo;
// Determine winding, and sort into a consistent order so we only need to find one root below.
bool winding = from.x < to.x;
vec2 left = winding ? from : to, right = winding ? to : from;
vec2 v0 = ctrl - left, v1 = right - ctrl;
vec2 left = from.x < to.x ? from : to, right = from.x < to.x ? to : from;
// Shoot a vertical ray toward the curve.
vec2 window = clamp(vec2(from.x, to.x), -0.5, 0.5);
float offset = mix(window.x, window.y, 0.5) - left.x;
float t = offset / (v0.x + sqrt(v1.x * offset - v0.x * (offset - v0.x)));
float t = offset / (right.x - left.x);
// Compute position and derivative to form a line approximation.
float y = mix(mix(left.y, ctrl.y, t), mix(ctrl.y, right.y, t), t);
float d = mix(v0.y, v1.y, t) / mix(v0.x, v1.x, t);
float y = mix(left.y, right.y, t);
float d = (right.y - left.y) / (right.x - left.x);
// Look up area under that line, and scale horizontally to the window size.
float dX = window.x - window.y;
gl_FragColor = vec4(texture2D(uAreaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX);
oFragColor = vec4(texture(uAreaLUT, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX);
}

View File

@ -0,0 +1,54 @@
#version {{version}}
// pathfinder/demo/fill.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform vec2 uFramebufferSize;
uniform vec2 uTileSize;
in vec2 aTessCoord;
in uint aFromPx;
in uint aToPx;
in vec2 aFromSubpx;
in vec2 aToSubpx;
in uint aTileIndex;
out vec2 vFrom;
out vec2 vTo;
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
uvec2 tileOffset = uvec2(aTileIndex % tilesPerRow, aTileIndex / tilesPerRow);
return vec2(tileOffset) * uTileSize;
}
void main() {
vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x);
vec2 from = vec2(aFromPx & 15u, aFromPx >> 4u) + aFromSubpx;
vec2 to = vec2(aToPx & 15u, aToPx >> 4u) + aToSubpx;
vec2 position;
if (aTessCoord.x < 0.5)
position.x = floor(min(from.x, to.x));
else
position.x = ceil(max(from.x, to.x));
if (aTessCoord.y < 0.5)
position.y = floor(min(from.y, to.y));
else
position.y = uTileSize.y;
vFrom = from - position;
vTo = to - position;
gl_Position = vec4((tileOrigin + position) / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0);
}

View File

@ -0,0 +1,59 @@
#version {{version}}
// pathfinder/resources/shaders/post.fs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
// TODO(pcwalton): This could be significantly optimized by operating on a
// sparse per-tile basis.
precision highp float;
uniform sampler2D uSource;
uniform vec2 uSourceSize;
uniform vec4 uFGColor;
uniform vec4 uBGColor;
uniform int uGammaCorrectionEnabled;
in vec2 vTexCoord;
out vec4 oFragColor;
{{{include_post_gamma_correct}}}
{{{include_post_convolve}}}
// Convolve horizontally in this pass.
float sample1Tap(float offset) {
return texture(uSource, vec2(vTexCoord.x + offset, vTexCoord.y)).r;
}
void main() {
// Apply defringing if necessary.
vec3 alpha;
if (uKernel.w == 0.0) {
alpha = texture(uSource, vTexCoord).rrr;
} else {
vec4 alphaLeft, alphaRight;
float alphaCenter;
sample9Tap(alphaLeft, alphaCenter, alphaRight, 1.0 / uSourceSize.x);
float r = convolve7Tap(alphaLeft, vec3(alphaCenter, alphaRight.xy));
float g = convolve7Tap(vec4(alphaLeft.yzw, alphaCenter), alphaRight.xyz);
float b = convolve7Tap(vec4(alphaLeft.zw, alphaCenter, alphaRight.x), alphaRight.yzw);
alpha = vec3(r, g, b);
}
// Apply gamma correction if necessary.
if (uGammaCorrectionEnabled != 0)
alpha = gammaCorrect(uBGColor.rgb, alpha);
// Finish.
oFragColor = vec4(mix(uBGColor.rgb, uFGColor.rgb, alpha), 1.0);
}

View File

@ -0,0 +1,22 @@
#version {{version}}
// pathfinder/resources/shaders/post.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
in vec2 aPosition;
out vec2 vTexCoord;
void main() {
vTexCoord = aPosition;
gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);
}

View File

@ -0,0 +1,37 @@
// pathfinder/resources/shaders/post_convolve.inc.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
// Zero if no convolution is to be performed.
uniform vec4 uKernel;
// This function is expected to return the alpha value of the pixel at the
// given offset in pixels. Offset 0.0 represents the current pixel.
float sample1Tap(float offset);
// Samples 9 taps around the current pixel.
void sample9Tap(out vec4 outAlphaLeft,
out float outAlphaCenter,
out vec4 outAlphaRight,
float onePixel) {
outAlphaLeft = vec4(uKernel.x > 0.0 ? sample1Tap(-4.0 * onePixel) : 0.0,
sample1Tap(-3.0 * onePixel),
sample1Tap(-2.0 * onePixel),
sample1Tap(-1.0 * onePixel));
outAlphaCenter = sample1Tap(0.0);
outAlphaRight = vec4(sample1Tap(1.0 * onePixel),
sample1Tap(2.0 * onePixel),
sample1Tap(3.0 * onePixel),
uKernel.x > 0.0 ? sample1Tap(4.0 * onePixel) : 0.0);
}
// Convolves 7 values with the kernel.
float convolve7Tap(vec4 alpha0, vec3 alpha1) {
return dot(alpha0, uKernel) + dot(alpha1, uKernel.zyx);
}

View File

@ -0,0 +1,24 @@
// pathfinder/resources/shaders/post_gamma_correct.inc.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
// The lookup table for gamma correction, in the same format WebRender
// expects.
uniform sampler2D uGammaLUT;
float gammaCorrectChannel(float bgColor, float fgColor) {
return texture(uGammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
}
// `fgColor` is in linear space.
vec3 gammaCorrect(vec3 bgColor, vec3 fgColor) {
return vec3(gammaCorrectChannel(bgColor.r, fgColor.r),
gammaCorrectChannel(bgColor.g, fgColor.g),
gammaCorrectChannel(bgColor.b, fgColor.b));
}

View File

@ -0,0 +1,26 @@
#version {{version}}
// pathfinder/resources/shaders/reproject.fs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform mat4 uOldTransform;
uniform sampler2D uTexture;
in vec2 vTexCoord;
out vec4 oFragColor;
void main() {
vec4 normTexCoord = uOldTransform * vec4(vTexCoord, 0.0, 1.0);
vec2 texCoord = ((normTexCoord.xy / normTexCoord.w) + 1.0) * 0.5;
oFragColor = texture(uTexture, texCoord);
}

View File

@ -0,0 +1,24 @@
#version {{version}}
// pathfinder/resources/shaders/reproject.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform mat4 uNewTransform;
in vec2 aPosition;
out vec2 vTexCoord;
void main() {
vTexCoord = aPosition;
gl_Position = uNewTransform * vec4(aPosition, 0.0, 1.0);
}

View File

@ -0,0 +1,20 @@
#version {{version}}
// pathfinder/resources/shaders/stencil.fs.glsl
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
precision highp float;
out vec4 oFragColor;
void main() {
// This should be color masked out.
oFragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,19 @@
#version {{version}}
// pathfinder/resources/shaders/stencil.vs.glsl
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
precision highp float;
in vec3 aPosition;
void main() {
gl_Position = vec4(aPosition, 1.0);
}

View File

@ -0,0 +1,26 @@
#version {{version}}
// pathfinder/demo/resources/shaders/mask_tile.fs.glsl
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
precision highp float;
uniform sampler2D uStencilTexture;
in vec2 vTexCoord;
in float vBackdrop;
in vec4 vColor;
out vec4 oFragColor;
void main() {
float coverage = abs(texture(uStencilTexture, vTexCoord).r + vBackdrop);
oFragColor = vec4(vColor.rgb, vColor.a * coverage);
}

View File

@ -0,0 +1,20 @@
#version {{version}}
// pathfinder/resources/shaders/tile_alpha_monochrome.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
{{{include_tile_alpha_vertex}}}
{{{include_tile_monochrome}}}
void main() {
computeVaryings();
}

View File

@ -0,0 +1,20 @@
#version {{version}}
// pathfinder/resources/shaders/tile_alpha_multicolor.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
{{{include_tile_alpha_vertex}}}
{{{include_tile_multicolor}}}
void main() {
computeVaryings();
}

View File

@ -0,0 +1,45 @@
// pathfinder/resources/shaders/tile_alpha_vertex.inc.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
uniform vec2 uFramebufferSize;
uniform vec2 uTileSize;
uniform vec2 uStencilTextureSize;
uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in uvec3 aTileOrigin;
in int aBackdrop;
in uint aObject;
in uint aTileIndex;
out vec2 vTexCoord;
out float vBackdrop;
out vec4 vColor;
vec4 getFillColor(uint object);
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
uvec2 tileOffset = uvec2(tileIndex % tilesPerRow, tileIndex / tilesPerRow);
return vec2(tileOffset) * uTileSize;
}
void computeVaryings() {
vec2 origin = vec2(aTileOrigin.xy) + vec2(aTileOrigin.z & 15u, aTileOrigin.z >> 4u) * 256.0;
vec2 pixelPosition = (origin + aTessCoord) * uTileSize + uViewBoxOrigin;
vec2 position = (pixelPosition / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0);
vec2 texCoord = computeTileOffset(aTileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize;
vTexCoord = texCoord / uStencilTextureSize;
vBackdrop = float(aBackdrop);
vColor = getFillColor(aObject);
gl_Position = vec4(position, 0.0, 1.0);
}

View File

@ -0,0 +1,15 @@
// pathfinder/resources/shaders/tile_monochrome.inc.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
uniform vec4 uFillColor;
vec4 getFillColor(uint object) {
return uFillColor;
}

View File

@ -0,0 +1,22 @@
// pathfinder/resources/shaders/tile_multicolor.inc.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
uniform sampler2D uFillColorsTexture;
uniform vec2 uFillColorsTextureSize;
vec2 computeFillColorTexCoord(uint object, vec2 textureSize) {
uint width = uint(textureSize.x);
return (vec2(float(object % width), float(object / width)) + vec2(0.5)) / textureSize;
}
vec4 getFillColor(uint object) {
vec2 colorTexCoord = computeFillColorTexCoord(object, uFillColorsTextureSize);
return texture(uFillColorsTexture, colorTexCoord);
}

View File

@ -0,0 +1,21 @@
#version {{version}}
// pathfinder/demo2/opaque.fs.glsl
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// 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.
precision highp float;
in vec4 vColor;
out vec4 oFragColor;
void main() {
oFragColor = vColor;
}

View File

@ -0,0 +1,20 @@
#version {{version}}
// pathfinder/resources/shaders/tile_solid_monochrome.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
{{{include_tile_solid_vertex}}}
{{{include_tile_monochrome}}}
void main() {
computeVaryings();
}

View File

@ -0,0 +1,20 @@
#version {{version}}
// pathfinder/resources/shaders/tile_solid_multicolor.vs.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
precision highp float;
{{{include_tile_solid_vertex}}}
{{{include_tile_multicolor}}}
void main() {
computeVaryings();
}

View File

@ -0,0 +1,29 @@
// pathfinder/resources/shaders/tile_solid_vertex.inc.glsl
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
uniform vec2 uFramebufferSize;
uniform vec2 uTileSize;
uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in vec2 aTileOrigin;
in uint aObject;
out vec4 vColor;
vec4 getFillColor(uint object);
void computeVaryings() {
vec2 pixelPosition = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin;
vec2 position = (pixelPosition / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0);
vColor = getFillColor(aObject);
gl_Position = vec4(position, 0.0, 1.0);
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 269 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 192 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 134 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 59 KiB

25021
resources/svg/paper.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 727 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 399 KiB

View File

@ -1,17 +0,0 @@
Font,Size,Character,AA Mode,Subpixel,Reference Renderer,Expected SSIM
open-sans,32,A,xcaa,1,core-graphics,0.766
open-sans,32,A,xcaa,1,freetype,0.595
open-sans,48,A,xcaa,1,core-graphics,0.952
open-sans,48,A,xcaa,1,freetype,0.774
open-sans,48,P,ssaa,0,core-graphics,0.969
open-sans,48,P,none,0,freetype,0.841
open-sans,48,P,ssaa,0,freetype,0.847
open-sans,48,P,none,0,core-graphics,0.926
eb-garamond,64,G,xcaa,1,core-graphics,0.705
eb-garamond,36,J,xcaa,0,core-graphics,0.462
eb-garamond,40,J,ssaa,0,core-graphics,0.399
eb-garamond,40,J,ssaa,0,freetype,0.321
eb-garamond,40,J,none,0,freetype,0.318
eb-garamond,24,J,xcaa,1,core-graphics,0.523
nimbus-sans,48,X,xcaa,1,freetype,0.725
nimbus-sans,48,X,xcaa,1,core-graphics,0.968
1 Font Size Character AA Mode Subpixel Reference Renderer Expected SSIM
2 open-sans 32 A xcaa 1 core-graphics 0.766
3 open-sans 32 A xcaa 1 freetype 0.595
4 open-sans 48 A xcaa 1 core-graphics 0.952
5 open-sans 48 A xcaa 1 freetype 0.774
6 open-sans 48 P ssaa 0 core-graphics 0.969
7 open-sans 48 P none 0 freetype 0.841
8 open-sans 48 P ssaa 0 freetype 0.847
9 open-sans 48 P none 0 core-graphics 0.926
10 eb-garamond 64 G xcaa 1 core-graphics 0.705
11 eb-garamond 36 J xcaa 0 core-graphics 0.462
12 eb-garamond 40 J ssaa 0 core-graphics 0.399
13 eb-garamond 40 J ssaa 0 freetype 0.321
14 eb-garamond 40 J none 0 freetype 0.318
15 eb-garamond 24 J xcaa 1 core-graphics 0.523
16 nimbus-sans 48 X xcaa 1 freetype 0.725
17 nimbus-sans 48 X xcaa 1 core-graphics 0.968

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 338 B

View File

@ -1,28 +0,0 @@
// pathfinder/shaders/gles2/blit-gamma.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Blits a texture, applying gamma correction.
precision mediump float;
/// The source texture to blit.
uniform sampler2D uSource;
/// The approximate background color, in linear RGB.
uniform vec3 uBGColor;
/// The gamma LUT.
uniform sampler2D uGammaLUT;
/// The incoming texture coordinate.
varying vec2 vTexCoord;
void main() {
vec4 source = texture2D(uSource, vTexCoord);
gl_FragColor = vec4(gammaCorrect(source.rgb, uBGColor, uGammaLUT), source.a);
}

View File

@ -1,23 +0,0 @@
// pathfinder/shaders/gles2/blit-linear.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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 trivial shader that does nothing more than blit a texture.
precision mediump float;
/// The source texture to blit.
uniform sampler2D uSource;
/// The incoming texture coordinate.
varying vec2 vTexCoord;
void main() {
gl_FragColor = texture2D(uSource, vTexCoord);
}

View File

@ -1,31 +0,0 @@
// pathfinder/shaders/gles2/blit.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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 trivial shader that does nothing more than blit a texture.
precision mediump float;
/// A 3D transform to apply to the scene.
uniform mat4 uTransform;
/// A pair of fixed scale factors to be applied to the texture coordinates.
uniform vec2 uTexScale;
/// The 2D vertex position.
attribute vec2 aPosition;
/// The texture coordinate.
attribute vec2 aTexCoord;
/// The outgoing texture coordinate.
varying vec2 vTexCoord;
void main() {
gl_Position = uTransform * vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord * uTexScale;
}

View File

@ -1,329 +0,0 @@
// pathfinder/shaders/gles2/common.inc.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
#version 100
#extension GL_OES_standard_derivatives : require
#define FREETYPE_LCD_FILTER_FACTOR_0 0.337254902
#define FREETYPE_LCD_FILTER_FACTOR_1 0.301960784
#define FREETYPE_LCD_FILTER_FACTOR_2 0.031372549
// These intentionally do not precisely match what Core Graphics does (a Lanczos function), because
// we don't want any ringing artefacts.
#define CG_LCD_FILTER_FACTOR_0 0.286651906
#define CG_LCD_FILTER_FACTOR_1 0.221434336
#define CG_LCD_FILTER_FACTOR_2 0.102074051
#define CG_LCD_FILTER_FACTOR_3 0.033165660
#define MAX_PATHS 65536
#define EPSILON 0.001
precision highp float;
/// Returns true if the given number is close to zero.
bool isNearZero(float x) {
return abs(x) < EPSILON;
}
/// Computes `ia % ib`.
///
/// This function is not in OpenGL ES 2 but can be polyfilled.
/// See: https://stackoverflow.com/a/36078859
int imod(int ia, int ib) {
float a = float(ia), b = float(ib);
float m = a - floor((a + 0.5) / b) * b;
return int(floor(m + 0.5));
}
float fastSign(float x) {
return x > 0.0 ? 1.0 : -1.0;
}
float det2(mat2 m) {
return m[0][0] * m[1][1] - m[0][1] * m[1][0];
}
/// Returns the *2D* result of transforming the given 2D point with the given 4D transformation
/// matrix.
///
/// The z and w coordinates are treated as 0.0 and 1.0, respectively.
vec2 transformVertexPosition(vec2 position, mat4 transform) {
return (transform * vec4(position, 0.0, 1.0)).xy;
}
/// Returns the 2D result of transforming the given 2D position by the given ST-transform.
///
/// An ST-transform is a combined 2D scale and translation, where the (x, y) coordinates specify
/// the scale and and the (z, w) coordinates specify the translation.
vec2 transformVertexPositionST(vec2 position, vec4 transformST) {
return position * transformST.xy + transformST.zw;
}
vec2 transformVertexPositionAffine(vec2 position, vec4 transformST, vec2 transformExt) {
return position * transformST.xy + position.yx * transformExt + transformST.zw;
}
vec2 transformVertexPositionInverseLinear(vec2 position, mat2 transform) {
position = vec2(det2(mat2(position, transform[1])), det2(mat2(transform[0], position)));
return position / det2(transform);
}
/// Interpolates the given 2D position in the vertical direction using the given ultra-slight
/// hints.
///
/// Similar in spirit to the `IUP[y]` TrueType command, but minimal.
///
/// pathHints.x: xHeight
/// pathHints.y: hintedXHeight
/// pathHints.z: stemHeight
/// pathHints.w: hintedStemHeight
///
/// TODO(pcwalton): Do something smarter with overshoots and the blue zone.
/// TODO(pcwalton): Support interpolating relative to arbitrary horizontal stems, not just the
/// baseline, x-height, and stem height.
vec2 hintPosition(vec2 position, vec4 pathHints) {
float y;
if (position.y >= pathHints.z) {
y = position.y - pathHints.z + pathHints.w;
} else if (position.y >= pathHints.x) {
float t = (position.y - pathHints.x) / (pathHints.z - pathHints.x);
y = mix(pathHints.y, pathHints.w, t);
} else if (position.y >= 0.0) {
y = mix(0.0, pathHints.y, position.y / pathHints.x);
} else {
y = position.y;
}
return vec2(position.x, y);
}
vec2 quantize(vec2 position) {
return (floor(position * 20000.0 + 0.5) - 0.5) / 20000.0;
}
/// Converts the given 2D position in clip space to device pixel space (with origin in the lower
/// left).
vec2 convertClipToScreenSpace(vec2 position, ivec2 framebufferSize) {
return (position + 1.0) * 0.5 * vec2(framebufferSize);
}
/// Converts the given 2D position in device pixel space (with origin in the lower left) to clip
/// space.
vec2 convertScreenToClipSpace(vec2 position, ivec2 framebufferSize) {
return position / vec2(framebufferSize) * 2.0 - 1.0;
}
/// Packs the given path ID into a floating point value suitable for storage in the depth buffer.
/// This function returns values in clip space (i.e. what `gl_Position` is in).
float convertPathIndexToViewportDepthValue(int pathIndex) {
return float(pathIndex) / float(MAX_PATHS) * 2.0 - 1.0;
}
/// Displaces the given point by the given distance in the direction of the normal angle.
vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) {
return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount;
}
/// Returns true if the slope of the line along the given vector is negative.
bool slopeIsNegative(vec2 dp) {
return dp.y < 0.0;
}
/// Uses Liang-Barsky to clip the line to the left and right of the pixel square.
///
/// Returns vec4(P0', dP').
vec4 clipLineToPixelColumn(vec2 p0, vec2 dP, float pixelCenterX) {
vec2 pixelColumnBounds = vec2(-0.5, 0.5) + pixelCenterX;
vec2 qX = pixelColumnBounds - p0.xx;
vec2 tX = clamp(qX / dP.xx, 0.0, 1.0);
return vec4(p0 + dP * tX.x, dP * (tX.y - tX.x));
}
/// Uses Liang-Barsky to clip the line to the top and bottom of the pixel square.
///
/// Returns vec4(P0'', dP''). In the case of horizontal lines, this can yield -Infinity or
/// Infinity.
vec4 clipLineToPixelRow(vec2 p0, vec2 dP, float pixelCenterY, out float outPixelTop) {
vec2 pixelRowBounds = vec2(-0.5, 0.5) + pixelCenterY;
outPixelTop = pixelRowBounds.y;
vec2 qY = pixelRowBounds - p0.yy;
vec2 tY = clamp((slopeIsNegative(dP) ? qY.yx : qY.xy) / dP.yy, 0.0, 1.0);
return vec4(p0 + dP * tY.x, dP * (tY.y - tY.x));
}
/// Computes the area of the polygon covering the pixel with the given boundaries.
///
/// The line must run left-to-right and must already be clipped to the left and right sides of the
/// pixel, which implies that `dP.x` must be within the range [0.0, 1.0].
///
/// * `p0X` is the start point of the line.
/// * `dPX` is the vector from the start point to the endpoint of the line.
/// * `pixelCenterY` is the Y coordinate of the center of the pixel in window coordinates (i.e.
/// `gl_FragCoord.y`).
/// * `winding` is the winding number (1 or -1).
float computeCoverage(vec2 p0X, vec2 dPX, float pixelCenterY, float winding) {
// Clip to the pixel row.
float pixelTop;
vec4 p0DPY = clipLineToPixelRow(p0X, dPX, pixelCenterY, pixelTop);
vec2 p0 = p0DPY.xy, dP = p0DPY.zw;
vec2 p1 = p0 + dP;
// If the line doesn't pass through this pixel, detect that and bail.
//
// This should be worth a branch because it's very common for fragment blocks to all hit this
// path.
//
// The variable is required to work around a bug in the macOS Nvidia drivers.
// Without moving the condition in a variable, the early return is ignored. See #51.
bool lineDoesNotPassThroughPixel = isNearZero(dP.x) && isNearZero(dP.y);
if (lineDoesNotPassThroughPixel)
return p0X.y < pixelTop ? winding * dPX.x : 0.0;
// Calculate points A0-A2.
float a2x;
vec2 a0, a1;
if (slopeIsNegative(dP)) {
a2x = p0X.x + dPX.x;
a0 = p0;
a1 = p1;
} else {
a2x = p0X.x;
a0 = p1;
a1 = p0;
}
// Calculate area with the shoelace formula.
// This is conceptually the sum of 5 determinants for points A0-A5, where A2-A5 are:
//
// A2 = (a2.x, a1.y)
// A3 = (a2.x, top)
// A4 = (a0.x, top)
//
// The formula is optimized. See: http://geomalgorithms.com/a01-_area.html
float area = a0.x * (a0.y + a1.y - 2.0 * pixelTop) +
a1.x * (a1.y - a0.y) +
2.0 * a2x * (pixelTop - a1.y);
return abs(area) * winding * 0.5;
}
/// Solves the equation:
///
/// x = p0x + t^2 * (p0x - 2*p1x + p2x) + t*(2*p1x - 2*p0x)
///
/// We use the Citardauq Formula to avoid floating point precision issues.
vec2 solveCurveT(float p0x, float p1x, float p2x, vec2 x) {
float a = p0x - 2.0 * p1x + p2x;
float b = 2.0 * p1x - 2.0 * p0x;
vec2 c = p0x - x;
return 2.0 * c / (-b - sqrt(b * b - 4.0 * a * c));
}
/// Applies a slight horizontal blur to reduce color fringing on LCD screens
/// when performing subpixel AA.
///
/// The algorithm should be identical to that of FreeType:
/// https://www.freetype.org/freetype2/docs/reference/ft2-lcd_filtering.html
float freetypeLCDFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float shadeR2) {
return FREETYPE_LCD_FILTER_FACTOR_2 * shadeL2 +
FREETYPE_LCD_FILTER_FACTOR_1 * shadeL1 +
FREETYPE_LCD_FILTER_FACTOR_0 * shade0 +
FREETYPE_LCD_FILTER_FACTOR_1 * shadeR1 +
FREETYPE_LCD_FILTER_FACTOR_2 * shadeR2;
}
float sample1Tap(sampler2D source, vec2 center, float offset) {
return texture2D(source, vec2(center.x + offset, center.y)).r;
}
void sample9Tap(out vec4 outShadesL,
out float outShadeC,
out vec4 outShadesR,
sampler2D source,
vec2 center,
float onePixel,
vec4 kernel) {
outShadesL = vec4(kernel.x > 0.0 ? sample1Tap(source, center, -4.0 * onePixel) : 0.0,
sample1Tap(source, center, -3.0 * onePixel),
sample1Tap(source, center, -2.0 * onePixel),
sample1Tap(source, center, -1.0 * onePixel));
outShadeC = sample1Tap(source, center, 0.0);
outShadesR = vec4(sample1Tap(source, center, 1.0 * onePixel),
sample1Tap(source, center, 2.0 * onePixel),
sample1Tap(source, center, 3.0 * onePixel),
kernel.x > 0.0 ? sample1Tap(source, center, 4.0 * onePixel) : 0.0);
}
float convolve7Tap(vec4 shades0, vec3 shades1, vec4 kernel) {
return dot(shades0, kernel) + dot(shades1, kernel.zyx);
}
float gammaCorrectChannel(float fgColor, float bgColor, sampler2D gammaLUT) {
return texture2D(gammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
}
// `fgColor` is in linear space.
vec3 gammaCorrect(vec3 fgColor, vec3 bgColor, sampler2D gammaLUT) {
return vec3(gammaCorrectChannel(fgColor.r, bgColor.r, gammaLUT),
gammaCorrectChannel(fgColor.g, bgColor.g, gammaLUT),
gammaCorrectChannel(fgColor.b, bgColor.b, gammaLUT));
}
vec4 fetchFloat4Data(sampler2D dataTexture, int index, ivec2 dimensions) {
ivec2 pixelCoord = ivec2(imod(index, dimensions.x), index / dimensions.x);
return texture2D(dataTexture, (vec2(pixelCoord) + 0.5) / vec2(dimensions));
}
vec2 fetchFloat2Data(sampler2D dataTexture, int index, ivec2 dimensions) {
int texelIndex = index / 2;
vec4 texel = fetchFloat4Data(dataTexture, texelIndex, dimensions);
return texelIndex * 2 == index ? texel.xy : texel.zw;
}
vec4 fetchPathAffineTransform(out vec2 outPathTransformExt,
sampler2D pathTransformSTTexture,
ivec2 pathTransformSTDimensions,
sampler2D pathTransformExtTexture,
ivec2 pathTransformExtDimensions,
int pathID) {
outPathTransformExt = fetchFloat2Data(pathTransformExtTexture,
pathID,
pathTransformExtDimensions);
return fetchFloat4Data(pathTransformSTTexture, pathID, pathTransformSTDimensions);
}
// Are we inside the convex hull of the curve? (This will always be false if this is a line.)
bool insideCurve(vec3 uv) {
return uv.z != 0.0 && uv.x > 0.0 && uv.x < 1.0 && uv.y > 0.0 && uv.y < 1.0;
}
float signedDistanceToCurve(vec2 uv, vec2 dUVDX, vec2 dUVDY, bool inCurve) {
// u^2 - v for curves inside uv square; u - v otherwise.
float g = uv.x;
vec2 dG = vec2(dUVDX.x, dUVDY.x);
if (inCurve) {
g *= uv.x;
dG *= 2.0 * uv.x;
}
g -= uv.y;
dG -= vec2(dUVDX.y, dUVDY.y);
return g / length(dG);
}
// Cubic approximation to the square area coverage, accurate to about 4%.
float estimateArea(float dist) {
if (dist >= 0.707107)
return 0.5;
// Catch NaNs here.
if (!(dist > -0.707107))
return -0.5;
return 1.14191 * dist - 0.83570 * dist * dist * dist;
}

View File

@ -1,74 +0,0 @@
// pathfinder/shaders/gles2/conservative-interior.vs.glsl
//
// Copyright (c) 2018 The Pathfinder Project Developers.
//
// 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.
//! Renders polygonal portions of a mesh, only filling pixels that are fully
//! covered.
//!
//! Typically, you will run this shader before running XCAA.
//! Remember to enable the depth test with a `GREATER` depth function for optimal
//! performance.
precision highp float;
/// An affine transform to be applied to all points.
uniform vec4 uTransformST;
uniform vec2 uTransformExt;
/// Vertical snapping positions.
uniform vec4 uHints;
/// The framebuffer size in pixels.
uniform ivec2 uFramebufferSize;
/// The size of the path colors texture in texels.
uniform ivec2 uPathColorsDimensions;
/// The fill color for each path.
uniform sampler2D uPathColors;
/// The size of the path transform buffer texture in texels.
uniform ivec2 uPathTransformSTDimensions;
/// The path transform buffer texture, one path dilation per texel.
uniform sampler2D uPathTransformST;
/// The size of the extra path transform factors buffer texture in texels.
uniform ivec2 uPathTransformExtDimensions;
/// The extra path transform factors buffer texture, packed two path transforms per texel.
uniform sampler2D uPathTransformExt;
/// The amount of faux-bold to apply, in local path units.
uniform vec2 uEmboldenAmount;
/// The 2D position of this point.
attribute vec2 aPosition;
/// The path ID, starting from 1.
attribute float aPathID;
/// The vertex ID. In OpenGL 3.0+, this can be omitted in favor of `gl_VertexID`.
attribute float aVertexID;
/// The color of this path.
varying vec4 vColor;
void main() {
int pathID = int(aPathID);
int vertexID = int(aVertexID);
vec4 transformST = fetchFloat4Data(uPathTransformST, pathID, uPathTransformSTDimensions);
mat2 globalTransformLinear = mat2(uTransformST.x, uTransformExt, uTransformST.y);
mat2 localTransformLinear = mat2(transformST.x, 0.0, 0.0, transformST.y);
mat2 transformLinear = globalTransformLinear * localTransformLinear;
vec2 translation = uTransformST.zw + globalTransformLinear * transformST.zw;
float onePixel = 2.0 / float(uFramebufferSize.y);
float dilation = length(transformVertexPositionInverseLinear(vec2(0.0, onePixel),
transformLinear));
vec2 position = aPosition + vec2(0.0, imod(vertexID, 6) < 3 ? dilation : -dilation);
position = transformLinear * position + translation;
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
}

View File

@ -1,25 +0,0 @@
// pathfinder/shaders/gles2/demo-3d-distant-glyph.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders cached textures of distant glyphs in the 3D demo.
precision highp float;
/// The color of the font.
uniform vec4 uColor;
/// The cached glyph atlas.
uniform sampler2D uAtlas;
/// The texture coordinate.
varying vec2 vTexCoord;
void main() {
gl_FragColor = uColor * texture2D(uAtlas, vTexCoord).rrrr;
}

View File

@ -1,34 +0,0 @@
// pathfinder/shaders/gles2/demo-3d-distant-glyph.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders cached textures of distant glyphs in the 3D demo.
precision highp float;
/// A concatenated transform to be applied to each point.
uniform mat4 uTransform;
/// The texture coordinates for this glyph.
uniform vec4 uGlyphTexCoords;
/// The size of the glyph in local coordinates.
uniform vec2 uGlyphSize;
/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0).
attribute vec2 aQuadPosition;
// The world-space 2D position of this vertex.
attribute vec2 aPosition;
/// The texture coordinate.
varying vec2 vTexCoord;
void main() {
vec2 positionBL = aPosition, positionTR = aPosition + uGlyphSize;
gl_Position = uTransform * vec4(mix(positionBL, positionTR, aQuadPosition), 0.0, 1.0);
vTexCoord = mix(uGlyphTexCoords.xy, uGlyphTexCoords.zw, aQuadPosition);
}

View File

@ -1,52 +0,0 @@
// pathfinder/shaders/gles2/demo-3d-monument.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders the monument surface in the 3D demo.
precision mediump float;
/// The 3D position of the light.
uniform vec3 uLightPosition;
/// The ambient color of the light.
uniform vec3 uAmbientColor;
/// The diffuse color of the light.
uniform vec3 uDiffuseColor;
/// The Phong specular color of the light.
uniform vec3 uSpecularColor;
/// The Phong albedo exponent.
uniform float uShininess;
/// The normal of these vertices.
uniform vec3 uNormal;
uniform bool uEnableLighting;
varying vec3 vPosition;
void main() {
vec3 normal = normalize(uNormal);
vec3 lightDirection = normalize(uLightPosition - vPosition);
vec3 color = uAmbientColor;
if (uEnableLighting) {
float lambertian = max(dot(lightDirection, normal), 0.0);
float specular = 0.0;
if (lambertian > 0.0) {
vec3 viewDirection = normalize(-vPosition);
vec3 halfDirection = normalize(lightDirection + viewDirection);
float specularAngle = max(dot(halfDirection, normal), 0.0);
specular = pow(specularAngle, uShininess);
}
color = color + uAmbientColor + lambertian * uDiffuseColor + specular * uSpecularColor;
}
gl_FragColor = vec4(color, 1.0);
}

View File

@ -1,30 +0,0 @@
// pathfinder/shaders/gles2/demo-3d-monument.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders the monument surface in the 3D demo.
precision mediump float;
/// The 3D projection matrix.
uniform mat4 uProjection;
/// The 3D modelview matrix.
uniform mat4 uModelview;
/// The 3D vertex position.
attribute vec3 aPosition;
/// The 3D vertex position.
varying vec3 vPosition;
void main() {
vec4 position = uModelview * vec4(aPosition, 1.0);
vPosition = position.xyz / position.w;
gl_Position = uProjection * position;
}

View File

@ -1,72 +0,0 @@
// pathfinder/shaders/gles2/direct-3d-curve.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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 version of `direct-curve` that takes each vertex's Z value from the
//! transform instead of the path ID.
//!
//! FIXME(pcwalton): For CSS 3D transforms, I think `direct-curve` will need
//! to do what this shader does. Perhaps these two shaders should be unified…
precision highp float;
/// A 3D transform to be applied to all points.
uniform mat4 uTransform;
/// The size of the path transform buffer texture in texels.
uniform ivec2 uPathTransformSTDimensions;
/// The path transform buffer texture, one dilation per path ID.
uniform sampler2D uPathTransformST;
/// The size of the extra path transform factors buffer texture in texels.
uniform ivec2 uPathTransformExtDimensions;
/// The extra path transform factors buffer texture, packed two path transforms per texel.
uniform sampler2D uPathTransformExt;
/// The size of the path colors buffer texture in texels.
uniform ivec2 uPathColorsDimensions;
/// The path colors buffer texture, one color per path ID.
uniform sampler2D uPathColors;
/// The amount of faux-bold to apply, in local path units.
uniform vec2 uEmboldenAmount;
/// The 2D position of this point.
attribute vec2 aPosition;
/// The angle of the 2D normal for this point.
attribute float aNormalAngle;
/// The vertex ID. In OpenGL 3.0+, this can be omitted in favor of `gl_VertexID`.
attribute float aVertexID;
/// The path ID, starting from 1.
attribute float aPathID;
/// The fill color of this path.
varying vec4 vColor;
/// The outgoing abstract Loop-Blinn texture coordinate.
varying vec2 vTexCoord;
void main() {
int pathID = int(aPathID);
int vertexID = int(aVertexID);
vec2 pathTransformExt;
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
vec2 position = dilatePosition(aPosition, aNormalAngle, uEmboldenAmount);
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
gl_Position = uTransform * vec4(position, 0.0, 1.0);
int vertexIndex = imod(vertexID, 3);
vec2 texCoord = vec2(float(vertexIndex) * 0.5, float(vertexIndex == 2));
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
vTexCoord = texCoord;
}

View File

@ -1,51 +0,0 @@
// pathfinder/shaders/gles2/direct-3d-interior.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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 version of `direct-interior` that takes each vertex's Z value from the
//! transform instead of the path ID.
//!
//! FIXME(pcwalton): For CSS 3D transforms, I think `direct-interior` will need
//! to do what this shader does. Perhaps these two shaders should be unified…
precision highp float;
uniform mat4 uTransform;
uniform vec2 uEmboldenAmount;
uniform ivec2 uPathColorsDimensions;
uniform sampler2D uPathColors;
uniform ivec2 uPathTransformSTDimensions;
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
attribute vec2 aPosition;
attribute float aPathID;
attribute float aNormalAngle;
varying vec4 vColor;
void main() {
int pathID = int(aPathID);
vec2 pathTransformExt;
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
vec2 position = dilatePosition(aPosition, aNormalAngle, uEmboldenAmount);
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
gl_Position = uTransform * vec4(position, 0.0, 1.0);
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
}

View File

@ -1,34 +0,0 @@
// pathfinder/shaders/gles2/direct-curve.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Implements the quadratic Loop-Blinn formulation to render curved parts of
//! the mesh.
//!
//! This shader performs no antialiasing; if you want antialiased output from
//! this shader, use MSAA with sample-level shading (GL 4.x) or else perform
//! SSAA by rendering to a higher-resolution framebuffer and downsampling (GL
//! 3.x and below).
//!
//! If you know your mesh has no curves (i.e. it consists solely of polygons),
//! then you don't need to run this shader.
precision highp float;
/// The fill color of this path.
varying vec4 vColor;
/// The abstract Loop-Blinn texture coordinate.
varying vec2 vTexCoord;
void main() {
float side = sign(vTexCoord.x * vTexCoord.x - vTexCoord.y);
float winding = gl_FrontFacing ? -1.0 : 1.0;
float alpha = float(side == winding);
gl_FragColor = alpha * vColor;
}

View File

@ -1,77 +0,0 @@
// pathfinder/shaders/gles2/direct-curve.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Implements the quadratic Loop-Blinn formulation to render curved parts of
//! the mesh.
//!
//! This shader performs no antialiasing; if you want antialiased output from
//! this shader, use MSAA with sample-level shading (GL 4.x) or else perform
//! SSAA by rendering to a higher-resolution framebuffer and downsampling (GL
//! 3.x and below).
//!
//! If you know your mesh has no curves (i.e. it consists solely of polygons),
//! then you don't need to run this shader.
precision highp float;
/// A 3D transform to be applied to all points.
uniform mat4 uTransform;
/// Vertical snapping positions.
uniform vec4 uHints;
/// The size of the path colors texture in texels.
uniform ivec2 uPathColorsDimensions;
/// The fill color for each path.
uniform sampler2D uPathColors;
/// The size of the path transform buffer texture in texels.
uniform ivec2 uPathTransformSTDimensions;
/// The path transform buffer texture, one path dilation per texel.
uniform sampler2D uPathTransformST;
/// The size of the extra path transform factors buffer texture in texels.
uniform ivec2 uPathTransformExtDimensions;
/// The extra path transform factors buffer texture, packed two path transforms per texel.
uniform sampler2D uPathTransformExt;
/// The 2D position of this point.
attribute vec2 aPosition;
/// The vertex ID. In OpenGL 3.0+, this can be omitted in favor of `gl_VertexID`.
attribute float aVertexID;
/// The path ID, starting from 1.
attribute float aPathID;
/// The fill color of this path.
varying vec4 vColor;
/// The outgoing abstract Loop-Blinn texture coordinate.
varying vec2 vTexCoord;
void main() {
int pathID = int(aPathID);
int vertexID = int(aVertexID);
vec2 pathTransformExt;
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
vec2 position = hintPosition(aPosition, uHints);
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
position = transformVertexPosition(position, uTransform);
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);
int vertexIndex = imod(vertexID, 3);
vec2 texCoord = vec2(float(vertexIndex) * 0.5, float(vertexIndex == 2));
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
vTexCoord = texCoord;
}

View File

@ -1,24 +0,0 @@
// pathfinder/shaders/gles2/direct-interior.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders polygonal portions of a mesh.
//!
//! Typically, you will run this shader before running `direct-curve`.
//! Remember to enable the depth test with a `GREATER` depth function for optimal
//! performance.
precision highp float;
/// The color of this path.
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}

View File

@ -1,63 +0,0 @@
// pathfinder/shaders/gles2/direct-interior.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders polygonal portions of a mesh.
//!
//! Typically, you will run this shader before running `direct-curve`.
//! Remember to enable the depth test with a `GREATER` depth function for optimal
//! performance.
precision highp float;
/// A 3D transform to be applied to all points.
uniform mat4 uTransform;
/// Vertical snapping positions.
uniform vec4 uHints;
/// The size of the path colors texture in texels.
uniform ivec2 uPathColorsDimensions;
/// The fill color for each path.
uniform sampler2D uPathColors;
/// The size of the path transform buffer texture in texels.
uniform ivec2 uPathTransformSTDimensions;
/// The path transform buffer texture, one path dilation per texel.
uniform sampler2D uPathTransformST;
/// The size of the extra path transform factors buffer texture in texels.
uniform ivec2 uPathTransformExtDimensions;
/// The extra path transform factors buffer texture, packed two path transforms per texel.
uniform sampler2D uPathTransformExt;
/// The 2D position of this point.
attribute vec2 aPosition;
/// The path ID, starting from 1.
attribute float aPathID;
/// The color of this path.
varying vec4 vColor;
void main() {
int pathID = int(aPathID);
vec2 pathTransformExt;
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
vec2 position = hintPosition(aPosition, uHints);
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
position = transformVertexPosition(position, uTransform);
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
}

View File

@ -1,31 +0,0 @@
// pathfinder/shaders/gles2/mcaa.fs.glsl
//
// Copyright (c) 2018 The Pathfinder Project Developers.
//
// 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.
//! Renders paths when performing multicolor *mesh coverage antialiasing*
//! (MCAA). This one shader handles both lines and curves.
//!
//! This shader expects to render to a standard RGB color buffer.
precision highp float;
varying vec4 vColor;
varying vec4 vUV;
varying vec4 vSignMode;
void main() {
bool inUpperCurve = insideCurve(vec3(vUV.xy, vSignMode.z > 0.0 ? 1.0 : 0.0));
bool inLowerCurve = insideCurve(vec3(vUV.zw, vSignMode.w > 0.0 ? 1.0 : 0.0));
float upperDist = signedDistanceToCurve(vUV.xy, dFdx(vUV.xy), dFdy(vUV.xy), inUpperCurve);
float lowerDist = signedDistanceToCurve(vUV.zw, dFdx(vUV.zw), dFdy(vUV.zw), inLowerCurve);
float alpha = -estimateArea(upperDist * vSignMode.x) - estimateArea(lowerDist * vSignMode.y);
gl_FragColor = alpha * vColor;
}

View File

@ -1,109 +0,0 @@
// pathfinder/shaders/gles2/mcaa.vs.glsl
//
// Copyright (c) 2018 The Pathfinder Project Developers.
//
// 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.
//! Renders paths when performing *mesh coverage antialiasing* (MCAA). This
//! one shader handles both lines and curves.
//!
//! This shader expects to render to a standard RGB color buffer in the
//! multicolor case or a single-channel floating-point color buffer in the
//! monochrome case.
//!
//! Set state as follows depending on whether multiple overlapping multicolor
//! paths are present:
//!
//! * When paths of multiple colors are present, use
//! `glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE)` and
//! set `uMulticolor` to 1.
//!
//! * Otherwise, if only one color of path is present, use
//! `glBlendFunc(GL_ONE, GL_ONE)` and set `uMulticolor` to 0.
//!
//! Use this shader only when your transform is only a scale and/or
//! translation, not a perspective, rotation, or skew. (Otherwise, consider
//! repartitioning the path to generate a new mesh, or, alternatively, use the
//! direct Loop-Blinn shaders.)
#define MAX_SLOPE 10.0
precision highp float;
/// A scale and transform to be applied to the object.
uniform vec4 uTransformST;
uniform vec2 uTransformExt;
/// The framebuffer size in pixels.
uniform ivec2 uFramebufferSize;
/// The size of the path transform buffer texture in texels.
uniform ivec2 uPathTransformSTDimensions;
/// The path transform buffer texture, one dilation per path ID.
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
/// The size of the path colors buffer texture in texels.
uniform ivec2 uPathColorsDimensions;
/// The path colors buffer texture, one color per path ID.
uniform sampler2D uPathColors;
/// True if multiple colors are being rendered; false otherwise.
///
/// If this is true, then points will be snapped to the nearest pixel.
uniform bool uMulticolor;
attribute vec2 aTessCoord;
attribute vec4 aRect;
attribute vec4 aUV;
attribute vec4 aDUVDX;
attribute vec4 aDUVDY;
// TODO(pcwalton): This is redundant; sign 0 can be used to indicate lines.
attribute vec4 aSignMode;
attribute float aPathID;
varying vec4 vColor;
varying vec4 vUV;
varying vec4 vSignMode;
void main() {
vec2 tessCoord = aTessCoord;
int pathID = int(floor(aPathID));
vec4 color;
if (uMulticolor)
color = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
else
color = vec4(1.0);
vec2 transformExt;
vec4 transformST = fetchPathAffineTransform(transformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
mat2 globalTransformLinear = mat2(uTransformST.x, uTransformExt, uTransformST.y);
mat2 localTransformLinear = mat2(transformST.x, -transformExt, transformST.y);
mat2 rectTransformLinear = mat2(aRect.z - aRect.x, 0.0, 0.0, aRect.w - aRect.y);
mat2 transformLinear = globalTransformLinear * localTransformLinear * rectTransformLinear;
vec2 translation = transformST.zw + localTransformLinear * aRect.xy;
translation = uTransformST.zw + globalTransformLinear * translation;
float onePixel = 2.0 / float(uFramebufferSize.y);
float dilation = length(transformVertexPositionInverseLinear(vec2(0.0, onePixel),
transformLinear));
tessCoord.y += tessCoord.y < 0.5 ? -dilation : dilation;
vec2 position = transformLinear * tessCoord + translation;
vec4 uv = aUV + tessCoord.x * aDUVDX + tessCoord.y * aDUVDY;
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);
vColor = color;
vUV = uv;
vSignMode = aSignMode;
}

View File

@ -1,36 +0,0 @@
// pathfinder/shaders/gles2/ssaa-subpixel-resolve.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Performs subpixel antialiasing for LCD screens by converting a
//! 3x-oversampled RGBA color buffer to an RGB framebuffer, applying the
//! FreeType color defringing filter as necessary.
precision mediump float;
/// The alpha coverage texture.
uniform sampler2D uSource;
/// The dimensions of the alpha coverage texture, in texels.
uniform ivec2 uSourceDimensions;
uniform vec4 uKernel;
varying vec2 vTexCoord;
void main() {
float onePixel = 1.0 / float(uSourceDimensions.x);
vec4 shadesL, shadesR;
float shadeC;
sample9Tap(shadesL, shadeC, shadesR, uSource, vTexCoord, onePixel, uKernel);
vec3 shades = vec3(convolve7Tap(shadesL, vec3(shadeC, shadesR.xy), uKernel),
convolve7Tap(vec4(shadesL.yzw, shadeC), shadesR.xyz, uKernel),
convolve7Tap(vec4(shadesL.zw, shadeC, shadesR.x), shadesR.yzw, uKernel));
gl_FragColor = vec4(shades, 1.0);
}

View File

@ -1,122 +0,0 @@
// pathfinder/shaders/gles2/stencil-aaa.vs.glsl
//
// Copyright (c) 2018 The Pathfinder Project Developers.
//
// 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.
uniform vec4 uTransformST;
uniform vec2 uTransformExt;
uniform ivec2 uFramebufferSize;
/// Vertical snapping positions.
uniform vec4 uHints;
uniform vec2 uEmboldenAmount;
uniform ivec2 uPathBoundsDimensions;
uniform sampler2D uPathBounds;
uniform ivec2 uPathTransformSTDimensions;
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
uniform int uSide;
attribute vec2 aTessCoord;
attribute vec2 aFromPosition;
attribute vec2 aCtrlPosition;
attribute vec2 aToPosition;
attribute vec2 aFromNormal;
attribute vec2 aCtrlNormal;
attribute vec2 aToNormal;
attribute float aPathID;
varying vec2 vFrom;
varying vec2 vCtrl;
varying vec2 vTo;
void main() {
// Unpack.
vec2 emboldenAmount = uEmboldenAmount * 0.5;
int pathID = int(aPathID);
// Hint positions.
vec2 from = hintPosition(aFromPosition, uHints);
vec2 ctrl = hintPosition(aCtrlPosition, uHints);
vec2 to = hintPosition(aToPosition, uHints);
// Embolden as necessary.
from -= aFromNormal * emboldenAmount;
ctrl -= aCtrlNormal * emboldenAmount;
to -= aToNormal * emboldenAmount;
// Fetch transform.
vec2 transformExt;
vec4 transformST = fetchPathAffineTransform(transformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
// Concatenate transforms.
mat2 globalTransformLinear = mat2(uTransformST.x, uTransformExt, uTransformST.y);
mat2 localTransformLinear = mat2(transformST.x, -transformExt, transformST.y);
mat2 transformLinear = globalTransformLinear * localTransformLinear;
// Perform the linear component of the transform (everything but translation).
from = transformLinear * from;
ctrl = transformLinear * ctrl;
to = transformLinear * to;
// Choose correct quadrant for rotation.
vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions);
vec2 fillVector = transformLinear * vec2(0.0, 1.0);
vec2 corner = transformLinear * vec2(fillVector.x < 0.0 ? bounds.z : bounds.x,
fillVector.y < 0.0 ? bounds.y : bounds.w);
// Compute edge vectors. De Casteljau subdivide if necessary.
vec2 v01 = ctrl - from, v12 = to - ctrl;
float t = clamp(v01.x / (v01.x - v12.x), 0.0, 1.0);
vec2 ctrl0 = mix(from, ctrl, t), ctrl1 = mix(ctrl, to, t);
vec2 mid = mix(ctrl0, ctrl1, t);
if (uSide == 0) {
from = mid;
ctrl = ctrl1;
} else {
ctrl = ctrl0;
to = mid;
}
// Compute position and dilate. If too thin, discard to avoid artefacts.
vec2 dilation, position;
bool zeroArea = abs(from.x - to.x) < 0.00001;
if (aTessCoord.x < 0.5) {
position.x = min(min(from.x, to.x), ctrl.x);
dilation.x = zeroArea ? 0.0 : -1.0;
} else {
position.x = max(max(from.x, to.x), ctrl.x);
dilation.x = zeroArea ? 0.0 : 1.0;
}
if (aTessCoord.y < 0.5) {
position.y = min(min(from.y, to.y), ctrl.y);
dilation.y = zeroArea ? 0.0 : -1.0;
} else {
position.y = corner.y;
dilation.y = 0.0;
}
position += dilation * 2.0 / vec2(uFramebufferSize);
// Compute final position and depth.
vec2 offsetPosition = position + uTransformST.zw + globalTransformLinear * transformST.zw;
float depth = convertPathIndexToViewportDepthValue(pathID);
// Compute transformed framebuffer size.
vec2 framebufferSizeVector = 0.5 * vec2(uFramebufferSize);
// Finish up.
gl_Position = vec4(offsetPosition, depth, 1.0);
vFrom = (from - position) * framebufferSizeVector;
vCtrl = (ctrl - position) * framebufferSizeVector;
vTo = (to - position) * framebufferSizeVector;
}

View File

@ -1,27 +0,0 @@
// pathfinder/shaders/gles2/xcaa-mono-resolve.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders a single-channel alpha coverage buffer to an RGB framebuffer.
precision mediump float;
/// The background color of the monochrome path.
uniform vec4 uBGColor;
/// The foreground color of the monochrome path.
uniform vec4 uFGColor;
/// The alpha coverage texture.
uniform sampler2D uAAAlpha;
varying vec2 vTexCoord;
void main() {
float alpha = abs(texture2D(uAAAlpha, vTexCoord).r);
gl_FragColor = mix(uBGColor, uFGColor, alpha);
}

View File

@ -1,30 +0,0 @@
// pathfinder/shaders/gles2/xcaa-mono-resolve.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Renders a single-channel alpha coverage buffer to an RGB framebuffer.
precision highp float;
/// A dilation (scale and transform) to be applied to the quad.
uniform vec4 uTransformST;
/// A fixed pair of factors to be applied to the texture coordinates.
uniform vec2 uTexScale;
/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0).
attribute vec2 aPosition;
/// The texture coordinates: (0.0, 0.0) to (1.0, 1.0).
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = vec4(transformVertexPositionST(aPosition, uTransformST), -1.0, 1.0);
vTexCoord = aTexCoord * uTexScale;
}

View File

@ -1,42 +0,0 @@
// pathfinder/shaders/gles2/xcaa-mono-subpixel-resolve.fs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Performs subpixel antialiasing for LCD screens by converting a
//! 3x-oversampled single-channel color buffer to an RGB framebuffer, applying
//! the FreeType color defringing filter as necessary.
precision mediump float;
/// The background color of the monochrome path.
uniform vec4 uBGColor;
/// The foreground color of the monochrome path.
uniform vec4 uFGColor;
/// The alpha coverage texture.
uniform sampler2D uAAAlpha;
/// The dimensions of the alpha coverage texture, in texels.
uniform ivec2 uAAAlphaDimensions;
uniform vec4 uKernel;
varying vec2 vTexCoord;
void main() {
float onePixel = 1.0 / float(uAAAlphaDimensions.x);
vec4 shadesL, shadesR;
float shadeC;
sample9Tap(shadesL, shadeC, shadesR, uAAAlpha, vTexCoord, onePixel, uKernel);
vec3 shades = vec3(convolve7Tap(shadesL, vec3(shadeC, shadesR.xy), uKernel),
convolve7Tap(vec4(shadesL.yzw, shadeC), shadesR.xyz, uKernel),
convolve7Tap(vec4(shadesL.zw, shadeC, shadesR.x), shadesR.yzw, uKernel));
vec3 color = mix(uBGColor.rgb, uFGColor.rgb, shades);
float alpha = any(greaterThan(shades, vec3(0.0))) ? uFGColor.a : uBGColor.a;
gl_FragColor = alpha * vec4(color, 1.0);
}

View File

@ -1,32 +0,0 @@
// pathfinder/shaders/gles2/xcaa-mono-subpixel-resolve.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// 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.
//! Performs subpixel antialiasing for LCD screens by converting a
//! 3x-oversampled single-channel color buffer to an RGB framebuffer, applying
//! the FreeType color defringing filter as necessary.
precision highp float;
/// A dilation (scale and transform) to be applied to the quad.
uniform vec4 uTransformST;
/// A fixed pair of factors to be applied to the texture coordinates.
uniform vec2 uTexScale;
/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0).
attribute vec2 aPosition;
/// The texture coordinates: (0.0, 0.0) to (1.0, 1.0).
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = vec4(transformVertexPositionST(aPosition, uTransformST), -1.0, 1.0);
vTexCoord = aTexCoord * uTexScale;
}

14
simd/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "pathfinder_simd"
version = "0.3.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
build = "build.rs"
[features]
pf-no-simd = []
[dependencies]
[build-dependencies]
rustc_version = "0.2"

26
simd/build.rs Normal file
View File

@ -0,0 +1,26 @@
// pathfinder/simd/build.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
extern crate rustc_version;
use rustc_version::Channel;
fn main() {
// Assert we haven't travelled back in time
assert!(rustc_version::version().unwrap().major >= 1);
// Set cfg flags depending on release channel
match rustc_version::version_meta().unwrap().channel {
Channel::Nightly => {
println!("cargo:rustc-cfg=pf_rustc_nightly");
}
_ => {}
}
}

422
simd/src/arm/mod.rs Normal file
View File

@ -0,0 +1,422 @@
// pathfinder/simd/src/arm.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 std::arch::aarch64::{self, float32x4_t, int32x4_t, uint32x4_t, uint64x2_t, uint8x16_t};
use std::arch::aarch64::{uint8x8x2_t, uint8x8_t};
use std::f32;
use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::ops::{Add, Index, IndexMut, Mul, Sub};
mod swizzle_f32x4;
mod swizzle_i32x4;
// 32-bit floats
#[derive(Clone, Copy)]
pub struct F32x4(pub float32x4_t);
impl F32x4 {
#[inline]
pub fn new(a: f32, b: f32, c: f32, d: f32) -> F32x4 {
unsafe { F32x4(mem::transmute([a, b, c, d])) }
}
#[inline]
pub fn splat(x: f32) -> F32x4 {
F32x4::new(x, x, x, x)
}
// Basic operations
#[inline]
pub fn approx_recip(self) -> F32x4 {
unsafe { F32x4(vrecpe_v4f32(self.0)) }
}
#[inline]
pub fn min(self, other: F32x4) -> F32x4 {
unsafe { F32x4(simd_fmin(self.0, other.0)) }
}
#[inline]
pub fn max(self, other: F32x4) -> F32x4 {
unsafe { F32x4(simd_fmax(self.0, other.0)) }
}
#[inline]
pub fn clamp(self, min: F32x4, max: F32x4) -> F32x4 {
self.max(min).min(max)
}
#[inline]
pub fn abs(self) -> F32x4 {
unsafe { F32x4(fabs_v4f32(self.0)) }
}
#[inline]
pub fn floor(self) -> F32x4 {
unsafe { F32x4(floor_v4f32(self.0)) }
}
#[inline]
pub fn ceil(self) -> F32x4 {
unsafe { F32x4(ceil_v4f32(self.0)) }
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: F32x4) -> U32x4 {
unsafe { U32x4(simd_eq(self.0, other.0)) }
}
#[inline]
pub fn packed_gt(self, other: F32x4) -> U32x4 {
unsafe { U32x4(simd_gt(self.0, other.0)) }
}
#[inline]
pub fn packed_le(self, other: F32x4) -> U32x4 {
unsafe { U32x4(simd_le(self.0, other.0)) }
}
#[inline]
pub fn packed_lt(self, other: F32x4) -> U32x4 {
unsafe { U32x4(simd_lt(self.0, other.0)) }
}
// Converts these packed floats to integers.
#[inline]
pub fn to_i32x4(self) -> I32x4 {
unsafe { I32x4(simd_cast(self.0)) }
}
// Concatenations
#[inline]
pub fn concat_xy_xy(self, other: F32x4) -> F32x4 {
unsafe { F32x4(simd_shuffle4(self.0, other.0, [0, 1, 4, 5])) }
}
#[inline]
pub fn concat_xy_zw(self, other: F32x4) -> F32x4 {
unsafe { F32x4(simd_shuffle4(self.0, other.0, [0, 1, 6, 7])) }
}
#[inline]
pub fn concat_zw_zw(self, other: F32x4) -> F32x4 {
unsafe { F32x4(simd_shuffle4(self.0, other.0, [2, 3, 6, 7])) }
}
#[inline]
pub fn concat_wz_yx(self, other: F32x4) -> F32x4 {
unsafe { F32x4(simd_shuffle4(self.0, other.0, [3, 2, 5, 4])) }
}
#[inline]
pub fn cross(&self, other: F32x4) -> F32x4 {
unimplemented!()
}
}
impl Default for F32x4 {
#[inline]
fn default() -> F32x4 {
F32x4::new(0.0, 0.0, 0.0, 0.0)
}
}
impl Index<usize> for F32x4 {
type Output = f32;
#[inline]
fn index(&self, index: usize) -> &f32 {
unsafe {
assert!(index < 4);
let ptr = &self.0 as *const float32x4_t as *const f32;
mem::transmute::<*const f32, &f32>(ptr.offset(index as isize))
}
}
}
impl IndexMut<usize> for F32x4 {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut f32 {
unsafe {
assert!(index < 4);
let ptr = &mut self.0 as *mut float32x4_t as *mut f32;
mem::transmute::<*mut f32, &mut f32>(ptr.offset(index as isize))
}
}
}
impl Debug for F32x4 {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}, {}, {}, {}>", self[0], self[1], self[2], self[3])
}
}
impl PartialEq for F32x4 {
#[inline]
fn eq(&self, other: &F32x4) -> bool {
self.packed_eq(*other).is_all_ones()
}
}
impl Add<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn add(self, other: F32x4) -> F32x4 {
unsafe {
F32x4(simd_add(self.0, other.0))
}
}
}
impl Mul<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn mul(self, other: F32x4) -> F32x4 {
unsafe {
F32x4(simd_mul(self.0, other.0))
}
}
}
impl Sub<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn sub(self, other: F32x4) -> F32x4 {
unsafe {
F32x4(simd_sub(self.0, other.0))
}
}
}
// 32-bit signed integers
#[derive(Clone, Copy, Debug)]
pub struct I32x4(pub int32x4_t);
impl I32x4 {
#[inline]
pub fn new(a: i32, b: i32, c: i32, d: i32) -> I32x4 {
unsafe { I32x4(mem::transmute([a, b, c, d])) }
}
#[inline]
pub fn splat(x: i32) -> I32x4 {
I32x4::new(x, x, x, x)
}
#[inline]
pub fn as_u8x16(self) -> U8x16 {
unsafe { U8x16(*mem::transmute::<&int32x4_t, &uint8x16_t>(&self.0)) }
}
#[inline]
pub fn min(self, other: I32x4) -> I32x4 {
unsafe { I32x4(simd_fmin(self.0, other.0)) }
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: I32x4) -> U32x4 {
unsafe { U32x4(simd_eq(self.0, other.0)) }
}
#[inline]
pub fn packed_le(self, other: I32x4) -> U32x4 {
unsafe { U32x4(simd_le(self.0, other.0)) }
}
// Concatenations
#[inline]
pub fn concat_xy_xy(self, other: I32x4) -> I32x4 {
unsafe { I32x4(simd_shuffle4(self.0, other.0, [0, 1, 4, 5])) }
}
// Conversions
/// Converts these packed integers to floats.
#[inline]
pub fn to_f32x4(self) -> F32x4 {
unsafe { F32x4(simd_cast(self.0)) }
}
}
impl Default for I32x4 {
#[inline]
fn default() -> I32x4 {
I32x4::new(0, 0, 0, 0)
}
}
impl Index<usize> for I32x4 {
type Output = i32;
#[inline]
fn index(&self, index: usize) -> &i32 {
unsafe {
assert!(index < 4);
let ptr = &self.0 as *const int32x4_t as *const i32;
mem::transmute::<*const i32, &i32>(ptr.offset(index as isize))
}
}
}
impl IndexMut<usize> for I32x4 {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut i32 {
unsafe {
assert!(index < 4);
let ptr = &mut self.0 as *mut int32x4_t as *mut i32;
mem::transmute::<*mut i32, &mut i32>(ptr.offset(index as isize))
}
}
}
impl Add<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn add(self, other: I32x4) -> I32x4 {
unsafe { I32x4(simd_add(self.0, other.0)) }
}
}
impl Sub<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn sub(self, other: I32x4) -> I32x4 {
unsafe { I32x4(simd_sub(self.0, other.0)) }
}
}
impl Mul<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn mul(self, other: I32x4) -> I32x4 {
unsafe { I32x4(simd_mul(self.0, other.0)) }
}
}
impl PartialEq for I32x4 {
#[inline]
fn eq(&self, other: &I32x4) -> bool {
self.packed_eq(*other).is_all_ones()
}
}
// 32-bit unsigned integers
#[derive(Clone, Copy)]
pub struct U32x4(pub uint32x4_t);
impl U32x4 {
#[inline]
pub fn is_all_ones(&self) -> bool {
unsafe { aarch64::vminvq_u32(self.0) == !0 }
}
#[inline]
pub fn is_all_zeroes(&self) -> bool {
unsafe { aarch64::vmaxvq_u32(self.0) == 0 }
}
}
impl Index<usize> for U32x4 {
type Output = u32;
#[inline]
fn index(&self, index: usize) -> &u32 {
unsafe {
assert!(index < 4);
let ptr = &self.0 as *const uint32x4_t as *const u32;
mem::transmute::<*const u32, &u32>(ptr.offset(index as isize))
}
}
}
// 8-bit unsigned integers
#[derive(Clone, Copy)]
pub struct U8x16(pub uint8x16_t);
impl U8x16 {
#[inline]
pub fn as_i32x4(self) -> I32x4 {
unsafe {
I32x4(*mem::transmute::<&uint8x16_t, &int32x4_t>(&self.0))
}
}
#[inline]
pub fn shuffle(self, indices: U8x16) -> U8x16 {
unsafe {
let table = mem::transmute::<uint8x16_t, uint8x8x2_t>(self.0);
let low = aarch64::vtbl2_u8(table, indices.extract_low());
let high = aarch64::vtbl2_u8(table, indices.extract_high());
U8x16(aarch64::vcombine_u8(low, high))
}
}
#[inline]
fn extract_low(self) -> uint8x8_t {
unsafe {
let low = simd_extract(mem::transmute::<uint8x16_t, uint64x2_t>(self.0), 0);
mem::transmute::<u64, uint8x8_t>(low)
}
}
#[inline]
fn extract_high(self) -> uint8x8_t {
unsafe {
let high = simd_extract(mem::transmute::<uint8x16_t, uint64x2_t>(self.0), 1);
mem::transmute::<u64, uint8x8_t>(high)
}
}
}
// Intrinsics
extern "platform-intrinsic" {
fn simd_add<T>(x: T, y: T) -> T;
fn simd_mul<T>(x: T, y: T) -> T;
fn simd_sub<T>(x: T, y: T) -> T;
fn simd_fmin<T>(x: T, y: T) -> T;
fn simd_fmax<T>(x: T, y: T) -> T;
fn simd_eq<T, U>(x: T, y: T) -> U;
fn simd_gt<T, U>(x: T, y: T) -> U;
fn simd_le<T, U>(x: T, y: T) -> U;
fn simd_lt<T, U>(x: T, y: T) -> U;
fn simd_shuffle4<T, U>(x: T, y: T, idx: [u32; 4]) -> U;
fn simd_cast<T, U>(x: T) -> U;
fn simd_insert<T, U>(x: T, index: u32, value: U) -> T;
fn simd_extract<T, U>(x: T, index: u32) -> U;
}
extern "C" {
#[link_name = "llvm.fabs.v4f32"]
fn fabs_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.floor.v4f32"]
fn floor_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.ceil.v4f32"]
fn ceil_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.aarch64.neon.frecpe.v4f32"]
fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

137
simd/src/extras.rs Normal file
View File

@ -0,0 +1,137 @@
// pathfinder/simd/src/extras.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::default::{F32x4, I32x4};
use std::ops::{AddAssign, MulAssign, Neg, SubAssign};
// 32-bit floats
impl F32x4 {
// Constructors
#[inline]
pub fn from_slice(slice: &[f32]) -> F32x4 {
F32x4::new(slice[0], slice[1], slice[2], slice[3])
}
// Accessors
#[inline]
pub fn x(self) -> f32 {
self[0]
}
#[inline]
pub fn y(self) -> f32 {
self[1]
}
#[inline]
pub fn z(self) -> f32 {
self[2]
}
#[inline]
pub fn w(self) -> f32 {
self[3]
}
// Mutators
#[inline]
pub fn set_x(&mut self, x: f32) {
self[0] = x
}
#[inline]
pub fn set_y(&mut self, y: f32) {
self[1] = y
}
#[inline]
pub fn set_z(&mut self, z: f32) {
self[2] = z
}
#[inline]
pub fn set_w(&mut self, w: f32) {
self[3] = w
}
// Comparisons
#[inline]
pub fn approx_eq(self, other: F32x4, epsilon: f32) -> bool {
(self - other)
.abs()
.packed_gt(F32x4::splat(epsilon))
.is_all_zeroes()
}
}
impl AddAssign for F32x4 {
#[inline]
fn add_assign(&mut self, other: F32x4) {
*self = *self + other
}
}
impl SubAssign for F32x4 {
#[inline]
fn sub_assign(&mut self, other: F32x4) {
*self = *self - other
}
}
impl MulAssign for F32x4 {
#[inline]
fn mul_assign(&mut self, other: F32x4) {
*self = *self * other
}
}
impl Neg for F32x4 {
type Output = F32x4;
#[inline]
fn neg(self) -> F32x4 {
F32x4::default() - self
}
}
// 32-bit integers
impl AddAssign for I32x4 {
#[inline]
fn add_assign(&mut self, other: I32x4) {
*self = *self + other
}
}
impl SubAssign for I32x4 {
#[inline]
fn sub_assign(&mut self, other: I32x4) {
*self = *self - other
}
}
impl MulAssign for I32x4 {
#[inline]
fn mul_assign(&mut self, other: I32x4) {
*self = *self * other
}
}
impl Neg for I32x4 {
type Output = I32x4;
#[inline]
fn neg(self) -> I32x4 {
I32x4::default() - self
}
}

34
simd/src/lib.rs Normal file
View File

@ -0,0 +1,34 @@
// pathfinder/simd/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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(pf_rustc_nightly, feature(link_llvm_intrinsics, platform_intrinsics))]
#![cfg_attr(pf_rustc_nightly, feature(simd_ffi, stdsimd))]
//! A minimal SIMD abstraction, usable outside of Pathfinder.
#[cfg(any(feature = "pf-no-simd",
not(any(target_arch = "x86",
target_arch = "x86_64",
all(pf_rustc_nightly, target_arch = "aarch64")))))]
pub use crate::scalar as default;
#[cfg(all(not(feature = "pf-no-simd"), pf_rustc_nightly, target_arch = "aarch64"))]
pub use crate::arm as default;
#[cfg(all(not(feature = "pf-no-simd"), any(target_arch = "x86", target_arch = "x86_64")))]
pub use crate::x86 as default;
pub mod scalar;
#[cfg(all(pf_rustc_nightly, target_arch = "aarch64"))]
pub mod arm;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub mod x86;
mod extras;
#[cfg(test)]
mod test;

360
simd/src/scalar/mod.rs Normal file
View File

@ -0,0 +1,360 @@
// pathfinder/simd/src/scalar.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 std::f32;
use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::ops::{Add, Index, IndexMut, Mul, Sub};
mod swizzle_f32x4;
mod swizzle_i32x4;
// 32-bit floats
#[derive(Clone, Copy, Default, PartialEq)]
pub struct F32x4(pub [f32; 4]);
impl F32x4 {
#[inline]
pub fn new(a: f32, b: f32, c: f32, d: f32) -> F32x4 {
F32x4([a, b, c, d])
}
#[inline]
pub fn splat(x: f32) -> F32x4 {
F32x4([x; 4])
}
// Basic operations
#[inline]
pub fn approx_recip(self) -> F32x4 {
F32x4([1.0 / self[0], 1.0 / self[1], 1.0 / self[2], 1.0 / self[3]])
}
#[inline]
pub fn min(self, other: F32x4) -> F32x4 {
F32x4([
self[0].min(other[0]),
self[1].min(other[1]),
self[2].min(other[2]),
self[3].min(other[3]),
])
}
#[inline]
pub fn max(self, other: F32x4) -> F32x4 {
F32x4([
self[0].max(other[0]),
self[1].max(other[1]),
self[2].max(other[2]),
self[3].max(other[3]),
])
}
#[inline]
pub fn clamp(self, min: F32x4, max: F32x4) -> F32x4 {
self.max(min).min(max)
}
#[inline]
pub fn abs(self) -> F32x4 {
F32x4([self[0].abs(), self[1].abs(), self[2].abs(), self[3].abs()])
}
#[inline]
pub fn floor(self) -> F32x4 {
F32x4([self[0].floor(), self[1].floor(), self[2].floor(), self[3].floor()])
}
#[inline]
pub fn ceil(self) -> F32x4 {
F32x4([self[0].ceil(), self[1].ceil(), self[2].ceil(), self[3].ceil()])
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: F32x4) -> U32x4 {
U32x4([
if self[0] == other[0] { !0 } else { 0 },
if self[1] == other[1] { !0 } else { 0 },
if self[2] == other[2] { !0 } else { 0 },
if self[3] == other[3] { !0 } else { 0 },
])
}
#[inline]
pub fn packed_gt(self, other: F32x4) -> U32x4 {
U32x4([
if self[0] > other[0] { !0 } else { 0 },
if self[1] > other[1] { !0 } else { 0 },
if self[2] > other[2] { !0 } else { 0 },
if self[3] > other[3] { !0 } else { 0 },
])
}
#[inline]
pub fn packed_le(self, other: F32x4) -> U32x4 {
U32x4([
if self[0] <= other[0] { !0 } else { 0 },
if self[1] <= other[1] { !0 } else { 0 },
if self[2] <= other[2] { !0 } else { 0 },
if self[3] <= other[3] { !0 } else { 0 },
])
}
#[inline]
pub fn packed_lt(self, other: F32x4) -> U32x4 {
U32x4([
if self[0] < other[0] { !0 } else { 0 },
if self[1] < other[1] { !0 } else { 0 },
if self[2] < other[2] { !0 } else { 0 },
if self[3] < other[3] { !0 } else { 0 },
])
}
// Converts these packed floats to integers.
#[inline]
pub fn to_i32x4(self) -> I32x4 {
I32x4([self[0] as i32, self[1] as i32, self[2] as i32, self[3] as i32])
}
// Concatenations
#[inline]
pub fn concat_xy_xy(self, other: F32x4) -> F32x4 {
F32x4([self[0], self[1], other[0], other[1]])
}
#[inline]
pub fn concat_xy_zw(self, other: F32x4) -> F32x4 {
F32x4([self[0], self[1], other[2], other[3]])
}
#[inline]
pub fn concat_zw_zw(self, other: F32x4) -> F32x4 {
F32x4([self[2], self[3], other[2], other[3]])
}
#[inline]
pub fn concat_wz_yx(self, other: F32x4) -> F32x4 {
F32x4([self[3], self[2], other[1], other[0]])
}
#[inline]
pub fn cross(&self, other: F32x4) -> F32x4 {
unimplemented!()
}
}
impl Index<usize> for F32x4 {
type Output = f32;
#[inline]
fn index(&self, index: usize) -> &f32 {
&self.0[index]
}
}
impl IndexMut<usize> for F32x4 {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut f32 {
&mut self.0[index]
}
}
impl Debug for F32x4 {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}, {}, {}, {}>", self[0], self[1], self[2], self[3])
}
}
impl Add<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn add(self, other: F32x4) -> F32x4 {
F32x4([self[0] + other[0], self[1] + other[1], self[2] + other[2], self[3] + other[3]])
}
}
impl Mul<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn mul(self, other: F32x4) -> F32x4 {
F32x4([self[0] * other[0], self[1] * other[1], self[2] * other[2], self[3] * other[3]])
}
}
impl Sub<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn sub(self, other: F32x4) -> F32x4 {
F32x4([self[0] - other[0], self[1] - other[1], self[2] - other[2], self[3] - other[3]])
}
}
// 32-bit signed integers
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub struct I32x4([i32; 4]);
impl I32x4 {
#[inline]
pub fn new(a: i32, b: i32, c: i32, d: i32) -> I32x4 {
I32x4([a, b, c, d])
}
#[inline]
pub fn splat(x: i32) -> I32x4 {
I32x4([x; 4])
}
#[inline]
pub fn as_u8x16(self) -> U8x16 {
unsafe {
U8x16(*mem::transmute::<&[i32; 4], &[u8; 16]>(&self.0))
}
}
#[inline]
pub fn min(self, other: I32x4) -> I32x4 {
I32x4([
self[0].min(other[0]),
self[1].min(other[1]),
self[2].min(other[2]),
self[3].min(other[3]),
])
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: I32x4) -> U32x4 {
U32x4([
if self[0] == other[0] { !0 } else { 0 },
if self[1] == other[1] { !0 } else { 0 },
if self[2] == other[2] { !0 } else { 0 },
if self[3] == other[3] { !0 } else { 0 },
])
}
#[inline]
pub fn packed_le(self, other: I32x4) -> U32x4 {
U32x4([
if self[0] <= other[0] { !0 } else { 0 },
if self[1] <= other[1] { !0 } else { 0 },
if self[2] <= other[2] { !0 } else { 0 },
if self[3] <= other[3] { !0 } else { 0 },
])
}
// Concatenations
#[inline]
pub fn concat_xy_xy(self, other: I32x4) -> I32x4 {
I32x4([self[0], self[1], other[0], other[1]])
}
// Conversions
/// Converts these packed integers to floats.
#[inline]
pub fn to_f32x4(self) -> F32x4 {
F32x4([self[0] as f32, self[1] as f32, self[2] as f32, self[3] as f32])
}
}
impl Index<usize> for I32x4 {
type Output = i32;
#[inline]
fn index(&self, index: usize) -> &i32 {
&self.0[index]
}
}
impl IndexMut<usize> for I32x4 {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut i32 {
&mut self.0[index]
}
}
impl Add<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn add(self, other: I32x4) -> I32x4 {
I32x4([self[0] + other[0], self[1] + other[1], self[2] + other[2], self[3] + other[3]])
}
}
impl Sub<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn sub(self, other: I32x4) -> I32x4 {
I32x4([self[0] - other[0], self[1] - other[1], self[2] - other[2], self[3] - other[3]])
}
}
impl Mul<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn mul(self, other: I32x4) -> I32x4 {
I32x4([self[0] * other[0], self[1] * other[1], self[2] * other[2], self[3] * other[3]])
}
}
// 32-bit unsigned integers
#[derive(Clone, Copy)]
pub struct U32x4(pub [u32; 4]);
impl U32x4 {
#[inline]
pub fn is_all_ones(&self) -> bool {
self[0] == !0 && self[1] == !0 && self[2] == !0 && self[3] == !0
}
#[inline]
pub fn is_all_zeroes(&self) -> bool {
self[0] == 0 && self[1] == 0 && self[2] == 0 && self[3] == 0
}
}
impl Index<usize> for U32x4 {
type Output = u32;
#[inline]
fn index(&self, index: usize) -> &u32 {
&self.0[index]
}
}
// 8-bit unsigned integers
#[derive(Clone, Copy)]
pub struct U8x16([u8; 16]);
impl U8x16 {
#[inline]
pub fn as_i32x4(self) -> I32x4 {
unsafe {
I32x4(*mem::transmute::<&[u8; 16], &[i32; 4]>(&self.0))
}
}
#[inline]
pub fn shuffle(self, indices: U8x16) -> U8x16 {
let mut result = [0; 16];
for index in 0..16 {
result[index] = self.0[(indices.0[index] & 0x0f) as usize]
}
U8x16(result)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

137
simd/src/test.rs Normal file
View File

@ -0,0 +1,137 @@
// pathfinder/simd/src/test.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::default::{F32x4, I32x4, U32x4};
// F32x4
#[test]
fn test_f32x4_constructors() {
let a = F32x4::new(1.0, 2.0, 3.0, 4.0);
assert_eq!((a[0], a[1], a[2], a[3]), (1.0, 2.0, 3.0, 4.0));
let b = F32x4::splat(10.0);
assert_eq!(b, F32x4::new(10.0, 10.0, 10.0, 10.0));
}
#[test]
fn test_f32x4_accessors_and_mutators() {
let a = F32x4::new(5.0, 6.0, 7.0, 8.0);
assert_eq!((a.x(), a.y(), a.z(), a.w()), (5.0, 6.0, 7.0, 8.0));
let mut b = F32x4::new(10.0, 11.0, 12.0, 13.0);
b.set_x(20.0);
b.set_y(30.0);
b.set_z(40.0);
b.set_w(50.0);
assert_eq!(b, F32x4::new(20.0, 30.0, 40.0, 50.0));
}
#[test]
fn test_f32x4_basic_ops() {
let a = F32x4::new(1.0, 3.0, 5.0, 7.0);
let b = F32x4::new(2.0, 2.0, 6.0, 6.0);
assert_eq!(a.min(b), F32x4::new(1.0, 2.0, 5.0, 6.0));
assert_eq!(a.max(b), F32x4::new(2.0, 3.0, 6.0, 7.0));
let c = F32x4::new(-1.0, 1.0, -20.0, 3.0);
assert_eq!(c.abs(), F32x4::new(1.0, 1.0, 20.0, 3.0));
}
#[test]
fn test_f32x4_packed_comparisons() {
let a = F32x4::new(7.0, 3.0, 6.0, -2.0);
let b = F32x4::new(10.0, 3.0, 5.0, -2.0);
assert_eq!(a.packed_eq(b), U32x4::new(0, !0, 0, !0));
}
// TODO(pcwalton): This should test them all!
#[test]
fn test_f32x4_swizzles() {
let a = F32x4::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(a.xxxx(), F32x4::splat(1.0));
assert_eq!(a.yyyy(), F32x4::splat(2.0));
assert_eq!(a.zzzz(), F32x4::splat(3.0));
assert_eq!(a.wwww(), F32x4::splat(4.0));
assert_eq!(a.xyxy(), F32x4::new(1.0, 2.0, 1.0, 2.0));
assert_eq!(a.yzzy(), F32x4::new(2.0, 3.0, 3.0, 2.0));
assert_eq!(a.wzyx(), F32x4::new(4.0, 3.0, 2.0, 1.0));
assert_eq!(a.ywzx(), F32x4::new(2.0, 4.0, 3.0, 1.0));
assert_eq!(a.wzwz(), F32x4::new(4.0, 3.0, 4.0, 3.0));
}
#[test]
fn test_f32x4_concatenations() {
let a = F32x4::new(4.0, 2.0, 6.0, -1.0);
let b = F32x4::new(10.0, -3.0, 15.0, 41.0);
assert_eq!(a.concat_xy_xy(b), F32x4::new( 4.0, 2.0, 10.0, -3.0));
assert_eq!(a.concat_xy_zw(b), F32x4::new( 4.0, 2.0, 15.0, 41.0));
assert_eq!(a.concat_zw_zw(b), F32x4::new( 6.0, -1.0, 15.0, 41.0));
assert_eq!(a.concat_wz_yx(b), F32x4::new(-1.0, 6.0, -3.0, 10.0));
}
#[test]
fn test_f32x4_arithmetic_overloads() {
let a = F32x4::new(4.0, -1.0, 6.0, -32.0);
let b = F32x4::new(0.5, 0.5, 10.0, 3.0);
let a_plus_b = F32x4::new(4.5, -0.5, 16.0, -29.0);
let a_minus_b = F32x4::new(3.5, -1.5, -4.0, -35.0);
let a_times_b = F32x4::new(2.0, -0.5, 60.0, -96.0);
assert_eq!(a + b, a_plus_b);
assert_eq!(a - b, a_minus_b);
assert_eq!(a * b, a_times_b);
let mut c = a;
c += b;
assert_eq!(c, a_plus_b);
c = a;
c -= b;
assert_eq!(c, a_minus_b);
c = a;
c *= b;
assert_eq!(c, a_times_b);
assert_eq!(-a, F32x4::new(-4.0, 1.0, -6.0, 32.0));
}
#[test]
fn test_f32x4_index_overloads() {
let mut a = F32x4::new(4.0, 1.0, -32.5, 75.0);
assert_eq!(a[2], -32.5);
a[3] = 300.0;
assert_eq!(a[3], 300.0);
a[0] *= 0.5;
assert_eq!(a[0], 2.0);
}
#[test]
fn test_f32x4_conversions() {
let a = F32x4::new(48.0, -4.0, 200.0, 7.0);
assert_eq!(a.to_i32x4(), I32x4::new(48, -4, 200, 7));
}
// I32x4
#[test]
fn test_i32x4_constructors() {
let a = I32x4::new(3, 58, 10, 4);
assert_eq!((a[0], a[1], a[2], a[3]), (3, 58, 10, 4));
let b = I32x4::splat(39);
assert_eq!(b, I32x4::new(39, 39, 39, 39));
}
#[test]
fn test_i32x4_basic_ops() {
let a = I32x4::new(6, 29, -40, 2 );
let b = I32x4::new(10, -5, 10, 46);
assert_eq!(a.min(b), I32x4::new(6, -5, -40, 2));
}
#[test]
fn test_i32x4_packed_comparisons() {
let a = I32x4::new( 59, 1, 5, 63 );
let b = I32x4::new(-59, 1, 5, 104);
assert_eq!(a.packed_eq(b), U32x4::new(0, !0, !0, 0));
}

454
simd/src/x86/mod.rs Normal file
View File

@ -0,0 +1,454 @@
// pathfinder/simd/src/x86.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 std::arch::x86_64::{self, __m128, __m128i};
use std::cmp::PartialEq;
use std::fmt::{self, Debug, Formatter};
use std::mem;
use std::ops::{Add, BitXor, Index, IndexMut, Mul, Not, Sub};
mod swizzle_f32x4;
mod swizzle_i32x4;
// 32-bit floats
#[derive(Clone, Copy)]
pub struct F32x4(pub __m128);
impl F32x4 {
// Constructors
#[inline]
pub fn new(a: f32, b: f32, c: f32, d: f32) -> F32x4 {
unsafe {
let vector = [a, b, c, d];
F32x4(x86_64::_mm_loadu_ps(vector.as_ptr()))
}
}
#[inline]
pub fn splat(x: f32) -> F32x4 {
unsafe { F32x4(x86_64::_mm_set1_ps(x)) }
}
// Basic operations
#[inline]
pub fn approx_recip(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_rcp_ps(self.0)) }
}
#[inline]
pub fn min(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_min_ps(self.0, other.0)) }
}
#[inline]
pub fn max(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_max_ps(self.0, other.0)) }
}
#[inline]
pub fn clamp(self, min: F32x4, max: F32x4) -> F32x4 {
self.max(min).min(max)
}
#[inline]
pub fn abs(self) -> F32x4 {
unsafe {
let tmp = x86_64::_mm_srli_epi32(I32x4::splat(-1).0, 1);
F32x4(x86_64::_mm_and_ps(x86_64::_mm_castsi128_ps(tmp), self.0))
}
}
#[inline]
pub fn floor(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_floor_ps(self.0)) }
}
#[inline]
pub fn ceil(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_ceil_ps(self.0)) }
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: F32x4) -> U32x4 {
unsafe {
U32x4(x86_64::_mm_castps_si128(x86_64::_mm_cmpeq_ps(
self.0, other.0,
)))
}
}
#[inline]
pub fn packed_gt(self, other: F32x4) -> U32x4 {
unsafe {
U32x4(x86_64::_mm_castps_si128(x86_64::_mm_cmpgt_ps(
self.0, other.0,
)))
}
}
#[inline]
pub fn packed_lt(self, other: F32x4) -> U32x4 {
other.packed_gt(self)
}
#[inline]
pub fn packed_le(self, other: F32x4) -> U32x4 {
!self.packed_gt(other)
}
// Conversions
/// Converts these packed floats to integers.
#[inline]
pub fn to_i32x4(self) -> I32x4 {
unsafe { I32x4(x86_64::_mm_cvtps_epi32(self.0)) }
}
// Concatenations
#[inline]
pub fn concat_xy_xy(self, other: F32x4) -> F32x4 {
unsafe {
let this = x86_64::_mm_castps_pd(self.0);
let other = x86_64::_mm_castps_pd(other.0);
let result = x86_64::_mm_unpacklo_pd(this, other);
F32x4(x86_64::_mm_castpd_ps(result))
}
}
#[inline]
pub fn concat_xy_zw(self, other: F32x4) -> F32x4 {
unsafe {
let this = x86_64::_mm_castps_pd(self.0);
let other = x86_64::_mm_castps_pd(other.0);
let result = x86_64::_mm_shuffle_pd(this, other, 0b10);
F32x4(x86_64::_mm_castpd_ps(result))
}
}
#[inline]
pub fn concat_zw_zw(self, other: F32x4) -> F32x4 {
unsafe {
let this = x86_64::_mm_castps_pd(self.0);
let other = x86_64::_mm_castps_pd(other.0);
let result = x86_64::_mm_unpackhi_pd(this, other);
F32x4(x86_64::_mm_castpd_ps(result))
}
}
#[inline]
pub fn concat_wz_yx(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, other.0, 0b0001_1011)) }
}
// FIXME(pcwalton): Move to `Point3DF32`!
#[inline]
pub fn cross(&self, other: F32x4) -> F32x4 {
self.yzxw() * other.zxyw() - self.zxyw() * other.yzxw()
}
}
impl Default for F32x4 {
#[inline]
fn default() -> F32x4 {
unsafe { F32x4(x86_64::_mm_setzero_ps()) }
}
}
impl Index<usize> for F32x4 {
type Output = f32;
#[inline]
fn index(&self, index: usize) -> &f32 {
unsafe { &mem::transmute::<&__m128, &[f32; 4]>(&self.0)[index] }
}
}
impl IndexMut<usize> for F32x4 {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut f32 {
unsafe { &mut mem::transmute::<&mut __m128, &mut [f32; 4]>(&mut self.0)[index] }
}
}
impl Debug for F32x4 {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}, {}, {}, {}>", self[0], self[1], self[2], self[3])
}
}
impl PartialEq for F32x4 {
#[inline]
fn eq(&self, other: &F32x4) -> bool {
self.packed_eq(*other).is_all_ones()
}
}
impl Add<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn add(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_add_ps(self.0, other.0)) }
}
}
impl Mul<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn mul(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_mul_ps(self.0, other.0)) }
}
}
impl Sub<F32x4> for F32x4 {
type Output = F32x4;
#[inline]
fn sub(self, other: F32x4) -> F32x4 {
unsafe { F32x4(x86_64::_mm_sub_ps(self.0, other.0)) }
}
}
// 32-bit signed integers
#[derive(Clone, Copy)]
pub struct I32x4(pub __m128i);
impl I32x4 {
// Constructors
#[inline]
pub fn new(a: i32, b: i32, c: i32, d: i32) -> I32x4 {
unsafe {
let vector = [a, b, c, d];
I32x4(x86_64::_mm_loadu_si128(vector.as_ptr() as *const __m128i))
}
}
#[inline]
pub fn splat(x: i32) -> I32x4 {
unsafe { I32x4(x86_64::_mm_set1_epi32(x)) }
}
// Concatenations
#[inline]
pub fn concat_xy_xy(self, other: I32x4) -> I32x4 {
unsafe {
let this = x86_64::_mm_castsi128_pd(self.0);
let other = x86_64::_mm_castsi128_pd(other.0);
let result = x86_64::_mm_unpacklo_pd(this, other);
I32x4(x86_64::_mm_castpd_si128(result))
}
}
// Conversions
#[inline]
pub fn as_u8x16(self) -> U8x16 {
U8x16(self.0)
}
/// Converts these packed integers to floats.
#[inline]
pub fn to_f32x4(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_cvtepi32_ps(self.0)) }
}
// Basic operations
#[inline]
pub fn min(self, other: I32x4) -> I32x4 {
unsafe { I32x4(x86_64::_mm_min_epi32(self.0, other.0)) }
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: I32x4) -> U32x4 {
unsafe { U32x4(x86_64::_mm_cmpeq_epi32(self.0, other.0)) }
}
// Comparisons
#[inline]
pub fn packed_gt(self, other: I32x4) -> U32x4 {
unsafe {
U32x4(x86_64::_mm_cmpgt_epi32(self.0, other.0))
}
}
#[inline]
pub fn packed_le(self, other: I32x4) -> U32x4 {
!self.packed_gt(other)
}
}
impl Default for I32x4 {
#[inline]
fn default() -> I32x4 {
unsafe { I32x4(x86_64::_mm_setzero_si128()) }
}
}
impl Index<usize> for I32x4 {
type Output = i32;
#[inline]
fn index(&self, index: usize) -> &i32 {
unsafe { &mem::transmute::<&__m128i, &[i32; 4]>(&self.0)[index] }
}
}
impl IndexMut<usize> for I32x4 {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut i32 {
unsafe { &mut mem::transmute::<&mut __m128i, &mut [i32; 4]>(&mut self.0)[index] }
}
}
impl Add<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn add(self, other: I32x4) -> I32x4 {
unsafe { I32x4(x86_64::_mm_add_epi32(self.0, other.0)) }
}
}
impl Sub<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn sub(self, other: I32x4) -> I32x4 {
unsafe { I32x4(x86_64::_mm_sub_epi32(self.0, other.0)) }
}
}
impl Mul<I32x4> for I32x4 {
type Output = I32x4;
#[inline]
fn mul(self, other: I32x4) -> I32x4 {
unsafe { I32x4(x86_64::_mm_mullo_epi32(self.0, other.0)) }
}
}
impl Debug for I32x4 {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}, {}, {}, {}>", self[0], self[1], self[2], self[3])
}
}
impl PartialEq for I32x4 {
#[inline]
fn eq(&self, other: &I32x4) -> bool {
self.packed_eq(*other).is_all_ones()
}
}
// 32-bit unsigned integers
#[derive(Clone, Copy)]
pub struct U32x4(pub __m128i);
impl U32x4 {
// Constructors
#[inline]
pub fn new(a: u32, b: u32, c: u32, d: u32) -> U32x4 {
unsafe {
let vector = [a, b, c, d];
U32x4(x86_64::_mm_loadu_si128(vector.as_ptr() as *const __m128i))
}
}
#[inline]
pub fn splat(x: u32) -> U32x4 {
unsafe { U32x4(x86_64::_mm_set1_epi32(x as i32)) }
}
// Basic operations
#[inline]
pub fn is_all_ones(self) -> bool {
unsafe { x86_64::_mm_test_all_ones(self.0) != 0 }
}
#[inline]
pub fn is_all_zeroes(self) -> bool {
unsafe { x86_64::_mm_test_all_zeros(self.0, self.0) != 0 }
}
// Packed comparisons
#[inline]
pub fn packed_eq(self, other: U32x4) -> U32x4 {
unsafe { U32x4(x86_64::_mm_cmpeq_epi32(self.0, other.0)) }
}
}
impl Debug for U32x4 {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}, {}, {}, {}>", self[0], self[1], self[2], self[3])
}
}
impl Index<usize> for U32x4 {
type Output = u32;
#[inline]
fn index(&self, index: usize) -> &u32 {
unsafe { &mem::transmute::<&__m128i, &[u32; 4]>(&self.0)[index] }
}
}
impl PartialEq for U32x4 {
#[inline]
fn eq(&self, other: &U32x4) -> bool {
self.packed_eq(*other).is_all_ones()
}
}
impl Not for U32x4 {
type Output = U32x4;
#[inline]
fn not(self) -> U32x4 {
self ^ U32x4::splat(!0)
}
}
impl BitXor<U32x4> for U32x4 {
type Output = U32x4;
#[inline]
fn bitxor(self, other: U32x4) -> U32x4 {
unsafe {
U32x4(x86_64::_mm_xor_si128(self.0, other.0))
}
}
}
// 8-bit unsigned integers
#[derive(Clone, Copy)]
pub struct U8x16(pub __m128i);
impl U8x16 {
#[inline]
pub fn as_i32x4(self) -> I32x4 {
I32x4(self.0)
}
#[inline]
pub fn shuffle(self, indices: U8x16) -> U8x16 {
unsafe { U8x16(x86_64::_mm_shuffle_epi8(self.0, indices.0)) }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
// pathfinder/simd/src/x86/swizzle_i32x4.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 crate::x86::I32x4;
use std::arch::x86_64;
// TODO(pcwalton): Add the remaining swizzles.
impl I32x4 {
#[inline]
pub fn xyxy(self) -> I32x4 {
unsafe {
let this = x86_64::_mm_castsi128_ps(self.0);
I32x4(x86_64::_mm_castps_si128(x86_64::_mm_shuffle_ps(this, this, 68)))
}
}
#[inline]
pub fn xwzy(self) -> I32x4 {
unsafe {
let this = x86_64::_mm_castsi128_ps(self.0);
I32x4(x86_64::_mm_castps_si128(x86_64::_mm_shuffle_ps(this, this, 108)))
}
}
#[inline]
pub fn zyxw(self) -> I32x4 {
unsafe {
let this = x86_64::_mm_castsi128_ps(self.0);
I32x4(x86_64::_mm_castps_si128(x86_64::_mm_shuffle_ps(this, this, 198)))
}
}
#[inline]
pub fn zwxy(self) -> I32x4 {
unsafe {
let this = x86_64::_mm_castsi128_ps(self.0);
I32x4(x86_64::_mm_castps_si128(x86_64::_mm_shuffle_ps(this, this, 78)))
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,51 @@
Copyright (C) 2017 Datto Inc. https://www.datto.com/fonts/d-din,
with Reserved Font Names "D-DIN".
Copyright (C) 2017 Datto Inc. https://www.datto.com/fonts/d-din,
with Reserved Font Names "D-DIN Condensed".
Copyright (C) 2017 Datto Inc. https://www.datto.com/fonts/d-din,
with Reserved Font Names "D-DIN Expanded".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

74
site/index.html Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<title>Pathfinder 3</title>
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
<style>
@font-face {
font-family: "D-DIN";
src: url("assets/fonts/D-DIN.woff2") format("woff2");
}
@font-face {
font-family: "D-DIN";
font-weight: bold;
src: url("assets/fonts/D-DIN-Bold.woff2") format("woff2");
}
body {
font-family: "D-DIN", sans-serif;
}
</style>
</head>
<body>
<header class="navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand text-light">Pathfinder</a>
<ul class="navbar-nav">
<li class="nav-item active"><a class="nav-link" href="/">Home</a></li>
<li class="nav-item"><a class="nav-link" href="/doc">Documentation</a></li>
</ul>
<ul class="navbar-nav ml-md-auto">
<li class="nav-item">
<a class="nav-link" href="https://github.com/pcwalton/pathfinder/tree/pf3">Code</a>
</li>
</ul>
</header>
<main id="content" class="py-5">
<div id="masthead" class="container">
<h1 class="font-weight-bold">Pathfinder 3</h1>
<h2>Fast GPU text and vector graphics rendering for your OpenGL app.</h2>
</div>
<div id="features">
<div class="feature">
<h2>Built on Rust</h2>
<div>
Pathfinder is written in Rust, with only the minimum unsafe code necessary to
use SIMD and render with OpenGL. You can use it to render untrusted content
without worrying about security problems or segfaults.
</div>
</div>
<div class="feature">
<h2>Works everywhere</h2>
<div>
Pathfinder runs on any desktop or mobile GPU supporting OpenGL or OpenGL ES 3.0
(roughly, any GPU manufactured after 2002). There's no need for fancy features
like compute, geometry shaders, and tessellation shaders.
</div>
</div>
<div class="feature">
<h2>Easy to use</h2>
<div>
Thanks to the inclusion of the resvg library, a subset of SVG is supported
out-of-the-box. Rendering SVG content to the screen is just a few lines of
code.
</div>
</div>
</div>
<div>
Pathfinder is an open-source Mozilla Research project under the Servo umbrella.
It's also a component of WebRender, the new Web renderer for Firefox. We welcome
contributions from anyone.
</div>
</main>
</body>
</html>

5
site/package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"bootstrap": "^4.3.1"
}
}

15
svg/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "pathfinder_svg"
version = "0.1.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
bitflags = "1.0"
usvg = "0.4"
[dependencies.pathfinder_geometry]
path = "../geometry"
[dependencies.pathfinder_renderer]
path = "../renderer"

371
svg/src/lib.rs Normal file
View File

@ -0,0 +1,371 @@
// pathfinder/svg/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! Converts a subset of SVG to a Pathfinder scene.
#[macro_use]
extern crate bitflags;
use pathfinder_geometry::basic::line_segment::LineSegmentF32;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::{Transform2DF32, Transform2DF32PathIter};
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::Outline;
use pathfinder_geometry::segment::{Segment, SegmentFlags};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::scene::{Paint, PathObject, PathObjectKind, Scene};
use std::fmt::{Display, Formatter, Result as FormatResult};
use std::mem;
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform};
use usvg::{Tree, Visibility};
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
pub struct BuiltSVG {
pub scene: Scene,
pub result_flags: BuildResultFlags,
}
bitflags! {
// NB: If you change this, make sure to update the `Display`
// implementation as well.
pub struct BuildResultFlags: u16 {
const UNSUPPORTED_CLIP_PATH_NODE = 0x0001;
const UNSUPPORTED_DEFS_NODE = 0x0002;
const UNSUPPORTED_FILTER_NODE = 0x0004;
const UNSUPPORTED_IMAGE_NODE = 0x0008;
const UNSUPPORTED_LINEAR_GRADIENT_NODE = 0x0010;
const UNSUPPORTED_MASK_NODE = 0x0020;
const UNSUPPORTED_PATTERN_NODE = 0x0040;
const UNSUPPORTED_RADIAL_GRADIENT_NODE = 0x0080;
const UNSUPPORTED_NESTED_SVG_NODE = 0x0100;
const UNSUPPORTED_TEXT_NODE = 0x0200;
const UNSUPPORTED_LINK_PAINT = 0x0400;
const UNSUPPORTED_CLIP_PATH_ATTR = 0x0800;
const UNSUPPORTED_FILTER_ATTR = 0x1000;
const UNSUPPORTED_MASK_ATTR = 0x2000;
const UNSUPPORTED_OPACITY_ATTR = 0x4000;
}
}
impl BuiltSVG {
// TODO(pcwalton): Allow a global transform to be set.
pub fn from_tree(tree: Tree) -> BuiltSVG {
let global_transform = Transform2DF32::default();
let mut built_svg = BuiltSVG {
scene: Scene::new(),
result_flags: BuildResultFlags::empty(),
};
let root = &tree.root();
match *root.borrow() {
NodeKind::Svg(ref svg) => {
built_svg.scene.view_box = usvg_rect_to_euclid_rect(&svg.view_box.rect);
for kid in root.children() {
built_svg.process_node(&kid, &global_transform);
}
}
_ => unreachable!(),
};
// FIXME(pcwalton): This is needed to avoid stack exhaustion in debug builds when
// recursively dropping reference counts on very large SVGs. :(
mem::forget(tree);
built_svg
}
fn process_node(&mut self, node: &Node, transform: &Transform2DF32) {
let node_transform = usvg_transform_to_transform_2d(&node.transform());
let transform = transform.pre_mul(&node_transform);
match *node.borrow() {
NodeKind::Group(ref group) => {
if group.clip_path.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_CLIP_PATH_ATTR);
}
if group.filter.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_FILTER_ATTR);
}
if group.mask.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_MASK_ATTR);
}
if group.opacity.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_OPACITY_ATTR);
}
for kid in node.children() {
self.process_node(&kid, &transform)
}
}
NodeKind::Path(ref path) if path.visibility == Visibility::Visible => {
if let Some(ref fill) = path.fill {
let style =
self.scene.push_paint(&Paint::from_svg_paint(&fill.paint,
&mut self.result_flags));
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let path = Transform2DF32PathIter::new(path, &transform);
let outline = Outline::from_segments(path);
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
self.scene.objects.push(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Fill,
));
}
if let Some(ref stroke) = path.stroke {
let style =
self.scene.push_paint(&Paint::from_svg_paint(&stroke.paint,
&mut self.result_flags));
let stroke_width =
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let outline = Outline::from_segments(path);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
stroke_to_fill.offset();
let mut outline = stroke_to_fill.outline;
outline.transform(&transform);
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
self.scene.objects.push(PathObject::new(
outline,
style,
node.id().to_string(),
PathObjectKind::Stroke,
));
}
}
NodeKind::Path(..) => {}
NodeKind::ClipPath(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_CLIP_PATH_NODE);
}
NodeKind::Defs { .. } => {
if node.has_children() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_DEFS_NODE);
}
}
NodeKind::Filter(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_FILTER_NODE);
}
NodeKind::Image(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_IMAGE_NODE);
}
NodeKind::LinearGradient(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_LINEAR_GRADIENT_NODE);
}
NodeKind::Mask(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_MASK_NODE);
}
NodeKind::Pattern(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_PATTERN_NODE);
}
NodeKind::RadialGradient(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_RADIAL_GRADIENT_NODE);
}
NodeKind::Svg(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_NESTED_SVG_NODE);
}
NodeKind::Text(..) => {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_TEXT_NODE);
}
}
}
}
impl Display for BuildResultFlags {
fn fmt(&self, formatter: &mut Formatter) -> FormatResult {
if self.is_empty() {
return Ok(())
}
let mut first = true;
for (bit, name) in NAMES.iter().enumerate() {
if (self.bits() >> bit) & 1 == 0 {
continue;
}
if !first {
formatter.write_str(", ")?;
} else {
first = false;
}
formatter.write_str(name)?;
}
return Ok(());
// Must match the order in `BuildResultFlags`.
static NAMES: &'static [&'static str] = &[
"<clipPath>",
"<defs>",
"<filter>",
"<image>",
"<linearGradient>",
"<mask>",
"<pattern>",
"<radialGradient>",
"nested <svg>",
"<text>",
"paint server element",
"clip-path attribute",
"filter attribute",
"mask attribute",
"opacity attribute",
];
}
}
trait PaintExt {
fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Self;
}
impl PaintExt for Paint {
#[inline]
fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Paint {
Paint {
color: match *svg_paint {
UsvgPaint::Color(color) => ColorU::from_svg_color(color),
UsvgPaint::Link(_) => {
// TODO(pcwalton)
result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT);
ColorU::black()
}
}
}
}
}
fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> RectF32 {
RectF32::new(
Point2DF32::new(rect.x as f32, rect.y as f32),
Point2DF32::new(rect.width as f32, rect.height as f32),
)
}
fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2DF32 {
Transform2DF32::row_major(
transform.a as f32,
transform.b as f32,
transform.c as f32,
transform.d as f32,
transform.e as f32,
transform.f as f32,
)
}
struct UsvgPathToSegments<I>
where
I: Iterator<Item = UsvgPathSegment>,
{
iter: I,
first_subpath_point: Point2DF32,
last_subpath_point: Point2DF32,
just_moved: bool,
}
impl<I> UsvgPathToSegments<I>
where
I: Iterator<Item = UsvgPathSegment>,
{
fn new(iter: I) -> UsvgPathToSegments<I> {
UsvgPathToSegments {
iter,
first_subpath_point: Point2DF32::default(),
last_subpath_point: Point2DF32::default(),
just_moved: false,
}
}
}
impl<I> Iterator for UsvgPathToSegments<I>
where
I: Iterator<Item = UsvgPathSegment>,
{
type Item = Segment;
fn next(&mut self) -> Option<Segment> {
match self.iter.next()? {
UsvgPathSegment::MoveTo { x, y } => {
let to = Point2DF32::new(x as f32, y as f32);
self.first_subpath_point = to;
self.last_subpath_point = to;
self.just_moved = true;
self.next()
}
UsvgPathSegment::LineTo { x, y } => {
let to = Point2DF32::new(x as f32, y as f32);
let mut segment =
Segment::line(&LineSegmentF32::new(&self.last_subpath_point, &to));
if self.just_moved {
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
}
self.last_subpath_point = to;
self.just_moved = false;
Some(segment)
}
UsvgPathSegment::CurveTo {
x1,
y1,
x2,
y2,
x,
y,
} => {
let ctrl0 = Point2DF32::new(x1 as f32, y1 as f32);
let ctrl1 = Point2DF32::new(x2 as f32, y2 as f32);
let to = Point2DF32::new(x as f32, y as f32);
let mut segment = Segment::cubic(
&LineSegmentF32::new(&self.last_subpath_point, &to),
&LineSegmentF32::new(&ctrl0, &ctrl1),
);
if self.just_moved {
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
}
self.last_subpath_point = to;
self.just_moved = false;
Some(segment)
}
UsvgPathSegment::ClosePath => {
let mut segment = Segment::line(&LineSegmentF32::new(
&self.last_subpath_point,
&self.first_subpath_point,
));
segment.flags.insert(SegmentFlags::CLOSES_SUBPATH);
self.just_moved = false;
self.last_subpath_point = self.first_subpath_point;
Some(segment)
}
}
}
}
trait ColorUExt {
fn from_svg_color(svg_color: SvgColor) -> Self;
}
impl ColorUExt for ColorU {
#[inline]
fn from_svg_color(svg_color: SvgColor) -> ColorU {
ColorU {
r: svg_color.red,
g: svg_color.green,
b: svg_color.blue,
a: 255,
}
}
}

23
ui/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "pathfinder_ui"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
[dependencies.hashbrown]
version = "0.1"
features = ["serde"]
[dependencies.pathfinder_geometry]
path = "../geometry"
[dependencies.pathfinder_gpu]
path = "../gpu"
[dependencies.pathfinder_simd]
path = "../simd"

780
ui/src/lib.rs Normal file
View File

@ -0,0 +1,780 @@
// pathfinder/ui/src/lib.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 minimal immediate mode UI, for debugging.
//!
//! This can be used in your own applications as an ultra-minimal lightweight
//! alternative to dear imgui, Conrod, etc.
#[macro_use]
extern crate serde_derive;
use hashbrown::HashMap;
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::color::ColorU;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, Device, Primitive};
use pathfinder_gpu::{RenderState, UniformData, VertexAttrType};
use pathfinder_simd::default::F32x4;
use serde_json;
use std::mem;
pub const PADDING: i32 = 12;
pub const LINE_HEIGHT: i32 = 42;
pub const FONT_ASCENT: i32 = 28;
pub const BUTTON_WIDTH: i32 = PADDING * 2 + ICON_SIZE;
pub const BUTTON_HEIGHT: i32 = PADDING * 2 + ICON_SIZE;
pub const BUTTON_TEXT_OFFSET: i32 = PADDING + 36;
pub const TOOLTIP_HEIGHT: i32 = FONT_ASCENT + PADDING * 2;
const DEBUG_TEXTURE_VERTEX_SIZE: usize = 8;
const DEBUG_SOLID_VERTEX_SIZE: usize = 4;
const ICON_SIZE: i32 = 48;
const SWITCH_SEGMENT_SIZE: i32 = 96;
pub static TEXT_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
pub static WINDOW_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 - 90 };
static BUTTON_ICON_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
static OUTLINE_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 192 };
static INVERTED_TEXT_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 };
static FONT_JSON_VIRTUAL_PATH: &'static str = "debug-fonts/regular.json";
static FONT_PNG_NAME: &'static str = "debug-font";
static CORNER_FILL_PNG_NAME: &'static str = "debug-corner-fill";
static CORNER_OUTLINE_PNG_NAME: &'static str = "debug-corner-outline";
static QUAD_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3];
static RECT_LINE_INDICES: [u32; 8] = [0, 1, 1, 2, 2, 3, 3, 0];
static OUTLINE_RECT_LINE_INDICES: [u32; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
pub struct UI<D> where D: Device {
pub event_queue: UIEventQueue,
pub mouse_position: Point2DF32,
framebuffer_size: Point2DI32,
texture_program: DebugTextureProgram<D>,
texture_vertex_array: DebugTextureVertexArray<D>,
solid_program: DebugSolidProgram<D>,
solid_vertex_array: DebugSolidVertexArray<D>,
font: DebugFont,
font_texture: D::Texture,
corner_fill_texture: D::Texture,
corner_outline_texture: D::Texture,
}
impl<D> UI<D> where D: Device {
pub fn new(device: &D, resources: &dyn ResourceLoader, framebuffer_size: Point2DI32) -> UI<D> {
let texture_program = DebugTextureProgram::new(device, resources);
let texture_vertex_array = DebugTextureVertexArray::new(device, &texture_program);
let font = DebugFont::load(resources);
let solid_program = DebugSolidProgram::new(device, resources);
let solid_vertex_array = DebugSolidVertexArray::new(device, &solid_program);
let font_texture = device.create_texture_from_png(resources, FONT_PNG_NAME);
let corner_fill_texture = device.create_texture_from_png(resources, CORNER_FILL_PNG_NAME);
let corner_outline_texture = device.create_texture_from_png(resources,
CORNER_OUTLINE_PNG_NAME);
UI {
event_queue: UIEventQueue::new(),
mouse_position: Point2DF32::default(),
framebuffer_size,
texture_program,
texture_vertex_array,
font,
solid_program,
solid_vertex_array,
font_texture,
corner_fill_texture,
corner_outline_texture,
}
}
pub fn framebuffer_size(&self) -> Point2DI32 {
self.framebuffer_size
}
pub fn set_framebuffer_size(&mut self, window_size: Point2DI32) {
self.framebuffer_size = window_size;
}
pub fn draw_solid_rect(&self, device: &D, rect: RectI32, color: ColorU) {
self.draw_rect(device, rect, color, true);
}
pub fn draw_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) {
self.draw_rect(device, rect, color, false);
}
fn draw_rect(&self, device: &D, rect: RectI32, color: ColorU, filled: bool) {
let vertex_data = [
DebugSolidVertex::new(rect.origin()),
DebugSolidVertex::new(rect.upper_right()),
DebugSolidVertex::new(rect.lower_right()),
DebugSolidVertex::new(rect.lower_left()),
];
if filled {
self.draw_solid_rects_with_vertex_data(device,
&vertex_data,
&QUAD_INDICES,
color,
true);
} else {
self.draw_solid_rects_with_vertex_data(device,
&vertex_data,
&RECT_LINE_INDICES,
color,
false);
}
}
fn draw_solid_rects_with_vertex_data(&self,
device: &D,
vertex_data: &[DebugSolidVertex],
index_data: &[u32],
color: ColorU,
filled: bool) {
device.bind_vertex_array(&self.solid_vertex_array.vertex_array);
device.allocate_buffer(&self.solid_vertex_array.vertex_buffer,
BufferData::Memory(vertex_data),
BufferTarget::Vertex,
BufferUploadMode::Dynamic);
device.allocate_buffer(&self.solid_vertex_array.index_buffer,
BufferData::Memory(index_data),
BufferTarget::Index,
BufferUploadMode::Dynamic);
device.use_program(&self.solid_program.program);
device.set_uniform(&self.solid_program.framebuffer_size_uniform,
UniformData::Vec2(self.framebuffer_size.0.to_f32x4()));
set_color_uniform(device, &self.solid_program.color_uniform, color);
let primitive = if filled { Primitive::Triangles } else { Primitive::Lines };
device.draw_elements(primitive, index_data.len() as u32, &RenderState {
blend: BlendState::RGBOneAlphaOneMinusSrcAlpha,
..RenderState::default()
});
}
pub fn draw_text(&self, device: &D, string: &str, origin: Point2DI32, invert: bool) {
let mut next = origin;
let char_count = string.chars().count();
let mut vertex_data = Vec::with_capacity(char_count * 4);
let mut index_data = Vec::with_capacity(char_count * 6);
for mut character in string.chars() {
if !self.font.characters.contains_key(&character) {
character = '?';
}
let info = &self.font.characters[&character];
let position_rect =
RectI32::new(Point2DI32::new(next.x() - info.origin_x, next.y() - info.origin_y),
Point2DI32::new(info.width as i32, info.height as i32));
let tex_coord_rect = RectI32::new(Point2DI32::new(info.x, info.y),
Point2DI32::new(info.width, info.height));
let first_vertex_index = vertex_data.len();
vertex_data.extend_from_slice(&[
DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()),
DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()),
DebugTextureVertex::new(position_rect.lower_right(), tex_coord_rect.lower_right()),
DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()),
]);
index_data.extend(QUAD_INDICES.iter().map(|&i| i + first_vertex_index as u32));
let next_x = next.x() + info.advance;
next.set_x(next_x);
}
let color = if invert { INVERTED_TEXT_COLOR } else { TEXT_COLOR };
self.draw_texture_with_vertex_data(device,
&vertex_data,
&index_data,
&self.font_texture,
color);
}
pub fn draw_texture(&self,
device: &D,
origin: Point2DI32,
texture: &D::Texture,
color: ColorU) {
let position_rect = RectI32::new(origin, device.texture_size(&texture));
let tex_coord_rect = RectI32::new(Point2DI32::default(), position_rect.size());
let vertex_data = [
DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()),
DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()),
DebugTextureVertex::new(position_rect.lower_right(), tex_coord_rect.lower_right()),
DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()),
];
self.draw_texture_with_vertex_data(device, &vertex_data, &QUAD_INDICES, texture, color);
}
pub fn measure_text(&self, string: &str) -> i32 {
let mut next = 0;
for mut character in string.chars() {
if !self.font.characters.contains_key(&character) {
character = '?';
}
let info = &self.font.characters[&character];
next += info.advance;
}
next
}
#[inline]
pub fn measure_switch(&self, segment_count: u8) -> i32 {
SWITCH_SEGMENT_SIZE * segment_count as i32 + (segment_count - 1) as i32
}
pub fn draw_solid_rounded_rect(&self, device: &D, rect: RectI32, color: ColorU) {
let corner_texture = self.corner_texture(true);
let corner_rects = CornerRects::new(device, rect, corner_texture);
self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects);
let solid_rect_mid = RectI32::from_points(corner_rects.upper_left.upper_right(),
corner_rects.lower_right.lower_left());
let solid_rect_left = RectI32::from_points(corner_rects.upper_left.lower_left(),
corner_rects.lower_left.upper_right());
let solid_rect_right = RectI32::from_points(corner_rects.upper_right.lower_left(),
corner_rects.lower_right.upper_right());
let vertex_data = vec![
DebugSolidVertex::new(solid_rect_mid.origin()),
DebugSolidVertex::new(solid_rect_mid.upper_right()),
DebugSolidVertex::new(solid_rect_mid.lower_right()),
DebugSolidVertex::new(solid_rect_mid.lower_left()),
DebugSolidVertex::new(solid_rect_left.origin()),
DebugSolidVertex::new(solid_rect_left.upper_right()),
DebugSolidVertex::new(solid_rect_left.lower_right()),
DebugSolidVertex::new(solid_rect_left.lower_left()),
DebugSolidVertex::new(solid_rect_right.origin()),
DebugSolidVertex::new(solid_rect_right.upper_right()),
DebugSolidVertex::new(solid_rect_right.lower_right()),
DebugSolidVertex::new(solid_rect_right.lower_left()),
];
let mut index_data = Vec::with_capacity(18);
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 0));
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4));
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8));
self.draw_solid_rects_with_vertex_data(device,
&vertex_data,
&index_data[0..18],
color,
true);
}
pub fn draw_rounded_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) {
let corner_texture = self.corner_texture(false);
let corner_rects = CornerRects::new(device, rect, corner_texture);
self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects);
let vertex_data = vec![
DebugSolidVertex::new(corner_rects.upper_left.upper_right()),
DebugSolidVertex::new(corner_rects.upper_right.origin()),
DebugSolidVertex::new(corner_rects.upper_right.lower_right()),
DebugSolidVertex::new(corner_rects.lower_right.upper_right()),
DebugSolidVertex::new(corner_rects.lower_left.lower_right()),
DebugSolidVertex::new(corner_rects.lower_right.lower_left()),
DebugSolidVertex::new(corner_rects.upper_left.lower_left()),
DebugSolidVertex::new(corner_rects.lower_left.origin()),
];
let index_data = &OUTLINE_RECT_LINE_INDICES;
self.draw_solid_rects_with_vertex_data(device, &vertex_data, index_data, color, false);
}
// TODO(pcwalton): `LineSegmentI32`.
fn draw_line(&self, device: &D, from: Point2DI32, to: Point2DI32, color: ColorU) {
let vertex_data = vec![DebugSolidVertex::new(from), DebugSolidVertex::new(to)];
self.draw_solid_rects_with_vertex_data(device, &vertex_data, &[0, 1], color, false);
}
fn draw_rounded_rect_corners(&self,
device: &D,
color: ColorU,
texture: &D::Texture,
corner_rects: &CornerRects) {
let corner_size = device.texture_size(&texture);
let tex_coord_rect = RectI32::new(Point2DI32::default(), corner_size);
let vertex_data = vec![
DebugTextureVertex::new(
corner_rects.upper_left.origin(), tex_coord_rect.origin()),
DebugTextureVertex::new(
corner_rects.upper_left.upper_right(), tex_coord_rect.upper_right()),
DebugTextureVertex::new(
corner_rects.upper_left.lower_right(), tex_coord_rect.lower_right()),
DebugTextureVertex::new(
corner_rects.upper_left.lower_left(), tex_coord_rect.lower_left()),
DebugTextureVertex::new(
corner_rects.upper_right.origin(), tex_coord_rect.lower_left()),
DebugTextureVertex::new(
corner_rects.upper_right.upper_right(), tex_coord_rect.origin()),
DebugTextureVertex::new(
corner_rects.upper_right.lower_right(), tex_coord_rect.upper_right()),
DebugTextureVertex::new(
corner_rects.upper_right.lower_left(), tex_coord_rect.lower_right()),
DebugTextureVertex::new(
corner_rects.lower_left.origin(), tex_coord_rect.upper_right()),
DebugTextureVertex::new(
corner_rects.lower_left.upper_right(), tex_coord_rect.lower_right()),
DebugTextureVertex::new(
corner_rects.lower_left.lower_right(), tex_coord_rect.lower_left()),
DebugTextureVertex::new(
corner_rects.lower_left.lower_left(), tex_coord_rect.origin()),
DebugTextureVertex::new(
corner_rects.lower_right.origin(), tex_coord_rect.lower_right()),
DebugTextureVertex::new(
corner_rects.lower_right.upper_right(), tex_coord_rect.lower_left()),
DebugTextureVertex::new(
corner_rects.lower_right.lower_right(), tex_coord_rect.origin()),
DebugTextureVertex::new(
corner_rects.lower_right.lower_left(), tex_coord_rect.upper_right()),
];
let mut index_data = Vec::with_capacity(24);
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 0));
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4));
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8));
index_data.extend(QUAD_INDICES.iter().map(|&index| index + 12));
self.draw_texture_with_vertex_data(device, &vertex_data, &index_data, texture, color);
}
fn corner_texture(&self, filled: bool) -> &D::Texture {
if filled { &self.corner_fill_texture } else { &self.corner_outline_texture }
}
fn draw_texture_with_vertex_data(&self,
device: &D,
vertex_data: &[DebugTextureVertex],
index_data: &[u32],
texture: &D::Texture,
color: ColorU) {
device.allocate_buffer(&self.texture_vertex_array.vertex_buffer,
BufferData::Memory(vertex_data),
BufferTarget::Vertex,
BufferUploadMode::Dynamic);
device.allocate_buffer(&self.texture_vertex_array.index_buffer,
BufferData::Memory(index_data),
BufferTarget::Index,
BufferUploadMode::Dynamic);
device.bind_vertex_array(&self.texture_vertex_array.vertex_array);
device.use_program(&self.texture_program.program);
device.set_uniform(&self.texture_program.framebuffer_size_uniform,
UniformData::Vec2(self.framebuffer_size.0.to_f32x4()));
device.set_uniform(&self.texture_program.texture_size_uniform,
UniformData::Vec2(device.texture_size(&texture).0.to_f32x4()));
set_color_uniform(device, &self.texture_program.color_uniform, color);
device.bind_texture(texture, 0);
device.set_uniform(&self.texture_program.texture_uniform, UniformData::TextureUnit(0));
device.draw_elements(Primitive::Triangles, index_data.len() as u32, &RenderState {
blend: BlendState::RGBOneAlphaOneMinusSrcAlpha,
..RenderState::default()
});
}
pub fn draw_button(&mut self, device: &D, origin: Point2DI32, texture: &D::Texture) -> bool {
let button_rect = RectI32::new(origin, Point2DI32::new(BUTTON_WIDTH, BUTTON_HEIGHT));
self.draw_solid_rounded_rect(device, button_rect, WINDOW_COLOR);
self.draw_rounded_rect_outline(device, button_rect, OUTLINE_COLOR);
self.draw_texture(device,
origin + Point2DI32::new(PADDING, PADDING),
texture,
BUTTON_ICON_COLOR);
self.event_queue.handle_mouse_down_in_rect(button_rect).is_some()
}
pub fn draw_text_switch(&mut self,
device: &D,
mut origin: Point2DI32,
segment_labels: &[&str],
mut value: u8)
-> u8 {
value = self.draw_switch(device, origin, value, segment_labels.len() as u8);
origin = origin + Point2DI32::new(0, BUTTON_TEXT_OFFSET);
for (segment_index, segment_label) in segment_labels.iter().enumerate() {
let label_width = self.measure_text(segment_label);
let offset = SWITCH_SEGMENT_SIZE / 2 - label_width / 2;
self.draw_text(device,
segment_label,
origin + Point2DI32::new(offset, 0),
segment_index as u8 == value);
origin += Point2DI32::new(SWITCH_SEGMENT_SIZE + 1, 0);
}
value
}
/// TODO(pcwalton): Support switches with more than two segments.
pub fn draw_image_switch(&mut self,
device: &D,
origin: Point2DI32,
off_texture: &D::Texture,
on_texture: &D::Texture,
mut value: bool)
-> bool {
value = self.draw_switch(device, origin, value as u8, 2) != 0;
let off_offset = SWITCH_SEGMENT_SIZE / 2 - device.texture_size(off_texture).x() / 2;
let on_offset = SWITCH_SEGMENT_SIZE + SWITCH_SEGMENT_SIZE / 2 -
device.texture_size(on_texture).x() / 2;
let off_color = if !value { WINDOW_COLOR } else { TEXT_COLOR };
let on_color = if value { WINDOW_COLOR } else { TEXT_COLOR };
self.draw_texture(device,
origin + Point2DI32::new(off_offset, PADDING),
off_texture,
off_color);
self.draw_texture(device,
origin + Point2DI32::new(on_offset, PADDING),
on_texture,
on_color);
value
}
fn draw_switch(&mut self, device: &D, origin: Point2DI32, mut value: u8, segment_count: u8)
-> u8 {
let widget_width = self.measure_switch(segment_count);
let widget_rect = RectI32::new(origin, Point2DI32::new(widget_width, BUTTON_HEIGHT));
if let Some(position) = self.event_queue.handle_mouse_down_in_rect(widget_rect) {
let segment = (position.x() / (SWITCH_SEGMENT_SIZE + 1)) as u8;
value = segment.min(segment_count - 1);
}
self.draw_solid_rounded_rect(device, widget_rect, WINDOW_COLOR);
self.draw_rounded_rect_outline(device, widget_rect, OUTLINE_COLOR);
let highlight_size = Point2DI32::new(SWITCH_SEGMENT_SIZE, BUTTON_HEIGHT);
let x_offset = value as i32 * SWITCH_SEGMENT_SIZE + (value as i32 - 1);
self.draw_solid_rounded_rect(device,
RectI32::new(origin + Point2DI32::new(x_offset, 0),
highlight_size),
TEXT_COLOR);
let mut segment_origin = origin + Point2DI32::new(SWITCH_SEGMENT_SIZE + 1, 0);
for next_segment_index in 1..segment_count {
let prev_segment_index = next_segment_index - 1;
if value != prev_segment_index && value != next_segment_index {
self.draw_line(device,
segment_origin,
segment_origin + Point2DI32::new(0, BUTTON_HEIGHT),
TEXT_COLOR);
}
segment_origin = segment_origin + Point2DI32::new(SWITCH_SEGMENT_SIZE + 1, 0);
}
value
}
pub fn draw_tooltip(&self, device: &D, string: &str, rect: RectI32) {
if !rect.to_f32().contains_point(self.mouse_position) {
return;
}
let text_size = self.measure_text(string);
let window_size = Point2DI32::new(text_size + PADDING * 2, TOOLTIP_HEIGHT);
let origin = rect.origin() - Point2DI32::new(0, window_size.y() + PADDING);
self.draw_solid_rounded_rect(device, RectI32::new(origin, window_size), WINDOW_COLOR);
self.draw_text(device,
string,
origin + Point2DI32::new(PADDING, PADDING + FONT_ASCENT),
false);
}
}
struct DebugTextureProgram<D> where D: Device {
program: D::Program,
framebuffer_size_uniform: D::Uniform,
texture_size_uniform: D::Uniform,
texture_uniform: D::Uniform,
color_uniform: D::Uniform,
}
impl<D> DebugTextureProgram<D> where D: Device {
fn new(device: &D, resources: &dyn ResourceLoader) -> DebugTextureProgram<D> {
let program = device.create_program(resources, "debug_texture");
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
let texture_size_uniform = device.get_uniform(&program, "TextureSize");
let texture_uniform = device.get_uniform(&program, "Texture");
let color_uniform = device.get_uniform(&program, "Color");
DebugTextureProgram {
program,
framebuffer_size_uniform,
texture_size_uniform,
texture_uniform,
color_uniform,
}
}
}
struct DebugTextureVertexArray<D> where D: Device {
vertex_array: D::VertexArray,
vertex_buffer: D::Buffer,
index_buffer: D::Buffer,
}
impl<D> DebugTextureVertexArray<D> where D: Device {
fn new(device: &D, debug_texture_program: &DebugTextureProgram<D>)
-> DebugTextureVertexArray<D> {
let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer());
let vertex_array = device.create_vertex_array();
let position_attr = device.get_vertex_attr(&debug_texture_program.program, "Position");
let tex_coord_attr = device.get_vertex_attr(&debug_texture_program.program, "TexCoord");
device.bind_vertex_array(&vertex_array);
device.use_program(&debug_texture_program.program);
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.bind_buffer(&index_buffer, BufferTarget::Index);
device.configure_float_vertex_attr(&position_attr,
2,
VertexAttrType::U16,
false,
DEBUG_TEXTURE_VERTEX_SIZE,
0,
0);
device.configure_float_vertex_attr(&tex_coord_attr,
2,
VertexAttrType::U16,
false,
DEBUG_TEXTURE_VERTEX_SIZE,
4,
0);
DebugTextureVertexArray { vertex_array, vertex_buffer, index_buffer }
}
}
struct DebugSolidVertexArray<D> where D: Device {
vertex_array: D::VertexArray,
vertex_buffer: D::Buffer,
index_buffer: D::Buffer,
}
impl<D> DebugSolidVertexArray<D> where D: Device {
fn new(device: &D, debug_solid_program: &DebugSolidProgram<D>) -> DebugSolidVertexArray<D> {
let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer());
let vertex_array = device.create_vertex_array();
let position_attr = device.get_vertex_attr(&debug_solid_program.program, "Position");
device.bind_vertex_array(&vertex_array);
device.use_program(&debug_solid_program.program);
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.bind_buffer(&index_buffer, BufferTarget::Index);
device.configure_float_vertex_attr(&position_attr,
2,
VertexAttrType::U16,
false,
DEBUG_SOLID_VERTEX_SIZE,
0,
0);
DebugSolidVertexArray { vertex_array, vertex_buffer, index_buffer }
}
}
struct DebugSolidProgram<D> where D: Device {
program: D::Program,
framebuffer_size_uniform: D::Uniform,
color_uniform: D::Uniform,
}
impl<D> DebugSolidProgram<D> where D: Device {
fn new(device: &D, resources: &dyn ResourceLoader) -> DebugSolidProgram<D> {
let program = device.create_program(resources, "debug_solid");
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
let color_uniform = device.get_uniform(&program, "Color");
DebugSolidProgram { program, framebuffer_size_uniform, color_uniform }
}
}
#[derive(Clone, Copy, Debug)]
#[allow(dead_code)]
#[repr(C)]
struct DebugTextureVertex {
position_x: i16,
position_y: i16,
tex_coord_x: u16,
tex_coord_y: u16,
}
impl DebugTextureVertex {
fn new(position: Point2DI32, tex_coord: Point2DI32) -> DebugTextureVertex {
DebugTextureVertex {
position_x: position.x() as i16,
position_y: position.y() as i16,
tex_coord_x: tex_coord.x() as u16,
tex_coord_y: tex_coord.y() as u16,
}
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
#[repr(C)]
struct DebugSolidVertex {
position_x: i16,
position_y: i16,
}
impl DebugSolidVertex {
fn new(position: Point2DI32) -> DebugSolidVertex {
DebugSolidVertex { position_x: position.x() as i16, position_y: position.y() as i16 }
}
}
struct CornerRects {
upper_left: RectI32,
upper_right: RectI32,
lower_left: RectI32,
lower_right: RectI32,
}
impl CornerRects {
fn new<D>(device: &D, rect: RectI32, texture: &D::Texture) -> CornerRects where D: Device {
let size = device.texture_size(texture);
CornerRects {
upper_left: RectI32::new(rect.origin(), size),
upper_right: RectI32::new(rect.upper_right() - Point2DI32::new(size.x(), 0), size),
lower_left: RectI32::new(rect.lower_left() - Point2DI32::new(0, size.y()), size),
lower_right: RectI32::new(rect.lower_right() - size, size),
}
}
}
fn set_color_uniform<D>(device: &D, uniform: &D::Uniform, color: ColorU) where D: Device {
let color = F32x4::new(color.r as f32, color.g as f32, color.b as f32, color.a as f32);
device.set_uniform(uniform, UniformData::Vec4(color * F32x4::splat(1.0 / 255.0)));
}
#[derive(Clone, Copy)]
pub enum UIEvent {
MouseDown(MousePosition),
MouseDragged(MousePosition),
}
pub struct UIEventQueue {
events: Vec<UIEvent>,
}
impl UIEventQueue {
fn new() -> UIEventQueue {
UIEventQueue { events: vec![] }
}
pub fn push(&mut self, event: UIEvent) {
self.events.push(event);
}
pub fn drain(&mut self) -> Vec<UIEvent> {
mem::replace(&mut self.events, vec![])
}
pub fn handle_mouse_down_in_rect(&mut self, rect: RectI32) -> Option<Point2DI32> {
let (mut remaining_events, mut result) = (vec![], None);
for event in self.events.drain(..) {
match event {
UIEvent::MouseDown(position) if rect.contains_point(position.absolute) => {
result = Some(position.absolute - rect.origin());
}
event => remaining_events.push(event),
}
}
self.events = remaining_events;
result
}
pub fn handle_mouse_down_or_dragged_in_rect(&mut self, rect: RectI32) -> Option<Point2DI32> {
let (mut remaining_events, mut result) = (vec![], None);
for event in self.events.drain(..) {
match event {
UIEvent::MouseDown(position) | UIEvent::MouseDragged(position) if
rect.contains_point(position.absolute) => {
result = Some(position.absolute - rect.origin());
}
event => remaining_events.push(event),
}
}
self.events = remaining_events;
result
}
}
#[derive(Clone, Copy)]
pub struct MousePosition {
pub absolute: Point2DI32,
pub relative: Point2DI32,
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct DebugFont {
name: String,
size: i32,
bold: bool,
italic: bool,
width: u32,
height: u32,
characters: HashMap<char, DebugCharacter>,
}
#[derive(Deserialize)]
struct DebugCharacter {
x: i32,
y: i32,
width: i32,
height: i32,
#[serde(rename = "originX")]
origin_x: i32,
#[serde(rename = "originY")]
origin_y: i32,
advance: i32,
}
impl DebugFont {
#[inline]
fn load(resources: &dyn ResourceLoader) -> DebugFont {
serde_json::from_slice(&resources.slurp(FONT_JSON_VIRTUAL_PATH).unwrap()).unwrap()
}
}

View File

@ -1,18 +0,0 @@
[package]
name = "pathfinder"
version = "0.2.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
clap = "2.27"
lyon_path = "0.12"
[dependencies.font-kit]
git = "https://github.com/pcwalton/font-kit"
features = ["loader-freetype-default"]
[dependencies.pathfinder_partitioner]
path = "../../partitioner"
[dependencies.pathfinder_path_utils]
path = "../../path-utils"

View File

@ -1,111 +0,0 @@
// pathfinder/utils/frontend/main.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// 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.
//! Pathfinder is built as a set of modular Rust crates and accompanying shaders. Depending on how
//! you plan to use Pathfinder, you may need to link against many of these crates, or you may not
//! need to link against any of them and and only use the shaders at runtime.
//!
//! Typically, if you need to generate paths at runtime or load fonts on the fly, then you will
//! need to use the `pathfinder_partitioner` and/or `pathfinder_font_renderer` crates. If your app
//! instead uses a fixed set of paths or fonts, then you may wish to consider running the
//! Pathfinder command-line tool as part of your build process. Note that in the latter case you
//! may not need to ship any Rust code at all!
//!
//! This crate defines the `pathfinder` command line tool. It takes a font as an argument and
//! produces *mesh libraries* for the glyphs you wish to include. A *mesh library* is essentially a
//! simple storage format for VBOs. To render these paths, you can directly upload these VBOs to
//! the GPU and render them using the shaders provided.
extern crate clap;
extern crate font_kit;
extern crate lyon_path;
extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils;
use clap::{App, Arg};
use font_kit::font::Font;
use font_kit::hinting::HintingOptions;
use lyon_path::PathEvent;
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
use lyon_path::default::Path as LyonPath;
use pathfinder_partitioner::FillRule;
use pathfinder_partitioner::mesh_pack::MeshPack;
use pathfinder_partitioner::partitioner::Partitioner;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::process;
fn convert_font(font_path: &Path, output_path: &Path) -> Result<(), ()> {
let font = try!(Font::from_path(font_path, 0).map_err(drop));
let glyph_count = font.glyph_count();
let mut paths: Vec<(u16, Vec<PathEvent>)> = vec![];
let mut mesh_pack = MeshPack::new();
for glyph_index in 0..glyph_count {
let mut path_builder = LyonPath::builder();
if font.outline(glyph_index, HintingOptions::None, &mut path_builder).is_err() {
continue
}
let path = path_builder.build();
let mut partitioner = Partitioner::new();
let path_index = (glyph_index + 1) as u16;
partitioner.mesh_mut().push_stencil_segments(path.iter());
path.iter().for_each(|event| partitioner.builder_mut().path_event(event));
partitioner.partition(FillRule::Winding);
partitioner.builder_mut().build_and_reset();
paths.push((path_index, path.iter().collect()));
mesh_pack.push(partitioner.into_mesh());
}
let mut output_file = try!(File::create(output_path).map_err(drop));
mesh_pack.serialize_into(&mut output_file).map_err(drop)
}
pub fn main() {
let app = App::new("Pathfinder Build Utility")
.version("0.1")
.author("The Pathfinder Project Developers")
.about("Builds meshes from fonts for use with Pathfinder")
.arg(Arg::with_name("FONT-PATH").help("The `.ttf` or `.otf` font file to use")
.required(true)
.index(1))
.arg(Arg::with_name("OUTPUT-PATH").help("The `.pfml` mesh library to produce").index(2));
let matches = app.get_matches();
let font_path = matches.value_of("FONT-PATH").unwrap();
let font_path = Path::new(font_path);
let output_path = match matches.value_of("OUTPUT-PATH") {
Some(output_path) => PathBuf::from(output_path),
None => {
match font_path.file_stem() {
None => {
eprintln!("error: No valid input path specified");
process::exit(1)
}
Some(output_path) => {
let mut output_path = PathBuf::from(output_path);
output_path.set_extension("pfml");
output_path
}
}
}
};
if convert_font(font_path, &output_path).is_err() {
// TODO(pcwalton): Better error handling.
eprintln!("error: Failed");
process::exit(1)
}
}

View File

@ -1,7 +1,8 @@
[package]
name = "pathfinder_gfx_utils"
version = "0.2.0"
name = "svg-to-skia"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
euclid = "0.19"
usvg = "0.4"

View File

@ -0,0 +1,87 @@
// pathfinder/utils/svg-to-skia/src/main.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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 std::env;
use usvg::{Node, NodeKind, Options, Paint, PathSegment, Tree};
fn main() {
let input_path = env::args().skip(1).next().unwrap();
let tree = Tree::from_file(&input_path, &Options::default()).unwrap();
println!("#ifndef PAINT_H");
println!("#define PAINT_H");
println!("static void paint(SkCanvas *canvas) {{");
println!(" SkPaint paint;");
println!(" SkPath path;");
println!(" paint.setAntiAlias(true);");
println!(" canvas->clear(SK_ColorWHITE);");
let root = &tree.root();
match *root.borrow() {
NodeKind::Svg(_) => {
for kid in root.children() {
process_node(&kid);
}
}
_ => unreachable!(),
}
println!("}}");
println!("#endif");
}
fn process_node(node: &Node) {
match *node.borrow() {
NodeKind::Group(_) => {
for kid in node.children() {
process_node(&kid)
}
}
NodeKind::Path(ref path) => {
for segment in path.segments.iter() {
match segment {
PathSegment::MoveTo { x, y } => println!(" path.moveTo({}, {});", x, y),
PathSegment::LineTo { x, y } => println!(" path.lineTo({}, {});", x, y),
PathSegment::CurveTo { x1, y1, x2, y2, x, y } => {
println!(" path.cubicTo({}, {}, {}, {}, {}, {});",
x1, y1, x2, y2, x, y);
}
PathSegment::ClosePath => println!(" path.close();"),
}
}
if let Some(ref fill) = path.fill {
set_color(&fill.paint);
println!(" paint.setStyle(SkPaint::kFill_Style);");
println!(" canvas->drawPath(path, paint);");
}
if let Some(ref stroke) = path.stroke {
set_color(&stroke.paint);
println!(" paint.setStrokeWidth({});", stroke.width.value());
println!(" paint.setStyle(SkPaint::kStroke_Style);");
println!(" canvas->drawPath(path, paint);");
}
println!(" path.reset();");
}
_ => {}
}
}
fn set_color(paint: &Paint) {
if let Paint::Color(color) = *paint {
println!(" paint.setColor(0x{:x});",
((color.red as u32) << 16) |
((color.green as u32) << 8) |
((color.blue as u32) << 0) |
(0xff << 24));
}
}